BUG-HUNT: [security] TransformExecutor sandbox exposes type builtin — allows metaclass inspection to reconstruct __import__ and escape the restricted execution environment #6587

Open
opened 2026-04-09 21:50:32 +00:00 by HAL9000 · 1 comment
Owner

Bug Report: [security] — TransformExecutor sandbox includes type builtin, enabling sandbox escape via metaclass chain

Severity Assessment

  • Impact: A malicious or compromised transform function in a Validation tool can escape the "sandboxed" execution environment to import arbitrary modules, execute shell commands, or read secrets from the host process. This completely bypasses the security controls documented for the transform sandbox.
  • Likelihood: Medium — requires ability to define/inject a Validation with a malicious transform field, which is possible if user-defined validations are accepted.
  • Priority: Critical

Location

  • File: src/cleveragents/tool/wrapping.py
  • Class/Function: TransformExecutor, _SAFE_BUILTINS
  • Lines: 51–81 (_SAFE_BUILTINS), 241–248 (exec call)

Description

TransformExecutor compiles user-supplied transform code and executes it with a restricted __builtins__ dict. However, the type built-in is included in _SAFE_BUILTINS:

_SAFE_BUILTINS: dict[str, Any] = {
    ...
    "type": type,    # <-- UNSAFE: enables metaclass chain to escape sandbox
    ...
}

With access to type, an attacker can traverse the Python object system to reconstruct __import__ using the classic CPython sandbox escape:

def transform(output):
    # Reconstruct __import__ from object.__subclasses__()
    # type.__mro__ leads to object, object.__subclasses__() contains
    # WarningMessage, _GeneratorContextManager, etc. that carry __globals__
    # which include __builtins__.__import__
    
    # Classic escape via type -> object subclass -> __globals__:
    evil = [x for x in type.__subclasses__(type.__mro__(str)[1])
            if '__import__' in getattr(x, '__globals__', {})]
    # ... reconstruct __builtins__ and call __import__('os') ...
    return {"passed": True}

More practically, type allows accessing type.__mro__, which gives object, and from object.__subclasses__() one can find a class with __globals__ containing the real __builtins__ dictionary — granting access to __import__.

The sandbox comment says "No filesystem, network, or import access is available inside the transform." This claim is false when type is available.

Evidence

The _SAFE_BUILTINS dict at lines 51–81 includes "type": type:

_SAFE_BUILTINS: dict[str, Any] = {
    "True": True,
    "False": False,
    "None": None,
    "abs": abs,
    ...
    "type": type,    # <-- the escape vector
    "zip": zip,
}

The exec call at line 245:

exec(
    self._compiled,
    sandbox,   # sandbox["__builtins__"] = dict(_SAFE_BUILTINS)
)

Expected Behavior

The transform sandbox should prevent access to Python's introspection facilities. type should be removed from _SAFE_BUILTINS. The only legitimate use of type in a transform is isinstance() type checks, which is already available via the included isinstance builtin.

Actual Behavior

The sandbox can be escaped by a sufficiently motivated attacker using type.__subclasses__() traversal to reconstruct __import__, leading to arbitrary code execution with the permissions of the CleverAgents process.

Suggested Fix

Remove type from _SAFE_BUILTINS. If type checking is needed, isinstance (already included) is sufficient:

_SAFE_BUILTINS: dict[str, Any] = {
    "True": True,
    "False": False,
    "None": None,
    "abs": abs,
    "all": all,
    "any": any,
    "bool": bool,
    "dict": dict,
    "enumerate": enumerate,
    "float": float,
    "frozenset": frozenset,
    "int": int,
    "isinstance": isinstance,
    "len": len,
    "list": list,
    "map": map,
    "max": max,
    "min": min,
    "range": range,
    "repr": repr,
    "reversed": reversed,
    "round": round,
    "set": set,
    "sorted": sorted,
    "str": str,
    "sum": sum,
    "tuple": tuple,
    # "type": type,  <-- REMOVED: enables metaclass escape
    "zip": zip,
}

Additionally, consider using ast.parse() + static analysis to reject transform code that references dunder attributes (__subclasses__, __globals__, __class__, etc.) as a defense-in-depth measure.

Category

security

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: [security] — `TransformExecutor` sandbox includes `type` builtin, enabling sandbox escape via metaclass chain ### Severity Assessment - **Impact**: A malicious or compromised `transform` function in a `Validation` tool can escape the "sandboxed" execution environment to import arbitrary modules, execute shell commands, or read secrets from the host process. This completely bypasses the security controls documented for the transform sandbox. - **Likelihood**: Medium — requires ability to define/inject a `Validation` with a malicious `transform` field, which is possible if user-defined validations are accepted. - **Priority**: Critical ### Location - **File**: `src/cleveragents/tool/wrapping.py` - **Class/Function**: `TransformExecutor`, `_SAFE_BUILTINS` - **Lines**: 51–81 (`_SAFE_BUILTINS`), 241–248 (exec call) ### Description `TransformExecutor` compiles user-supplied `transform` code and executes it with a restricted `__builtins__` dict. However, the `type` built-in is included in `_SAFE_BUILTINS`: ```python _SAFE_BUILTINS: dict[str, Any] = { ... "type": type, # <-- UNSAFE: enables metaclass chain to escape sandbox ... } ``` With access to `type`, an attacker can traverse the Python object system to reconstruct `__import__` using the classic CPython sandbox escape: ```python def transform(output): # Reconstruct __import__ from object.__subclasses__() # type.__mro__ leads to object, object.__subclasses__() contains # WarningMessage, _GeneratorContextManager, etc. that carry __globals__ # which include __builtins__.__import__ # Classic escape via type -> object subclass -> __globals__: evil = [x for x in type.__subclasses__(type.__mro__(str)[1]) if '__import__' in getattr(x, '__globals__', {})] # ... reconstruct __builtins__ and call __import__('os') ... return {"passed": True} ``` More practically, `type` allows accessing `type.__mro__`, which gives `object`, and from `object.__subclasses__()` one can find a class with `__globals__` containing the real `__builtins__` dictionary — granting access to `__import__`. The sandbox comment says "No filesystem, network, or import access is available inside the transform." This claim is false when `type` is available. ### Evidence The `_SAFE_BUILTINS` dict at lines 51–81 includes `"type": type`: ```python _SAFE_BUILTINS: dict[str, Any] = { "True": True, "False": False, "None": None, "abs": abs, ... "type": type, # <-- the escape vector "zip": zip, } ``` The exec call at line 245: ```python exec( self._compiled, sandbox, # sandbox["__builtins__"] = dict(_SAFE_BUILTINS) ) ``` ### Expected Behavior The transform sandbox should prevent access to Python's introspection facilities. `type` should be removed from `_SAFE_BUILTINS`. The only legitimate use of `type` in a transform is `isinstance()` type checks, which is already available via the included `isinstance` builtin. ### Actual Behavior The sandbox can be escaped by a sufficiently motivated attacker using `type.__subclasses__()` traversal to reconstruct `__import__`, leading to arbitrary code execution with the permissions of the CleverAgents process. ### Suggested Fix Remove `type` from `_SAFE_BUILTINS`. If type checking is needed, `isinstance` (already included) is sufficient: ```python _SAFE_BUILTINS: dict[str, Any] = { "True": True, "False": False, "None": None, "abs": abs, "all": all, "any": any, "bool": bool, "dict": dict, "enumerate": enumerate, "float": float, "frozenset": frozenset, "int": int, "isinstance": isinstance, "len": len, "list": list, "map": map, "max": max, "min": min, "range": range, "repr": repr, "reversed": reversed, "round": round, "set": set, "sorted": sorted, "str": str, "sum": sum, "tuple": tuple, # "type": type, <-- REMOVED: enables metaclass escape "zip": zip, } ``` Additionally, consider using `ast.parse()` + static analysis to reject `transform` code that references dunder attributes (`__subclasses__`, `__globals__`, `__class__`, etc.) as a defense-in-depth measure. ### Category `security` ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: `@tdd_issue`, `@tdd_issue_<this-issue-number>`, and `@tdd_expected_fail` to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-09 22:13:19 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Unverified
  • Priority: Critical — SANDBOX ESCAPE VULNERABILITY: TransformExecutor sandbox exposes the type builtin. This is a well-known Python sandbox escape vector — type can be used to construct arbitrary classes and access __subclasses__() to reach os, subprocess, or other dangerous modules.
  • Milestone: v3.2.0 — Sandbox escape vulnerabilities must be fixed in the earliest milestone
  • MoSCoW: Must Have — Sandbox escapes are critical security issues

Security Impact: With access to type, an attacker can use type.__subclasses__() chains to access os.system(), subprocess.Popen(), or file I/O, completely bypassing the sandbox. The type builtin must be removed from the sandbox's allowed builtins.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner

Issue triaged by project owner: - **State**: Unverified - **Priority**: Critical — **SANDBOX ESCAPE VULNERABILITY**: `TransformExecutor` sandbox exposes the `type` builtin. This is a well-known Python sandbox escape vector — `type` can be used to construct arbitrary classes and access `__subclasses__()` to reach `os`, `subprocess`, or other dangerous modules. - **Milestone**: v3.2.0 — Sandbox escape vulnerabilities must be fixed in the earliest milestone - **MoSCoW**: Must Have — Sandbox escapes are critical security issues **Security Impact**: With access to `type`, an attacker can use `type.__subclasses__()` chains to access `os.system()`, `subprocess.Popen()`, or file I/O, completely bypassing the sandbox. The `type` builtin must be removed from the sandbox's allowed builtins. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#6587
No description provided.