PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder
Date: 2026-06
Affected: pip/praisonaiagents
Attack vector: other
Category: vulnerability-disclosure
Severity: Medium
CWE: CWE-693
CVSS: 6.5 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)

## 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__`, …). ##


Mitigations:

Architectural layers (MAESTRO):