CVE-2026-57120
PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder
PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.59, execute_code sandbox mode permits runtime assembly of blocklisted dunder names and allows str.format or str.format_map to resolve dotted fields through C-level attribute access that bypasses _safe_getattr. This exposes class, qualified-name, base-class, globals, and object-dictionary attributes to prompt-influenced code when approval is automatically granted, producing a high-impact read primitive without establishing a complete in-process execution chain. This issue is fixed in praisonaiagents 1.6.59.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
6.5
PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder
PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.59, execute_code sandbox mode permits runtime assembly of blocklisted dunder names and allows str.format or str.format_map to resolve dotted fields through C-level attribute access that bypasses _safe_getattr. This exposes class, qualified-name, base-class, globals, and object-dictionary attributes to prompt-influenced code when approval is automatically granted, producing a high-impact read primitive without establishing a complete in-process execution chain. This issue is fixed in praisonaiagents 1.6.59.
- Record
CVE-2026-57120- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-693
- Updated
- 2026-11-02
EUVDEUVD-2026-77541
6.5
PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.59, execute_code sandbox mode permits runtime assembly of blocklisted dunder names and allows str.format or str.format_map to resolve dotted fields through C-level attribute access that bypasses _safe_getattr. This exposes class, qualified-name, base-class, globals, and object-dictionary attributes to prompt-influenced code when approval is automatically granted, producing a high-impact read primitive without establishing a complete in-process execution chain. This issue is fixed in praisonaiagents 1.6.59.
- Record
EUVD-2026-77541- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-pv2j-rghr-v5r9
6.5
PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder
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:
- Runtime string assembly. The AST validator (
src/praisonai-agents/praisonaiagents/tools/python_tools.py:75) enumerates blocked dunder names againstast.Attribute.attr,ast.Call.func.id, andast.Constantstring-substring. Names assembled at runtime (e.g."_"*2 + "class" + "_"*2) appear in the AST as multiple shortast.Constantnodes, none containing a blocked substring, so the static check passes. - C-level attribute access via format-spec.
str.format/str.format_mapresolve dotted field references through CPython's internalPyObject_GetAttr(do_string_format→get_field). This C path never consults the Python-levelgetattrbinding. The sandbox's_safe_getattrwrapper (python_tools.py:221) is installed only as thegetattrname insafe_builtins, so any C-level attribute access — including format-spec field resolution — sidesteps it.format/format_mapare 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…
- Record
GHSA-pv2j-rghr-v5r9- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-693
- Updated
- 2026-11-02
NVD
6.5
PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.59, execute_code sandbox mode permits runtime assembly of blocklisted dunder names and allows str.format or str.format_map to resolve dotted fields through C-level attribute access that bypasses _safe_getattr. This exposes class, qualified-name, base-class, globals, and object-dictionary attributes to prompt-influenced code when approval is automatically granted, producing a high-impact read primitive without establishing a complete in-process execution chain. This issue is fixed in praisonaiagents 1.6.59.
- Record
CVE-2026-57120- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-693
- Updated
- 2026-11-02
OSV
6.5
PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder
PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.59, execute_code sandbox mode permits runtime assembly of blocklisted dunder names and allows str.format or str.format_map to resolve dotted fields through C-level attribute access that bypasses _safe_getattr. This exposes class, qualified-name, base-class, globals, and object-dictionary attributes to prompt-influenced code when approval is automatically granted, producing a high-impact read primitive without establishing a complete in-process execution chain. This issue is fixed in praisonaiagents 1.6.59.
- Record
CVE-2026-57120- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-693
- Updated
- 2026-11-02
OSVGHSA-pv2j-rghr-v5r9
6.5
PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder
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:
- Runtime string assembly. The AST validator (
src/praisonai-agents/praisonaiagents/tools/python_tools.py:75) enumerates blocked dunder names againstast.Attribute.attr,ast.Call.func.id, andast.Constantstring-substring. Names assembled at runtime (e.g."_"*2 + "class" + "_"*2) appear in the AST as multiple shortast.Constantnodes, none containing a blocked substring, so the static check passes. - C-level attribute access via format-spec.
str.format/str.format_mapresolve dotted field references through CPython's internalPyObject_GetAttr(do_string_format→get_field). This C path never consults the Python-levelgetattrbinding. The sandbox's_safe_getattrwrapper (python_tools.py:221) is installed only as thegetattrname insafe_builtins, so any C-level attribute access — including format-spec field resolution — sidesteps it.format/format_mapare 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…
- Record
GHSA-pv2j-rghr-v5r9- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-693
- Updated
- 2026-11-02
OSVPYSEC-2026-3533
6.5
PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder
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:
- Runtime string assembly. The AST validator (
src/praisonai-agents/praisonaiagents/tools/python_tools.py:75) enumerates blocked dunder names againstast.Attribute.attr,ast.Call.func.id, andast.Constantstring-substring. Names assembled at runtime (e.g."_"*2 + "class" + "_"*2) appear in the AST as multiple shortast.Constantnodes, none containing a blocked substring, so the static check passes. - C-level attribute access via format-spec.
str.format/str.format_mapresolve dotted field references through CPython's internalPyObject_GetAttr(do_string_format→get_field). This C path never consults the Python-levelgetattrbinding. The sandbox's_safe_getattrwrapper (python_tools.py:221) is installed only as thegetattrname insafe_builtins, so any C-level attribute access — including format-spec field resolution — sidesteps it.format/format_mapare 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…
- Record
PYSEC-2026-3533- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
praisonaiagentsPyPI |
before 1.6.59 | 1.6.59 |
praisonaiagentsPyPI |
before 1.6.59, plus listed versions 0.0.10.0.100.0.1000.0.1010.0.102 Show 45 moreShow less0.0.1030.0.1040.0.1050.0.1060.0.1070.0.1080.0.1090.0.110.0.1100.0.1110.0.1120.0.1130.0.1140.0.1150.0.1160.0.1170.0.1180.0.1190.0.120.0.1200.0.1210.0.1220.0.1230.0.1240.0.1250.0.1260.0.1270.0.1280.0.1290.0.130.0.1300.0.1310.0.1320.0.1330.0.1340.0.1350.0.1360.0.1370.0.1380.0.1390.0.140.0.1400.0.1410.0.1420.0.143 |
1.6.59 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-57120 is a medium vulnerability (CVSS 6.5) in Mervinpraison Praisonaiagents, published June 18, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.56%, the 46th percentile. Fixed in praisonaiagents 1.6.59.
References
github.com/MervinPraison/PraisonAI/security/advisories/GHSA-pv2j-rghr-v5r9
github.com/MervinPraison/PraisonAI/commit/2adfe7e8323f6deec66925cf15a885b6238895e9
github.com/MervinPraison/PraisonAI/releases/tag/v4.6.59
github.com/advisories/GHSA-pv2j-rghr-v5r9
github.com/CVEProject/cvelistV5/tree/main/cves/2026/57xxx/CVE-2026-57120.json
nvd.nist.gov/vuln/detail/CVE-2026-57120
github.com/MervinPraison/PraisonAI
pypi.org/project/praisonaiagents
Left out here: 536 versions and the rest of 3 source descriptions. The JSON record has them.