Search

Find a vulnerability

Search criteria

    Related vulnerabilities

    GHSA-PV2J-RGHR-V5R9

    Vulnerability from github – Published: 2026-06-18 13:55 – Updated: 2026-07-20 21:24
    VLAI
    Summary
    PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder
    Details

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

    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 format-spec attribute resolution never consults the blocklist or _safe_getattr. "{0.__self__}".format(print) would leak __self__ regardless of the blocklist. Same defense surface, different mechanism; the GHSA-4mr5 fix does not mitigate this.

    Scope (read primitive only)

    This reports the read primitive. Turning the read into in-process execution requires a callable bridge; the obvious one (string.Formatter().get_field() returning the live object) is not directly reachable because import string is blocked at the AST layer (no ast.Import). Other bridges may exist; a full execution chain is not claimed here. If one is found, severity rises to ~8.8 (the subprocess has no seccomp/setrlimit/syscall filtering).

    Suggested fix

    1. Add format, format_map to _SANDBOX_BLOCKED_CALLS (blocks the calls at the AST layer; cost: also blocks benign str.format).
    2. Or replace str in safe_builtins with a subclass whose format/format_map reject dotted fields resolving to leading-underscore attributes (preserves benign formatting).
    3. Or drop sandbox-mode's in-process security claim and document that real isolation requires external sandboxing (gVisor/firejail/container/microVM) — which matches what the subprocess provides today.

    The text-pattern blocklist present in the direct path (python_tools.py:487-502) is absent from the sandbox path; even if added, the runtime-assembly trick defeats it, so (1) or (2) is required.

    Reporter: Kai Aizen / SnailSploit — kai@snailsploit.com — PGP on request. Coordinated disclosure; no public posting.

    Show details on source website

    {
      "affected": [
        {
          "package": {
            "ecosystem": "PyPI",
            "name": "praisonaiagents"
          },
          "ranges": [
            {
              "events": [
                {
                  "introduced": "0"
                },
                {
                  "fixed": "1.6.59"
                }
              ],
              "type": "ECOSYSTEM"
            }
          ]
        }
      ],
      "aliases": [
        "CVE-2026-57120"
      ],
      "database_specific": {
        "cwe_ids": [
          "CWE-693"
        ],
        "github_reviewed": true,
        "github_reviewed_at": "2026-06-18T13:55:29Z",
        "nvd_published_at": null,
        "severity": "MODERATE"
      },
      "details": "## Summary\n\nThe `execute_code` tool\u0027s subprocess sandbox advertises a three-layer defense (AST validation, text-pattern blocklist, restricted `__builtins__`). In **sandbox mode** (the default) only two layers are active \u2014 the text-pattern blocklist is skipped \u2014 and both remaining layers are bypassed by combining two CPython semantics:\n\n1. **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.\n2. **C-level attribute access via format-spec.** `str.format` / `str.format_map` resolve dotted field references through CPython\u0027s internal `PyObject_GetAttr` (`do_string_format` \u2192 `get_field`). This C path never consults the Python-level `getattr` binding. The sandbox\u0027s `_safe_getattr` wrapper (`python_tools.py:221`) is installed only as the `getattr` name in `safe_builtins`, so any C-level attribute access \u2014 including format-spec field resolution \u2014 sidesteps it. `format`/`format_map` are also absent from `_SANDBOX_BLOCKED_CALLS` (`python_tools.py:56`).\n\nCombined, this yields an arbitrary read primitive over every blocklisted attribute (`__class__`, `__qualname__`, `__bases__`, `__base__`, function `__globals__`, `__dict__`, \u2026).\n\n## Affected code\n\n| File | Lines | Symbol | Role |\n|---|---|---|---|\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 39\u201354 | `_SANDBOX_BLOCKED_ATTRS` | The blocklist the bypass renders unreachable |\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 56\u201360 | `_SANDBOX_BLOCKED_CALLS` | Missing entries: `format`, `format_map` |\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 75\u2013102 | `_validate_code_ast` | Static check, blind to runtime string assembly |\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 221\u2013226 | `_safe_getattr` | Wraps Python-level `getattr` only; C-level access bypasses |\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 352 | `execute_code` | Entry point; gated by `@require_approval(risk_level=\"critical\")` |\n\n## Reproducer\n\n```python\nimport os\nos.environ[\"PRAISONAI_AUTO_APPROVE\"] = \"true\"\nfrom praisonaiagents.tools.python_tools import execute_code\n\npayload = \u0027\u0027\u0027\nund = \"_\" * 2                         # \"__\" assembled at runtime\nkey1 = und + \"class\" + und            # \"__class__\"\nkey2 = und + \"qualname\" + und         # \"__qualname__\"\nfmt_class = \"{0.\" + key1 + \"}\"\nfmt_qual2 = \"{0.\" + key1 + \".\" + key2 + \"}\"\nprint(\"LEAK_CLASS=\" + fmt_class.format(()))\nprint(\"LEAK_QUAL2=\" + fmt_qual2.format(()))\n\u0027\u0027\u0027\nprint(execute_code(payload, sandbox_mode=\"sandbox\", timeout=15))\n```\n\nObserved: `stdout` = `LEAK_CLASS=\u003cclass \u0027tuple\u0027\u003e` / `LEAK_QUAL2=tuple`, `success: true`, no security error. Both `__class__` (one hop) and `__class__.__qualname__` (two hops) \u2014 all blocklisted \u2014 are read.\n\n## Trust boundary\n\nThe `@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\u0027s FULL_AUTO autonomy mode, the bots-CLI launchers, and the project\u0027s own issue-triage CI workflow \u2014 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.\n\n## Relationship to GHSA-4mr5-g6f9-cfrh\n\nThe code\u0027s 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 \u2014 it bypasses the **entire** blocklist, because format-spec attribute resolution never consults the blocklist or `_safe_getattr`. `\"{0.__self__}\".format(print)` would leak `__self__` regardless of the blocklist. Same defense surface, different mechanism; the GHSA-4mr5 fix does not mitigate this.\n\n## Scope (read primitive only)\n\nThis reports the **read primitive**. Turning the read into in-process execution requires a callable bridge; the obvious one (`string.Formatter().get_field()` returning the live object) is not directly reachable because `import string` is blocked at the AST layer (no `ast.Import`). Other bridges may exist; a full execution chain is **not** claimed here. If one is found, severity rises to ~8.8 (the subprocess has no seccomp/`setrlimit`/syscall filtering).\n\n## Suggested fix\n\n1. Add `format`, `format_map` to `_SANDBOX_BLOCKED_CALLS` (blocks the calls at the AST layer; cost: also blocks benign `str.format`).\n2. Or replace `str` in `safe_builtins` with a subclass whose `format`/`format_map` reject dotted fields resolving to leading-underscore attributes (preserves benign formatting).\n3. Or drop sandbox-mode\u0027s in-process security claim and document that real isolation requires external sandboxing (gVisor/firejail/container/microVM) \u2014 which matches what the subprocess provides today.\n\nThe text-pattern blocklist present in the `direct` path (`python_tools.py:487-502`) is absent from the sandbox path; even if added, the runtime-assembly trick defeats it, so (1) or (2) is required.\n\nReporter: Kai Aizen / SnailSploit \u2014 kai@snailsploit.com \u2014 PGP on request. Coordinated disclosure; no public posting.",
      "id": "GHSA-pv2j-rghr-v5r9",
      "modified": "2026-07-20T21:24:25Z",
      "published": "2026-06-18T13:55:29Z",
      "references": [
        {
          "type": "WEB",
          "url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-pv2j-rghr-v5r9"
        },
        {
          "type": "PACKAGE",
          "url": "https://github.com/MervinPraison/PraisonAI"
        }
      ],
      "schema_version": "1.4.0",
      "severity": [
        {
          "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
          "type": "CVSS_V3"
        }
      ],
      "summary": "PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder"
    }

    PYSEC-2026-3533

    Vulnerability from pysec - Published: 2026-07-23 11:41 - Updated: 2026-07-23 14:32
    VLAI
    Details

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

    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 format-spec attribute resolution never consults the blocklist or _safe_getattr. "{0.__self__}".format(print) would leak __self__ regardless of the blocklist. Same defense surface, different mechanism; the GHSA-4mr5 fix does not mitigate this.

    Scope (read primitive only)

    This reports the read primitive. Turning the read into in-process execution requires a callable bridge; the obvious one (string.Formatter().get_field() returning the live object) is not directly reachable because import string is blocked at the AST layer (no ast.Import). Other bridges may exist; a full execution chain is not claimed here. If one is found, severity rises to ~8.8 (the subprocess has no seccomp/setrlimit/syscall filtering).

    Suggested fix

    1. Add format, format_map to _SANDBOX_BLOCKED_CALLS (blocks the calls at the AST layer; cost: also blocks benign str.format).
    2. Or replace str in safe_builtins with a subclass whose format/format_map reject dotted fields resolving to leading-underscore attributes (preserves benign formatting).
    3. Or drop sandbox-mode's in-process security claim and document that real isolation requires external sandboxing (gVisor/firejail/container/microVM) — which matches what the subprocess provides today.

    The text-pattern blocklist present in the direct path (python_tools.py:487-502) is absent from the sandbox path; even if added, the runtime-assembly trick defeats it, so (1) or (2) is required.

    Reporter: Kai Aizen / SnailSploit — kai@snailsploit.com — PGP on request. Coordinated disclosure; no public posting.

    Impacted products
    Name purl
    praisonaiagents pkg:pypi/praisonaiagents

    {
      "affected": [
        {
          "package": {
            "ecosystem": "PyPI",
            "name": "praisonaiagents",
            "purl": "pkg:pypi/praisonaiagents"
          },
          "ranges": [
            {
              "events": [
                {
                  "introduced": "0"
                },
                {
                  "fixed": "1.6.59"
                }
              ],
              "type": "ECOSYSTEM"
            }
          ],
          "versions": [
            "0.0.1",
            "0.0.10",
            "0.0.100",
            "0.0.101",
            "0.0.102",
            "0.0.103",
            "0.0.104",
            "0.0.105",
            "0.0.106",
            "0.0.107",
            "0.0.108",
            "0.0.109",
            "0.0.11",
            "0.0.110",
            "0.0.111",
            "0.0.112",
            "0.0.113",
            "0.0.114",
            "0.0.115",
            "0.0.116",
            "0.0.117",
            "0.0.118",
            "0.0.119",
            "0.0.12",
            "0.0.120",
            "0.0.121",
            "0.0.122",
            "0.0.123",
            "0.0.124",
            "0.0.125",
            "0.0.126",
            "0.0.127",
            "0.0.128",
            "0.0.129",
            "0.0.13",
            "0.0.130",
            "0.0.131",
            "0.0.132",
            "0.0.133",
            "0.0.134",
            "0.0.135",
            "0.0.136",
            "0.0.137",
            "0.0.138",
            "0.0.139",
            "0.0.14",
            "0.0.140",
            "0.0.141",
            "0.0.142",
            "0.0.143",
            "0.0.144",
            "0.0.145",
            "0.0.146",
            "0.0.147",
            "0.0.148",
            "0.0.149",
            "0.0.15",
            "0.0.150",
            "0.0.151",
            "0.0.152",
            "0.0.153",
            "0.0.154",
            "0.0.155",
            "0.0.156",
            "0.0.157",
            "0.0.158",
            "0.0.159",
            "0.0.16",
            "0.0.160",
            "0.0.161",
            "0.0.162",
            "0.0.163",
            "0.0.164",
            "0.0.165",
            "0.0.166",
            "0.0.167",
            "0.0.168",
            "0.0.169",
            "0.0.17",
            "0.0.170",
            "0.0.171",
            "0.0.172",
            "0.0.173",
            "0.0.174",
            "0.0.175",
            "0.0.176",
            "0.0.177",
            "0.0.178",
            "0.0.179",
            "0.0.18",
            "0.0.180",
            "0.0.181",
            "0.0.182",
            "0.0.183",
            "0.0.184",
            "0.0.185",
            "0.0.187",
            "0.0.188",
            "0.0.189",
            "0.0.19",
            "0.0.190",
            "0.0.191",
            "0.0.192",
            "0.0.193",
            "0.0.194",
            "0.0.195",
            "0.0.196",
            "0.0.197",
            "0.0.198",
            "0.0.199",
            "0.0.2",
            "0.0.20",
            "0.0.21",
            "0.0.22",
            "0.0.23",
            "0.0.24",
            "0.0.25",
            "0.0.26",
            "0.0.27",
            "0.0.28",
            "0.0.29",
            "0.0.3",
            "0.0.30",
            "0.0.31",
            "0.0.32",
            "0.0.33",
            "0.0.34",
            "0.0.35",
            "0.0.36",
            "0.0.37",
            "0.0.38",
            "0.0.39",
            "0.0.4",
            "0.0.40",
            "0.0.41",
            "0.0.42",
            "0.0.43",
            "0.0.44",
            "0.0.45",
            "0.0.46",
            "0.0.47",
            "0.0.48",
            "0.0.49",
            "0.0.5",
            "0.0.50",
            "0.0.51",
            "0.0.52",
            "0.0.53",
            "0.0.54",
            "0.0.56",
            "0.0.57",
            "0.0.58",
            "0.0.59",
            "0.0.6",
            "0.0.60",
            "0.0.61",
            "0.0.62",
            "0.0.63",
            "0.0.64",
            "0.0.65",
            "0.0.66",
            "0.0.67",
            "0.0.68",
            "0.0.69",
            "0.0.7",
            "0.0.70",
            "0.0.71",
            "0.0.72",
            "0.0.73",
            "0.0.74",
            "0.0.75",
            "0.0.76",
            "0.0.77",
            "0.0.78",
            "0.0.79",
            "0.0.8",
            "0.0.80",
            "0.0.81",
            "0.0.82",
            "0.0.83",
            "0.0.84",
            "0.0.85",
            "0.0.86",
            "0.0.87",
            "0.0.88",
            "0.0.89",
            "0.0.9",
            "0.0.90",
            "0.0.91",
            "0.0.92",
            "0.0.93",
            "0.0.94",
            "0.0.95",
            "0.0.96",
            "0.0.97",
            "0.0.98",
            "0.0.99",
            "0.1.0",
            "0.1.1",
            "0.1.10",
            "0.1.11",
            "0.1.12",
            "0.1.13",
            "0.1.14",
            "0.1.15",
            "0.1.16",
            "0.1.17",
            "0.1.18",
            "0.1.19",
            "0.1.2",
            "0.1.20",
            "0.1.21",
            "0.1.22",
            "0.1.23",
            "0.1.24",
            "0.1.25",
            "0.1.26",
            "0.1.27",
            "0.1.3",
            "0.1.4",
            "0.1.5",
            "0.1.6",
            "0.1.7",
            "0.1.8",
            "0.1.9",
            "0.10.0",
            "0.10.1",
            "0.10.10",
            "0.10.2",
            "0.10.3",
            "0.10.4",
            "0.10.5",
            "0.10.6",
            "0.10.7",
            "0.10.8",
            "0.10.9",
            "0.11.0",
            "0.11.1",
            "0.11.10",
            "0.11.11",
            "0.11.12",
            "0.11.13",
            "0.11.14",
            "0.11.15",
            "0.11.16",
            "0.11.17",
            "0.11.18",
            "0.11.19",
            "0.11.2",
            "0.11.20",
            "0.11.21",
            "0.11.22",
            "0.11.23",
            "0.11.24",
            "0.11.25",
            "0.11.27",
            "0.11.28",
            "0.11.29",
            "0.11.3",
            "0.11.30",
            "0.11.31",
            "0.11.4",
            "0.11.5",
            "0.11.6",
            "0.11.7",
            "0.11.8",
            "0.11.9",
            "0.12.0",
            "0.12.1",
            "0.12.10",
            "0.12.11",
            "0.12.12",
            "0.12.13",
            "0.12.14",
            "0.12.15",
            "0.12.16",
            "0.12.17",
            "0.12.18",
            "0.12.19",
            "0.12.2",
            "0.12.20",
            "0.12.21",
            "0.12.3",
            "0.12.4",
            "0.12.5",
            "0.12.6",
            "0.12.7",
            "0.12.8",
            "0.12.9",
            "0.13.0",
            "0.13.1",
            "0.13.10",
            "0.13.11",
            "0.13.12",
            "0.13.13",
            "0.13.14",
            "0.13.15",
            "0.13.16",
            "0.13.17",
            "0.13.18",
            "0.13.19",
            "0.13.2",
            "0.13.20",
            "0.13.21",
            "0.13.22",
            "0.13.23",
            "0.13.3",
            "0.13.4",
            "0.13.5",
            "0.13.6",
            "0.13.7",
            "0.13.8",
            "0.13.9",
            "0.14.0",
            "0.14.1",
            "0.14.10",
            "0.14.11",
            "0.14.12",
            "0.14.14",
            "0.14.15",
            "0.14.16",
            "0.14.2",
            "0.14.3",
            "0.14.4",
            "0.14.5",
            "0.14.6",
            "0.14.7",
            "0.14.8",
            "0.14.9",
            "0.15.0",
            "0.15.1",
            "0.15.2",
            "0.15.3",
            "0.2.0",
            "0.2.1",
            "0.2.2",
            "0.3.0",
            "0.3.1",
            "0.3.2",
            "0.3.3",
            "0.3.4",
            "0.4.0",
            "0.4.1",
            "0.5.0",
            "0.5.1",
            "0.5.2",
            "0.5.3",
            "0.6.0",
            "0.6.1",
            "0.6.2",
            "0.6.3",
            "0.6.4",
            "0.6.5",
            "0.6.6",
            "0.6.7",
            "0.6.8",
            "0.7.0",
            "0.7.1",
            "0.8.0",
            "0.8.1",
            "0.9.0",
            "0.9.1",
            "1.0.0",
            "1.1.0",
            "1.2.0",
            "1.2.1",
            "1.2.2",
            "1.2.3",
            "1.2.4",
            "1.3.0",
            "1.3.1",
            "1.4.0",
            "1.4.1",
            "1.4.2",
            "1.4.3",
            "1.4.4",
            "1.4.5",
            "1.4.6",
            "1.4.7",
            "1.4.8",
            "1.5.0",
            "1.5.1",
            "1.5.10",
            "1.5.100",
            "1.5.101",
            "1.5.102",
            "1.5.103",
            "1.5.104",
            "1.5.105",
            "1.5.106",
            "1.5.107",
            "1.5.108",
            "1.5.109",
            "1.5.11",
            "1.5.110",
            "1.5.111",
            "1.5.112",
            "1.5.113",
            "1.5.114",
            "1.5.115",
            "1.5.116",
            "1.5.117",
            "1.5.118",
            "1.5.119",
            "1.5.12",
            "1.5.120",
            "1.5.121",
            "1.5.122",
            "1.5.123",
            "1.5.124",
            "1.5.125",
            "1.5.126",
            "1.5.127",
            "1.5.128",
            "1.5.129",
            "1.5.13",
            "1.5.130",
            "1.5.131",
            "1.5.132",
            "1.5.133",
            "1.5.134",
            "1.5.135",
            "1.5.136",
            "1.5.137",
            "1.5.138",
            "1.5.139",
            "1.5.14",
            "1.5.140",
            "1.5.141",
            "1.5.142",
            "1.5.143",
            "1.5.144",
            "1.5.145",
            "1.5.146",
            "1.5.147",
            "1.5.148",
            "1.5.149",
            "1.5.15",
            "1.5.16",
            "1.5.17",
            "1.5.18",
            "1.5.19",
            "1.5.2",
            "1.5.20",
            "1.5.21",
            "1.5.22",
            "1.5.23",
            "1.5.24",
            "1.5.25",
            "1.5.26",
            "1.5.27",
            "1.5.28",
            "1.5.29",
            "1.5.3",
            "1.5.30",
            "1.5.31",
            "1.5.32",
            "1.5.33",
            "1.5.34",
            "1.5.35",
            "1.5.36",
            "1.5.37",
            "1.5.38",
            "1.5.39",
            "1.5.40",
            "1.5.41",
            "1.5.42",
            "1.5.43",
            "1.5.44",
            "1.5.45",
            "1.5.46",
            "1.5.47",
            "1.5.48",
            "1.5.49",
            "1.5.5",
            "1.5.50",
            "1.5.51",
            "1.5.52",
            "1.5.53",
            "1.5.54",
            "1.5.55",
            "1.5.56",
            "1.5.57",
            "1.5.58",
            "1.5.59",
            "1.5.6",
            "1.5.60",
            "1.5.61",
            "1.5.62",
            "1.5.63",
            "1.5.64",
            "1.5.65",
            "1.5.66",
            "1.5.67",
            "1.5.68",
            "1.5.69",
            "1.5.7",
            "1.5.70",
            "1.5.71",
            "1.5.72",
            "1.5.73",
            "1.5.74",
            "1.5.75",
            "1.5.76",
            "1.5.77",
            "1.5.78",
            "1.5.79",
            "1.5.8",
            "1.5.80",
            "1.5.81",
            "1.5.82",
            "1.5.83",
            "1.5.84",
            "1.5.85",
            "1.5.86",
            "1.5.87",
            "1.5.88",
            "1.5.89",
            "1.5.9",
            "1.5.90",
            "1.5.91",
            "1.5.92",
            "1.5.93",
            "1.5.94",
            "1.5.95",
            "1.5.96",
            "1.5.97",
            "1.5.98",
            "1.5.99",
            "1.6.1",
            "1.6.10",
            "1.6.11",
            "1.6.12",
            "1.6.13",
            "1.6.14",
            "1.6.15",
            "1.6.16",
            "1.6.17",
            "1.6.18",
            "1.6.19",
            "1.6.2",
            "1.6.20",
            "1.6.21",
            "1.6.22",
            "1.6.23",
            "1.6.24",
            "1.6.25",
            "1.6.26",
            "1.6.27",
            "1.6.28",
            "1.6.29",
            "1.6.3",
            "1.6.30",
            "1.6.31",
            "1.6.32",
            "1.6.33",
            "1.6.34",
            "1.6.35",
            "1.6.36",
            "1.6.37",
            "1.6.38",
            "1.6.39",
            "1.6.4",
            "1.6.40",
            "1.6.41",
            "1.6.42",
            "1.6.43",
            "1.6.44",
            "1.6.45",
            "1.6.46",
            "1.6.47",
            "1.6.48",
            "1.6.5",
            "1.6.50",
            "1.6.51",
            "1.6.52",
            "1.6.53",
            "1.6.54",
            "1.6.55",
            "1.6.56",
            "1.6.57",
            "1.6.58",
            "1.6.6",
            "1.6.7",
            "1.6.8",
            "1.6.9"
          ]
        }
      ],
      "aliases": [
        "CVE-2026-57120",
        "GHSA-pv2j-rghr-v5r9"
      ],
      "details": "## Summary\n\nThe `execute_code` tool\u0027s subprocess sandbox advertises a three-layer defense (AST validation, text-pattern blocklist, restricted `__builtins__`). In **sandbox mode** (the default) only two layers are active \u2014 the text-pattern blocklist is skipped \u2014 and both remaining layers are bypassed by combining two CPython semantics:\n\n1. **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.\n2. **C-level attribute access via format-spec.** `str.format` / `str.format_map` resolve dotted field references through CPython\u0027s internal `PyObject_GetAttr` (`do_string_format` \u2192 `get_field`). This C path never consults the Python-level `getattr` binding. The sandbox\u0027s `_safe_getattr` wrapper (`python_tools.py:221`) is installed only as the `getattr` name in `safe_builtins`, so any C-level attribute access \u2014 including format-spec field resolution \u2014 sidesteps it. `format`/`format_map` are also absent from `_SANDBOX_BLOCKED_CALLS` (`python_tools.py:56`).\n\nCombined, this yields an arbitrary read primitive over every blocklisted attribute (`__class__`, `__qualname__`, `__bases__`, `__base__`, function `__globals__`, `__dict__`, \u2026).\n\n## Affected code\n\n| File | Lines | Symbol | Role |\n|---|---|---|---|\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 39\u201354 | `_SANDBOX_BLOCKED_ATTRS` | The blocklist the bypass renders unreachable |\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 56\u201360 | `_SANDBOX_BLOCKED_CALLS` | Missing entries: `format`, `format_map` |\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 75\u2013102 | `_validate_code_ast` | Static check, blind to runtime string assembly |\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 221\u2013226 | `_safe_getattr` | Wraps Python-level `getattr` only; C-level access bypasses |\n| `src/praisonai-agents/praisonaiagents/tools/python_tools.py` | 352 | `execute_code` | Entry point; gated by `@require_approval(risk_level=\"critical\")` |\n\n## Reproducer\n\n```python\nimport os\nos.environ[\"PRAISONAI_AUTO_APPROVE\"] = \"true\"\nfrom praisonaiagents.tools.python_tools import execute_code\n\npayload = \u0027\u0027\u0027\nund = \"_\" * 2                         # \"__\" assembled at runtime\nkey1 = und + \"class\" + und            # \"__class__\"\nkey2 = und + \"qualname\" + und         # \"__qualname__\"\nfmt_class = \"{0.\" + key1 + \"}\"\nfmt_qual2 = \"{0.\" + key1 + \".\" + key2 + \"}\"\nprint(\"LEAK_CLASS=\" + fmt_class.format(()))\nprint(\"LEAK_QUAL2=\" + fmt_qual2.format(()))\n\u0027\u0027\u0027\nprint(execute_code(payload, sandbox_mode=\"sandbox\", timeout=15))\n```\n\nObserved: `stdout` = `LEAK_CLASS=\u003cclass \u0027tuple\u0027\u003e` / `LEAK_QUAL2=tuple`, `success: true`, no security error. Both `__class__` (one hop) and `__class__.__qualname__` (two hops) \u2014 all blocklisted \u2014 are read.\n\n## Trust boundary\n\nThe `@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\u0027s FULL_AUTO autonomy mode, the bots-CLI launchers, and the project\u0027s own issue-triage CI workflow \u2014 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.\n\n## Relationship to GHSA-4mr5-g6f9-cfrh\n\nThe code\u0027s 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 \u2014 it bypasses the **entire** blocklist, because format-spec attribute resolution never consults the blocklist or `_safe_getattr`. `\"{0.__self__}\".format(print)` would leak `__self__` regardless of the blocklist. Same defense surface, different mechanism; the GHSA-4mr5 fix does not mitigate this.\n\n## Scope (read primitive only)\n\nThis reports the **read primitive**. Turning the read into in-process execution requires a callable bridge; the obvious one (`string.Formatter().get_field()` returning the live object) is not directly reachable because `import string` is blocked at the AST layer (no `ast.Import`). Other bridges may exist; a full execution chain is **not** claimed here. If one is found, severity rises to ~8.8 (the subprocess has no seccomp/`setrlimit`/syscall filtering).\n\n## Suggested fix\n\n1. Add `format`, `format_map` to `_SANDBOX_BLOCKED_CALLS` (blocks the calls at the AST layer; cost: also blocks benign `str.format`).\n2. Or replace `str` in `safe_builtins` with a subclass whose `format`/`format_map` reject dotted fields resolving to leading-underscore attributes (preserves benign formatting).\n3. Or drop sandbox-mode\u0027s in-process security claim and document that real isolation requires external sandboxing (gVisor/firejail/container/microVM) \u2014 which matches what the subprocess provides today.\n\nThe text-pattern blocklist present in the `direct` path (`python_tools.py:487-502`) is absent from the sandbox path; even if added, the runtime-assembly trick defeats it, so (1) or (2) is required.\n\nReporter: Kai Aizen / SnailSploit \u2014 kai@snailsploit.com \u2014 PGP on request. Coordinated disclosure; no public posting.",
      "id": "PYSEC-2026-3533",
      "modified": "2026-07-23T14:32:41.822909Z",
      "published": "2026-07-23T11:41:41.087045Z",
      "references": [
        {
          "type": "WEB",
          "url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-pv2j-rghr-v5r9"
        },
        {
          "type": "PACKAGE",
          "url": "https://github.com/MervinPraison/PraisonAI"
        },
        {
          "type": "PACKAGE",
          "url": "https://pypi.org/project/praisonaiagents"
        },
        {
          "type": "ADVISORY",
          "url": "https://github.com/advisories/GHSA-pv2j-rghr-v5r9"
        },
        {
          "type": "ADVISORY",
          "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-57120"
        }
      ],
      "severity": [
        {
          "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
          "type": "CVSS_V3"
        }
      ],
      "summary": "PraisonAI: execute_code sandbox bypass: str.format C-level attribute access reads every blocklisted dunder"
    }