GHSA-j7qx-p75m-wp7g
PraisonAI dynamic-context artifact tools read arbitrary host files outside artifact storage
Packages
Details
As written in the advisory (Markdown source, shown as plain text).
# 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.
References
External links from the advisory. They lead to third-party sites.