VAITP Dataset

← Back to the dataset

CVE-2026-2275

CrewAI CodeInterpreter's unsafe fallback to SandboxPython enables RCE.

  • CVSS 9.6
  • CWE-749
  • Design Defects
  • Remote

The CrewAI CodeInterpreter tool falls back to SandboxPython when it cannot reach Docker, which can enable RCE through arbitrary C function calling.

CVSS base score
9.6
Published
2026-03-30
OWASP
A06 Vulnerable and Outdated Components
Orthogonal defect classification
Algorithm
Code defect classification
Incorrect Algorithm
Category
Design Defects
Subcategory
Inadequate Error Handling
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
CrewAI
Fixed by upgrading
Yes

Solution

Upgrade `crewai` to version 0.35.9 or later.

Vulnerable code sample

import subprocess
import sys
import os
import uuid

# The code below is a conceptual representation of the vulnerable logic
# present in the CrewAI CodeInterpreterTool before it was patched.
# The core of the vulnerability was the fallback from a secure Docker
# environment to an insecure local subprocess execution when Docker was
# not available.

class VulnerableCodeInterpreter:
    def _execute_code(self, code: str) -> str:
        """
        A method that attempts to run code in Docker and falls back
        to a local subprocess, demonstrating the vulnerability.
        """
        # In the original code, a temporary file was often used.
        file_path = f"temp_code_{uuid.uuid4()}.py"
        with open(file_path, "w") as f:
            f.write(code)

        try:
            # 1. Attempt to use a secure, sandboxed environment (Docker).
            # In this demonstration, we simulate that the Docker daemon is
            # not available by immediately raising an exception.
            # In the real code, this would involve using the `docker` library.
            raise ConnectionError("Simulated failure: Docker daemon is not running.")

            # The actual code would have looked something like this:
            #
            # import docker
            # client = docker.from_env()
            # container = client.containers.run(
            #     'python:3.11-slim',
            #     f'python {file_path}',
            #     remove=True,
            #     volumes={os.getcwd(): {'bind': '/app', 'mode': 'rw'}},
            #     working_dir='/app'
            # )
            # output = container.decode('utf-8')

        except (ConnectionError, Exception):
            # 2. VULNERABLE FALLBACK: If the Docker attempt fails, the code
            #    falls back to executing the script directly on the host
            #    machine using a subprocess. This is not sandboxed and
            #    allows for Remote Code Execution (RCE).
            result = subprocess.run(
                [sys.executable, file_path],
                capture_output=True,
                text=True
            )
            output = result.stdout or result.stderr
        finally:
            # 3. Clean up the temporary file.
            if os.path.exists(file_path):
                os.remove(file_path)

        return output

Patched code sample

import docker
import os
import io

# This code demonstrates a hypothetical fix for the described vulnerability.
# The vulnerability was a dangerous fallback to a less secure execution
# environment when a primary, secure one (Docker) was unavailable.
#
# The fix is to REMOVE the insecure fallback entirely. If the secure
# environment cannot be initialized, the tool should fail explicitly
# and refuse to execute any code, rather than silently switching to
# a vulnerable mode.

class CodeInterpreterTool:
    """
    A tool for executing Python code in a secure, isolated Docker environment.

    This version has been fixed to prevent a fallback to an insecure
    execution method. It will now raise an exception during initialization if
    a connection to Docker cannot be established, ensuring that code is never
    run outside of the intended sandboxed container.
    """
    _docker_client = None
    _image_name: str = "python:3.11-slim"

    def __init__(self):
        try:
            self._docker_client = docker.from_env()
            # The ping() method confirms that the Docker daemon is running and accessible.
            if not self._docker_client.ping():
                raise docker.errors.DockerException
            
            # Ensure the required Docker image is available locally
            try:
                self._docker_client.images.get(self._image_name)
            except docker.errors.ImageNotFound:
                print(f"Pulling required Docker image: {self._image_name}...")
                self._docker_client.images.pull(self._image_name)

        except docker.errors.DockerException as e:
            # FIX: Instead of falling back to a different sandbox, we raise an error.
            # This ensures that the tool cannot be used in an insecure state.
            raise EnvironmentError(
                "Docker is not running or is not accessible. "
                "The CodeInterpreterTool requires a running Docker daemon "
                "to execute code securely. Insecure fallbacks have been removed."
            ) from e

    def execute(self, code: str) -> str:
        """
        Executes the given Python code inside a Docker container and returns the output.

        Args:
            code: A string containing the Python code to be executed.

        Returns:
            A string containing the stdout and stderr from the code execution.
        """
        if self._docker_client is None:
            # This check is redundant if __init__ is always called, but serves as a safeguard.
            raise RuntimeError("Docker client not initialized. Cannot execute code.")

        try:
            container = self._docker_client.containers.run(
                image=self._image_name,
                command=["python", "-c", code],
                detach=False,
                remove=True, # Automatically remove the container when it exits
                stderr=True,
                stdout=True
            )
            # The output from the container is bytes, so we decode it to a string.
            return container.decode('utf-8')
        except docker.errors.ContainerError as e:
            # This error occurs if the code inside the container returns a non-zero exit code.
            return f"Error executing code in container: {e.stderr.decode('utf-8')}"
        except Exception as e:
            return f"An unexpected error occurred with Docker: {str(e)}"

# --- Demonstration of the fix ---
if __name__ == "__main__":
    print("Attempting to initialize the fixed CodeInterpreterTool...")
    
    # This block will only succeed if you have Docker installed and running.
    # If Docker is not available, it will raise the EnvironmentError from __init__.
    try:
        code_interpreter = CodeInterpreterTool()
        print("✅ Tool initialized successfully (Docker is running).")

        # Example 1: Benign code execution
        print("\n--- Running benign code ---")
        benign_code = "print('Hello from the secure container!')"
        output = code_interpreter.execute(benign_code)
        print(f"Output:\n{output}")

        # Example 2: Code that would have been dangerous in the vulnerable version.
        # This code attempts to use ctypes, which could lead to RCE on the host
        # in a weak sandbox. Here, it will be safely contained within Docker.
        # It will likely fail because system commands like 'touch' may not
        # be available or have permissions, but it won't affect the host machine.
        print("\n--- Running potentially malicious code (safely) ---")
        malicious_code_attempt = """
import ctypes
import os
try:
    # This command will only run inside the isolated container.
    # It cannot affect the host system.
    os.system('echo "This is running inside the container" > /tmp/test.txt && cat /tmp/test.txt')
except Exception as e:
    print(f"Could not execute system command: {e}")
"""
        output = code_interpreter.execute(malicious_code_attempt)
        print(f"Output:\n{output}")
        
    except EnvironmentError as e:
        print("\n--- Test Result: FAILED TO INITIALIZE ---")
        print(f"❌ ERROR: {e}")
        print("\nThis is the expected and correct behavior when Docker is not available.")
        print("The tool has safely prevented execution instead of using an insecure fallback.")

Payload

import ctypes
libc = ctypes.CDLL(None)
command = b"id"
libc.system(command)

Cite this entry

@misc{vaitp:cve20262275,
  title        = {{CrewAI CodeInterpreter's unsafe fallback to SandboxPython enables RCE.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-2275},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-2275/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::