GHSA-4mr5-g6f9-cfrh: PraisonAI vulnerable to sandbox escape via `print.__self__` builtins module leak in `execute_code` (subprocess mode)
Summary`execute_code()` in `praisonaiagents/tools/python_tools.py` (v1.6.37, subprocess sandbox mode) can be fully bypassed using `print.__self__` to retrieve the real Python `builtins` module, from which `__import__` can be extracted via `vars()` and runtime string construction. This achieves arbitrary OS command execution on the host, completely defeating the sandbox.This is a **novel bypass** that survives all patches for CVE-2026-39888 (frame traversal), CVE-2026-34938 (str subclass), and CVE-2026-40158 (`type.__getattribute__` trampoline).Severity**CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — 9.9 Critical**Root CauseThree independent gaps in the AST-based security validation:Gap 1: `__self__` missing from `_blocked_attrs`In CPython, all built-in functions (C-level functions) have a `__self__` attribute that returns the module they belong to. The built-in functions in `safe_builtins` (`print`, `len`, `range`, etc.) are the *real* CPython built-in functions, so `print.__self__` returns `<module 'builtins' (built-in)>`.The `_blocked_attrs` frozenset (line 52) does NOT include `__self__`. The AST check at line 74 only blocks attributes that are IN this set, so `print.__self__` passes.Gap 2: `vars` not blocked as callable or attribute`builtins.vars(obj)` returns `obj.__dict__`. The function name `vars` is not in the AST `Call` blocklist (line 83: only blocks `exec`, `eval`, `compile`, `__import__`, `open`, `input`, `breakpoint`, `setattr`, `delattr`, `dir`). And `vars` is not in `_blocked_attrs` for attribute access.So `b.vars(b)` (where `b` is the builtins module) returns `builtins.__dict__` — a dict containing ALL built-in functions including `__import__`, `exec`, `eval`, `open`, etc.Gap 3: AST `Call` check only catches `ast.Name` nodesThe dangerous-call check (line 82-88) only fires when `isinstance(func, ast.Name)` — i.e., bare-name calls like `exec(...)`. It does NOT catch:Attribute calls: `b.exec(...)` — func is `ast.Attribute`Subscript calls: `d["exec"](...)` — func is `ast.Subscript`Gap 4: Runtime string construction bypasses string constant checkThe string constant check (line 92-98) catches literals like `"__import__"`, but NOT runtime concatenation like `"_" + "_" + "import" + "_" + "_"`. The AST sees 5 separate `Constant` nodes (`"_"`, `"_"`, `"import"`, `"_"`, `"_"`), none of which contain any blocked attr as a substring.Proof of ConceptStep-by-step bypass analysis:| Line | AST node | Check | Result |
|---|---|---|---|
| `print.__self__` | `Attribute(attr='__self__')` | `__self__` in `_blocked_attrs`? | **NO** → passes |
| `b.vars` | `Attribute(attr='vars')` | `vars` in `_blocked_attrs`? | **NO** → passes |
| `b.vars(b)` | `Call(func=Attribute)` | `isinstance(func, ast.Name)`? | **NO** → passes |
| `"_"`, `"import"` | `Constant(value=str)` | Contains blocked attr? | **NO** → passes |
| `d[key]` | `Subscript` | Not checked | passes |
| `imp("os")` | `Call(func=Name('imp'))` | `imp` in blocked calls? | **NO** → passes |**Result: Full sandbox escape → arbitrary command execution**ImpactAn attacker who can influence agent input (via prompt injection, malicious documents, or direct code submission) can:Execute arbitrary commands on the host systemRead/write any file accessible to the processExfiltrate environment variables, API keys, and credentialsPivot to internal networksInstall persistent backdoorsAffected**Package**: `praisonaiagents` (PyPI)**Affected versions**: All versions through 1.6.37 (latest)**Component**: `praisonaiagents/tools/python_tools.py`, `_execute_code_sandboxed()` function**Default configuration affected**: Yes (`sandbox_mode="sandbox"` is the default)RemediationImmediate fixAdd `__self__` to `_blocked_attrs`:Additional hardeningBlock `vars` in the callable blocklistExtend the `ast.Call` check to also catch `ast.Attribute` and `ast.Subscript` function nodesAdd AST check for `BinOp` string concatenation that could construct blocked attr namesFundamental recommendationDenylist-based Python sandboxes are fundamentally insecure. Each patch introduces a new bypass opportunity. Consider:Using `isolated-vm` (Node.js) or WebAssembly-based isolationUsing OS-level sandboxing (seccomp, namespaces, gVisor)Removing in-process code execution entirely in favor of containerized execution
CVSS v3.1: 9.9