GHSA-pv2j-rghr-v5r9: praisonaiagents: sandbox escape via format-spec read
GHSA-pv2j-rghr-v5r9 MEDIUMPraisonAI Agents' code-execution sandbox is bypassed through two combined CPython mechanics: runtime string assembly fools the AST validator into missing blocked dunder names, while C-level attribute resolution via str.format/str.format_map sidesteps the Python-level _safe_getattr guard entirely — yielding an arbitrary read primitive over __class__, __globals__, __dict__, and every other blocked attribute. The critical-risk approval gate (@require_approval) is automatically disabled whenever PRAISONAI_AUTO_APPROVE is set, a condition triggered by FULL_AUTO mode, CI workflows, and bot launchers, meaning any LLM-visible surface — user input, RAG-retrieved document, MCP tool output — can deliver and execute the payload with zero human intervention. With a callable bridge, the current read primitive escalates to in-process code execution inside an unfiltered subprocess that has no seccomp, no setrlimit, and no syscall filtering; 11 downstream dependents and 42 prior CVEs in the same package suggest a pattern of weak sandbox assumptions that compounds this risk. Organizations using praisonaiagents in autonomous pipelines must patch to 1.6.59 immediately and audit every deployment for PRAISONAI_AUTO_APPROVE usage.
What is the risk?
The CVSS 6.5 Medium rating materially understates operational risk in agentic deployments. The auto-approve bypass converts a chained-exploit sandbox escape into a single-step attack whenever FULL_AUTO mode is active — a common posture for CI-integrated or fully-automated agent pipelines. The read primitive is confirmed with a published PoC; an execution primitive is plausible given the subprocess lacks syscall filtering. The attack requires only Low privileges per the CVSS vector and no user interaction, and the format-spec vector is not blocked in sandbox mode at all. With 42 prior CVEs in the same package, this codebase has a demonstrated history of security boundary failures. Effective risk in production agentic systems should be assessed as HIGH.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| PraisonAI Agents | pip | < 1.6.59 | 1.6.59 |
Do you use PraisonAI Agents? You're affected.
How severe is it?
What is the attack surface?
What should I do?
6 steps-
Patch immediately: upgrade praisonaiagents to >= 1.6.59 which addresses the format/format_map bypass.
-
Remove PRAISONAI_AUTO_APPROVE=true from all production and CI environments — treat it as a security-critical configuration setting, not a convenience flag; document all places it is set.
-
Replace the in-process sandbox with genuine OS-level isolation (gVisor, firejail, container, or microVM) for any deployment executing untrusted code — the current blocklist-based design cannot be fully secured by additions alone.
-
Until patched, add 'format' and 'format_map' to _SANDBOX_BLOCKED_CALLS in python_tools.py:56-60 as a partial workaround.
-
Audit RAG sources, MCP tool outputs, web-scraped content, and any user-controlled text flowing into execute_code for runtime string assembly patterns such as '_'*2 + 'class' + '_'*2.
-
Review all CI pipelines that set PRAISONAI_AUTO_APPROVE and consider introducing a human-approval step before code execution in production contexts.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is GHSA-pv2j-rghr-v5r9?
PraisonAI Agents' code-execution sandbox is bypassed through two combined CPython mechanics: runtime string assembly fools the AST validator into missing blocked dunder names, while C-level attribute resolution via str.format/str.format_map sidesteps the Python-level _safe_getattr guard entirely — yielding an arbitrary read primitive over __class__, __globals__, __dict__, and every other blocked attribute. The critical-risk approval gate (@require_approval) is automatically disabled whenever PRAISONAI_AUTO_APPROVE is set, a condition triggered by FULL_AUTO mode, CI workflows, and bot launchers, meaning any LLM-visible surface — user input, RAG-retrieved document, MCP tool output — can deliver and execute the payload with zero human intervention. With a callable bridge, the current read primitive escalates to in-process code execution inside an unfiltered subprocess that has no seccomp, no setrlimit, and no syscall filtering; 11 downstream dependents and 42 prior CVEs in the same package suggest a pattern of weak sandbox assumptions that compounds this risk. Organizations using praisonaiagents in autonomous pipelines must patch to 1.6.59 immediately and audit every deployment for PRAISONAI_AUTO_APPROVE usage.
Is GHSA-pv2j-rghr-v5r9 actively exploited?
No confirmed active exploitation of GHSA-pv2j-rghr-v5r9 has been reported, but organizations should still patch proactively.
How to fix GHSA-pv2j-rghr-v5r9?
1. Patch immediately: upgrade praisonaiagents to >= 1.6.59 which addresses the format/format_map bypass. 2. Remove PRAISONAI_AUTO_APPROVE=true from all production and CI environments — treat it as a security-critical configuration setting, not a convenience flag; document all places it is set. 3. Replace the in-process sandbox with genuine OS-level isolation (gVisor, firejail, container, or microVM) for any deployment executing untrusted code — the current blocklist-based design cannot be fully secured by additions alone. 4. Until patched, add 'format' and 'format_map' to _SANDBOX_BLOCKED_CALLS in python_tools.py:56-60 as a partial workaround. 5. Audit RAG sources, MCP tool outputs, web-scraped content, and any user-controlled text flowing into execute_code for runtime string assembly patterns such as '_'*2 + 'class' + '_'*2. 6. Review all CI pipelines that set PRAISONAI_AUTO_APPROVE and consider introducing a human-approval step before code execution in production contexts.
What systems are affected by GHSA-pv2j-rghr-v5r9?
This vulnerability affects the following AI/ML architecture patterns: Agent frameworks, Autonomous AI pipelines, Code execution sandboxes, LLM-integrated CI/CD, RAG pipelines.
What is the CVSS score for GHSA-pv2j-rghr-v5r9?
GHSA-pv2j-rghr-v5r9 has a CVSS v3.1 base score of 6.5 (MEDIUM).
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0050 Command and Scripting Interpreter AML.T0051.001 Indirect AML.T0053 AI Agent Tool Invocation AML.T0080 AI Agent Context Poisoning AML.T0105 Escape to Host AML.T0107 Exploitation for Defense Evasion Compliance Controls Affected
What are the technical details?
Original Advisory
## 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 ```python 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 1. Add `format`, `format_map` to `_SANDBOX_BLOCKED_CALLS` (blocks the calls at the AST layer; cost: also blocks benign `str.format`). 2. Or replace `str` in `safe_builtins` with a subclass whose `format`/`format_map` reject dotted fields resolving to leading-underscore attributes (preserves benign formatting). 3. 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.
Exploitation Scenario
An attacker embeds a Python sandbox-escape payload inside a document indexed in a RAG knowledge base, a web page scraped during autonomous research, or a poisoned MCP tool response. When the LLM processes this content and calls execute_code — automatically in FULL_AUTO mode or CI environments where PRAISONAI_AUTO_APPROVE is set — the payload assembles blocked dunder names at runtime (e.g., '_'*2+'class'+'_'*2) to pass AST validation undetected, then invokes str.format_map with a dotted field reference like {0.__class__.__globals__} to resolve attributes through CPython's C-level path, entirely bypassing _safe_getattr. The attacker reads imported modules and environment variables from __globals__, extracting API keys, database credentials, or authentication tokens loaded into the agent process. With a callable bridge reachable from the object graph — for instance via a module reference discovered in __globals__ — the attacker escalates from read primitive to arbitrary command execution within the unfiltered subprocess, enabling data exfiltration, lateral movement, or destruction of downstream systems.
Weaknesses (CWE)
CWE-693 — Protection Mechanism Failure: The product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N References
Timeline
Related Vulnerabilities
CVE-2026-34938 10.0 praisonaiagents: sandbox bypass enables full host RCE
Same package: praisonaiagents CVE-2026-39888 10.0 praisonaiagents: sandbox escape enables host RCE
Same package: praisonaiagents CVE-2026-47392 9.9 praisonaiagents: RCE via Python sandbox bypass
Same package: praisonaiagents GHSA-vc46-vw85-3wvm 9.8 PraisonAI: RCE via malicious workflow YAML execution
Same package: praisonaiagents CVE-2026-47391 9.8 PraisonAI: Unauth RCE via A2A eval injection
Same package: praisonaiagents