PYSEC-2026-3514 
# 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.
CVSS v3.1 score: 7.5