Medium

PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder

  • Disclosed: 18.06.2026
  • Updated: 11.10.2026 14:18

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.

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
Python (PyPI) praisonaiagents < 1.6.59 1.6.59

References

CVSS score 6.5/10
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weakness (CWE)
CWE-693
Platform
Python (PyPI)
Package
praisonaiagents
Disclosed
18.06.2026
Updated
11.10.2026 14:18

Sources and licenses

OSV / GitHub Advisory Database - PYSEC-2026-3533 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-pv2j-rghr-v5r9 - CC BY 4.0

Vulnerability history: praisonaiagents

All published vulnerabilities of this package (Python (PyPI)), newest first. Shows whether this is a one-off slip or a package that needs watching.

All (50) in the archive
Total entries
50
First → latest
04.2026 10.2026
Without a fix
0
Exploited in attacks
0

Severity breakdown

  • Critical 10
  • High 27
  • Medium 13

Disclosures per year

  1. 2026: 50
Severity:
  1. PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder this entry
    Medium · 6.5 CVE-2026-57120 < 1.6.59 1.6.59
  2. High · 7.5 CVE-2026-55525 < 1.6.58 1.6.58
  3. High · 7.5 CVE-2026-57129 < 1.6.59 1.6.59
  4. Critical · 9.8 CVE-2026-57125 < 1.6.59 1.6.59
Show more (17) Showing 21 of 38

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.