GHSA-j7qx-p75m-wp7g
PraisonAI dynamic-context artifact tools read arbitrary host files outside artifact storage
Details
# PraisonAI dynamic-context artifact tools read arbitrary host files outside artifact storage
## Summary
PraisonAI's Dynamic Context Discovery feature exposes artifact helper tools through `ctx.get_tools()`:
```python ctx = setup_dynamic_context()
agent = Agent( instructions="You are a data analyst.", tools=ctx.get_tools(), hooks=[ctx.get_middleware()], ) ```
The official documentation describes these helpers as a way for the agent to explore large tool-output artifacts that were queued by the middleware:
- large tool outputs are saved as artifacts; - the agent receives compact artifact references; and - the agent uses `artifact_tail` and `artifact_grep` to explore that data.
The implemented artifact tools do not enforce that the supplied `artifact_path` is an artifact created by the configured store or that it lives under the configured artifact base directory. Instead, `artifact_head`, `artifact_tail`, `artifact_grep`, and `artifact_chunk` wrap the caller-supplied path directly into an `ArtifactRef` and then read it from the host filesystem.
As a result, any prompt/user/tool-caller that can influence those tool arguments can read files readable by the PraisonAI process, such as project `.env` files, cloud credentials, SSH keys, source files, or other local data.
## Affected Product
- Repository: `MervinPraison/PraisonAI` - Ecosystem: `pip` - Package: `praisonai` - Component: Dynamic Context Discovery artifact tools - Current source path: `src/praisonai/praisonai/context/queue.py` - Artifact store path: `src/praisonai/praisonai/context/artifact_store.py` - Latest PyPI version validated: `4.6.58` - Current `origin/main` validated: `1ad58ca02975ff1398efeda694ea2ab78f20cf3e` - Current `origin/main` tag validated: `v4.6.58`
Suggested affected range:
```text pip:praisonai >= 3.8.1, <= 4.6.58 ```
Representative local sweep:
- `3.8.1`: vulnerable - `4.0.0`: vulnerable - `4.5.113`: vulnerable - `4.6.33`: vulnerable - `4.6.34`: vulnerable - `4.6.40`: vulnerable - `4.6.50`: vulnerable - `4.6.58`: vulnerable
## Root Cause
`create_artifact_tools()` creates an artifact store bound to `base_dir`, but the read tools do not use `base_dir` for containment.
For example, `artifact_head()` accepts `artifact_path` and immediately creates an `ArtifactRef` with that path:
```python def artifact_head(artifact_path: str, lines: int = 50) -> str: ref = ArtifactRef(path=artifact_path, summary="", size_bytes=0) try: return artifact_store.head(ref, lines=lines) except FileNotFoundError: return f"Error: Artifact not found: {artifact_path}" ```
`artifact_tail()`, `artifact_grep()`, and `artifact_chunk()` have the same pattern. They trust the caller-supplied path rather than resolving it through an artifact identifier, store lookup, manifest, or base-directory containment check.
The store methods then read that path directly:
```python def head(self, ref: ArtifactRef, lines: int = 50) -> str: file_path = Path(ref.path) if not file_path.exists(): raise FileNotFoundError(f"Artifact not found: {ref.path}")
result_lines = [] with open(file_path, "r", encoding="utf-8", errors="replace") as f: ... ```
There is no check equivalent to:
```python resolved = Path(ref.path).resolve() base = self.base_dir.resolve() resolved.relative_to(base) ```
There is also no check that the file has a valid `.meta` sidecar or appears in `artifact_list()`.
## Local PoV
Run against the latest PyPI package:
```bash uv run --with 'praisonai==4.6.58' \ python poc/pov_prai_cand_026_artifact_tools_arbitrary_file_read.py --json ```
The PoV:
1. Creates a temporary artifact base directory. 2. Creates a separate `outside-secret.txt` file outside that base directory. 3. Stores one legitimate artifact through `FileSystemArtifactStore.store()`. 4. Calls `artifact_head()` on the legitimate artifact as a positive control. 5. Calls `artifact_head()`, `artifact_grep()`, and `artifact_chunk()` on the outside file path. 6. Confirms `artifact_list()` does not list the outside file.
Observed output summary from `evidence/pov-pypi-4.6.58.json`:
```json { "package": "praisonai", "package_version": "4.6.58", "controls": { "outside_file_not_listed": true, "outside_file_outside_base_dir": true, "valid_artifact_read_works": true }, "outside_head": "PRAI-CAND-026-OUTSIDE-ARTIFACT-SECRET", "outside_grep": "Found 1 matches:\\n\\n--- Line 1 ---\\n> PRAI-CAND-026-OUTSIDE-ARTIFACT-SECRET\\n second line", "outside_chunk": "PRAI-CAND-026-OUTSIDE-ARTIFACT-SECRET", "outside_file_listed_by_artifact_list": false, "vulnerable": true } ```
The PoV was rerun successfully after a fresh `origin/main` fetch; see `evidence/pov-pypi-4.6.58-rerun.json`.
The PoV is local-only. It does not start a server, contact a third-party target, or use real credentials.
## Why This Is Not Intended Behavior
This report does not claim that every file-reading tool is automatically a vulnerability. The issue is narrower: tools documented and named as artifact helpers accept arbitrary host file paths.
The controls show the intended boundary:
- a valid artifact stored under `base_dir` is readable; - an outside file is not returned by `artifact_list()`; - the outside file is outside `base_dir`; and - the read helpers still disclose the outside file when handed its absolute path.
PraisonAI's own context-security documentation recommends relative paths and reviewing ignore rules to avoid sensitive-file exposure. Those controls are bypassed when artifact tools can be pointed directly at any readable host path.
## Impact
If a PraisonAI application exposes an agent with `ctx.get_tools()` to untrusted or lower-trust prompts, the lower-trust caller can request artifact tools against arbitrary local paths. This can disclose sensitive host files readable by the PraisonAI process, including:
- project `.env` files; - cloud or service credentials; - SSH keys; - local application configuration; - source files and private data; and - terminal/history artifacts from other runs if the path is known or guessed.
The impact is confidentiality-only in the tested surface. Integrity and availability are not claimed for this report.
## Duplicate Posture
I checked visible PraisonAI advisories and local prior PraisonAI submissions. This is distinct from nearby file-read/file-write issues:
- `GHSA-9cr9-25q5-8prj` / `CVE-2026-47394` covers MCP CLI `workflow.show`, `workflow.validate`, and `deploy.validate` path handling. This report covers Dynamic Context Discovery artifact tools in `context/queue.py`. - `GHSA-hvhp-v2gc-268q` / `CVE-2026-47397` covers `write_file` arbitrary file write when `workspace=None`. This report is a read-only disclosure issue in artifact helper tools. - Public recipe registry path traversal advisories cover recipe publish/pull storage and extraction. This report does not involve the recipe registry. - Local prior submissions in this harness do not cover `artifact_head`, `artifact_tail`, `artifact_grep`, `artifact_chunk`, or `FileSystemArtifactStore` path containment.
## Severity
Suggested severity: High.
Suggested CVSS v3.1:
Rationale:
- `AV`: applies when an application exposes a PraisonAI agent over a network chat/API surface, which is a documented PraisonAI deployment pattern. - `AC`: no race, special environment, or complex path manipulation is required; an absolute readable path is sufficient. - `PR`: an unauthenticated or public-facing agent endpoint can be exploited without an account. Deployments that require authenticated chat/API access may score this as `PR:L`. - `UI`: the attacker directly supplies the prompt/tool argument to the exposed agent surface. - `C`: arbitrary readable host files can contain secrets or private data. - `I/A`: this report demonstrates read-only disclosure.
## Remediation
Do not let artifact tools open arbitrary paths. Prefer stable artifact IDs over raw filesystem paths in tool arguments.
Recommended fixes:
1. Change tool schemas to accept `artifact_id` plus optional `run_id` and `agent_id`, then resolve those through the artifact store's metadata/index. 2. If path arguments must remain for compatibility, resolve the path with `Path(path).resolve()` and reject it unless it is under `artifact_store.base_dir.resolve()`. 3. Require a valid artifact metadata sidecar for read helpers. Files not created by `FileSystemArtifactStore.store()` should not be readable through artifact tools. 4. Apply the same containment check to `load()`, `head()`, `tail()`, `grep()`, `chunk()`, and `delete()`. 5. Avoid returning absolute host paths in prompt-visible artifact references when an opaque artifact ID would suffice.
Minimal containment helper:
```python def _resolve_artifact_path(self, path: str) -> Path: resolved = Path(path).expanduser().resolve() base = self.base_dir.resolve() try: resolved.relative_to(base) except ValueError as exc: raise PermissionError("Artifact path is outside artifact storage") from exc return resolved ```
This helper should be paired with metadata-sidecar validation so arbitrary non-artifact files placed under the base directory are not automatically treated as valid artifacts.
Are you affected?
Enter the version of the package you're using.
Affected packages
3.8.1 Fixed in: 4.6.59 pip install --upgrade 'praisonai>=4.6.59'