CVE-2026-57120
Published:July 20, 2026
Updated:August 02, 2026
Summary The "execute_code" tool's subprocess sandbox advertises a three-layer defense (AST validation, text-pattern blocklist, restricted "builtins"). In sandbox mode (the default) only two layers are active — the text-pattern blocklist is skipped — and both remaining layers are bypassed by combining two CPython semantics: 1. Runtime string assembly. The AST validator ("src/praisonai-agents/praisonaiagents/tools/python_tools.py:75") enumerates blocked dunder names against "ast.Attribute.attr", "ast.Call.func.id", and "ast.Constant" string-substring. Names assembled at runtime (e.g. """*2 + "class" + ""*2") appear in the AST as multiple short "ast.Constant" nodes, none containing a blocked substring, so the static check passes. 2. C-level attribute access via format-spec. "str.format" / "str.format_map" resolve dotted field references through CPython's internal "PyObject_GetAttr" ("do_string_format" → "get_field"). This C path never consults the Python-level "getattr" binding. The sandbox's "_safe_getattr" wrapper ("python_tools.py:221") is installed only as the "getattr" name in "safe_builtins", so any C-level attribute access — including format-spec field resolution — sidesteps it. "format"/"format_map" are also absent from "_SANDBOX_BLOCKED_CALLS" ("python_tools.py:56"). Combined, this yields an arbitrary read primitive over every blocklisted attribute ("class", "qualname", "bases", "base", function "globals", "dict", …). Affected code | File | Lines | Symbol | Role | |---|---|---|---| | "src/praisonai-agents/praisonaiagents/tools/python_tools.py" | 39–54 | "_SANDBOX_BLOCKED_ATTRS" | The blocklist the bypass renders unreachable | | "src/praisonai-agents/praisonaiagents/tools/python_tools.py" | 56–60 | "_SANDBOX_BLOCKED_CALLS" | Missing entries: "format", "format_map" | | "src/praisonai-agents/praisonaiagents/tools/python_tools.py" | 75–102 | "_validate_code_ast" | Static check, blind to runtime string assembly | | "src/praisonai-agents/praisonaiagents/tools/python_tools.py" | 221–226 | "safe_getattr" | Wraps Python-level "getattr" only; C-level access bypasses | | "src/praisonai-agents/praisonaiagents/tools/python_tools.py" | 352 | "execute_code" | Entry point; gated by "@require_approval(risk_level="critical")" | Reproducer import os os.environ["PRAISONAI_AUTO_APPROVE"] = "true" from praisonaiagents.tools.python_tools import execute_code payload = ''' und = "" * 2 # "__" assembled at runtime key1 = und + "class" + und # "class" key2 = und + "qualname" + und # "qualname" fmt_class = "{0." + key1 + "}" fmt_qual2 = "{0." + key1 + "." + key2 + "}" print("LEAK_CLASS=" + fmt_class.format(())) print("LEAK_QUAL2=" + fmt_qual2.format(())) ''' print(execute_code(payload, sandbox_mode="sandbox", timeout=15)) Observed: "stdout" = "LEAK_CLASS=<class 'tuple'>" / "LEAK_QUAL2=tuple", "success: true", no security error. Both "class" (one hop) and "class.qualname" (two hops) — all blocklisted — are read. Trust boundary The "@require_approval(risk_level="critical")" gate is bypassed when "PRAISONAI_AUTO_APPROVE" is set (verified: "require_approval" short-circuits on "is_env_auto_approve()"). That variable is set by the project's FULL_AUTO autonomy mode, the bots-CLI launchers, and the project's own issue-triage CI workflow — postures where the agent reaches "execute_code" with no human approval. The payload then arrives via any LLM-visible surface (user message, retrieved document, tool/web/MCP output) and the tool-call machinery passes it as the "code" argument. Relationship to GHSA-4mr5-g6f9-cfrh The code's own comment at "python_tools.py:46" cites GHSA-4mr5-g6f9-cfrh, which added "self" to the blocklist to stop C-builtins leaking "builtins" via "func.self". This finding does not bypass that single entry — it bypasses the entire blocklist, because format-spec attribute resolution never consults the blocklist or "_safe_getattr". ""{0.self}".format(print)" would leak "self" regardless of the blocklist. Same defense surface, different mechanism; the GHSA-4mr5 fix does not mitigate this. Scope (read primitive only) This reports the read primitive. Turning the read into in-process execution requires a callable bridge; the obvious one ("string.Formatter().get_field()" returning the live object) is not directly reachable because "import string" is blocked at the AST layer (no "ast.Import"). Other bridges may exist; a full execution chain is not claimed here. If one is found, severity rises to ~8.8 (the subprocess has no seccomp/"setrlimit"/syscall filtering). Suggested fix 3. Add "format", "format_map" to "_SANDBOX_BLOCKED_CALLS" (blocks the calls at the AST layer; cost: also blocks benign "str.format"). 4. Or replace "str" in "safe_builtins" with a subclass whose "format"/"format_map" reject dotted fields resolving to leading-underscore attributes (preserves benign formatting). 5. Or drop sandbox-mode's in-process security claim and document that real isolation requires external sandboxing (gVisor/firejail/container/microVM) — which matches what the subprocess provides today. The text-pattern blocklist present in the "direct" path ("python_tools.py:487-502") is absent from the sandbox path; even if added, the runtime-assembly trick defeats it, so (1) or (2) is required. Reporter: Kai Aizen / SnailSploit — kai@snailsploit.com — PGP on request. Coordinated disclosure; no public posting.
Affected Packages
praisonaiagents (PYTHON):
Affected version(s) >=0.0.1 <1.6.59Fix Suggestion:
Update to version 1.6.59Related Resources (5)
Do you need more information?
Contact UsCVSS v4
Base Score:
7.1
Attack Vector
NETWORK
Attack Complexity
LOW
Attack Requirements
NONE
Privileges Required
LOW
User Interaction
NONE
Vulnerable System Confidentiality
HIGH
Vulnerable System Integrity
NONE
Vulnerable System Availability
NONE
Subsequent System Confidentiality
NONE
Subsequent System Integrity
NONE
Subsequent System Availability
NONE
CVSS v3
Base Score:
6.5
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
LOW
User Interaction
NONE
Scope
UNCHANGED
Confidentiality
HIGH
Integrity
NONE
Availability
NONE
Weakness Type (CWE)
Protection Mechanism Failure