{"id":"PYSEC-2026-3514","summary":"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\n\n## Summary\n\nPraisonAI's Dynamic Context Discovery feature exposes artifact helper tools\nthrough `ctx.get_tools()`:\n\n```python\nctx = setup_dynamic_context()\n\nagent = Agent(\n    instructions=\"You are a data analyst.\",\n    tools=ctx.get_tools(),\n    hooks=[ctx.get_middleware()],\n)\n```\n\nThe official documentation describes these helpers as a way for the agent to\nexplore large tool-output artifacts that were queued by the middleware:\n\n- large tool outputs are saved as artifacts;\n- the agent receives compact artifact references; and\n- the agent uses `artifact_tail` and `artifact_grep` to explore that data.\n\nThe implemented artifact tools do not enforce that the supplied\n`artifact_path` is an artifact created by the configured store or that it lives\nunder the configured artifact base directory. Instead, `artifact_head`,\n`artifact_tail`, `artifact_grep`, and `artifact_chunk` wrap the caller-supplied\npath directly into an `ArtifactRef` and then read it from the host filesystem.\n\nAs a result, any prompt/user/tool-caller that can influence those tool\narguments can read files readable by the PraisonAI process, such as project\n`.env` files, cloud credentials, SSH keys, source files, or other local data.\n\n## Affected Product\n\n- Repository: `MervinPraison/PraisonAI`\n- Ecosystem: `pip`\n- Package: `praisonai`\n- Component: Dynamic Context Discovery artifact tools\n- Current source path: `src/praisonai/praisonai/context/queue.py`\n- Artifact store path: `src/praisonai/praisonai/context/artifact_store.py`\n- Latest PyPI version validated: `4.6.58`\n- Current `origin/main` validated:\n  `1ad58ca02975ff1398efeda694ea2ab78f20cf3e`\n- Current `origin/main` tag validated: `v4.6.58`\n\nSuggested affected range:\n\n```text\npip:praisonai \u003e= 3.8.1, \u003c= 4.6.58\n```\n\nRepresentative local sweep:\n\n- `3.8.1`: vulnerable\n- `4.0.0`: vulnerable\n- `4.5.113`: vulnerable\n- `4.6.33`: vulnerable\n- `4.6.34`: vulnerable\n- `4.6.40`: vulnerable\n- `4.6.50`: vulnerable\n- `4.6.58`: vulnerable\n\n## Root Cause\n\n`create_artifact_tools()` creates an artifact store bound to `base_dir`, but the\nread tools do not use `base_dir` for containment.\n\nFor example, `artifact_head()` accepts `artifact_path` and immediately creates\nan `ArtifactRef` with that path:\n\n```python\ndef artifact_head(artifact_path: str, lines: int = 50) -\u003e str:\n    ref = ArtifactRef(path=artifact_path, summary=\"\", size_bytes=0)\n    try:\n        return artifact_store.head(ref, lines=lines)\n    except FileNotFoundError:\n        return f\"Error: Artifact not found: {artifact_path}\"\n```\n\n`artifact_tail()`, `artifact_grep()`, and `artifact_chunk()` have the same\npattern. They trust the caller-supplied path rather than resolving it through\nan artifact identifier, store lookup, manifest, or base-directory containment\ncheck.\n\nThe store methods then read that path directly:\n\n```python\ndef head(self, ref: ArtifactRef, lines: int = 50) -\u003e str:\n    file_path = Path(ref.path)\n    if not file_path.exists():\n        raise FileNotFoundError(f\"Artifact not found: {ref.path}\")\n\n    result_lines = []\n    with open(file_path, \"r\", encoding=\"utf-8\", errors=\"replace\") as f:\n        ...\n```\n\nThere is no check equivalent to:\n\n```python\nresolved = Path(ref.path).resolve()\nbase = self.base_dir.resolve()\nresolved.relative_to(base)\n```\n\nThere is also no check that the file has a valid `.meta` sidecar or appears in\n`artifact_list()`.\n\n## Local PoV\n\nRun against the latest PyPI package:\n\n```bash\nuv run --with 'praisonai==4.6.58' \\\n  python poc/pov_prai_cand_026_artifact_tools_arbitrary_file_read.py --json\n```\n\nThe PoV:\n\n1. Creates a temporary artifact base directory.\n2. Creates a separate `outside-secret.txt` file outside that base directory.\n3. Stores one legitimate artifact through `FileSystemArtifactStore.store()`.\n4. Calls `artifact_head()` on the legitimate artifact as a positive control.\n5. Calls `artifact_head()`, `artifact_grep()`, and `artifact_chunk()` on the\n   outside file path.\n6. Confirms `artifact_list()` does not list the outside file.\n\nObserved output summary from `evidence/pov-pypi-4.6.58.json`:\n\n```json\n{\n  \"package\": \"praisonai\",\n  \"package_version\": \"4.6.58\",\n  \"controls\": {\n    \"outside_file_not_listed\": true,\n    \"outside_file_outside_base_dir\": true,\n    \"valid_artifact_read_works\": true\n  },\n  \"outside_head\": \"PRAI-CAND-026-OUTSIDE-ARTIFACT-SECRET\",\n  \"outside_grep\": \"Found 1 matches:\\\\n\\\\n--- Line 1 ---\\\\n\u003e PRAI-CAND-026-OUTSIDE-ARTIFACT-SECRET\\\\n  second line\",\n  \"outside_chunk\": \"PRAI-CAND-026-OUTSIDE-ARTIFACT-SECRET\",\n  \"outside_file_listed_by_artifact_list\": false,\n  \"vulnerable\": true\n}\n```\n\nThe PoV was rerun successfully after a fresh `origin/main` fetch; see\n`evidence/pov-pypi-4.6.58-rerun.json`.\n\nThe PoV is local-only. It does not start a server, contact a third-party\ntarget, or use real credentials.\n\n## Why This Is Not Intended Behavior\n\nThis report does not claim that every file-reading tool is automatically a\nvulnerability. The issue is narrower: tools documented and named as artifact\nhelpers accept arbitrary host file paths.\n\nThe controls show the intended boundary:\n\n- a valid artifact stored under `base_dir` is readable;\n- an outside file is not returned by `artifact_list()`;\n- the outside file is outside `base_dir`; and\n- the read helpers still disclose the outside file when handed its absolute\n  path.\n\nPraisonAI's own context-security documentation recommends relative paths and\nreviewing ignore rules to avoid sensitive-file exposure. Those controls are\nbypassed when artifact tools can be pointed directly at any readable host path.\n\n## Impact\n\nIf a PraisonAI application exposes an agent with `ctx.get_tools()` to\nuntrusted or lower-trust prompts, the lower-trust caller can request artifact\ntools against arbitrary local paths. This can disclose sensitive host files\nreadable by the PraisonAI process, including:\n\n- project `.env` files;\n- cloud or service credentials;\n- SSH keys;\n- local application configuration;\n- source files and private data; and\n- terminal/history artifacts from other runs if the path is known or guessed.\n\nThe impact is confidentiality-only in the tested surface. Integrity and\navailability are not claimed for this report.\n\n## Duplicate Posture\n\nI checked visible PraisonAI advisories and local prior PraisonAI submissions.\nThis is distinct from nearby file-read/file-write issues:\n\n- `GHSA-9cr9-25q5-8prj` / `CVE-2026-47394` covers MCP CLI\n  `workflow.show`, `workflow.validate`, and `deploy.validate` path handling.\n  This report covers Dynamic Context Discovery artifact tools in\n  `context/queue.py`.\n- `GHSA-hvhp-v2gc-268q` / `CVE-2026-47397` covers `write_file` arbitrary file\n  write when `workspace=None`. This report is a read-only disclosure issue in\n  artifact helper tools.\n- Public recipe registry path traversal advisories cover recipe publish/pull\n  storage and extraction. This report does not involve the recipe registry.\n- Local prior submissions in this harness do not cover `artifact_head`,\n  `artifact_tail`, `artifact_grep`, `artifact_chunk`, or\n  `FileSystemArtifactStore` path containment.\n\n## Severity\n\nSuggested severity: High.\n\nSuggested CVSS v3.1:\n\nRationale:\n\n- `AV`: applies when an application exposes a PraisonAI agent over a network\n  chat/API surface, which is a documented PraisonAI deployment pattern.\n- `AC`: no race, special environment, or complex path manipulation is\n  required; an absolute readable path is sufficient.\n- `PR`: an unauthenticated or public-facing agent endpoint can be exploited\n  without an account. Deployments that require authenticated chat/API access\n  may score this as `PR:L`.\n- `UI`: the attacker directly supplies the prompt/tool argument to the\n  exposed agent surface.\n- `C`: arbitrary readable host files can contain secrets or private data.\n- `I/A`: this report demonstrates read-only disclosure.\n\n## Remediation\n\nDo not let artifact tools open arbitrary paths. Prefer stable artifact IDs over\nraw filesystem paths in tool arguments.\n\nRecommended fixes:\n\n1. Change tool schemas to accept `artifact_id` plus optional `run_id` and\n   `agent_id`, then resolve those through the artifact store's metadata/index.\n2. If path arguments must remain for compatibility, resolve the path with\n   `Path(path).resolve()` and reject it unless it is under\n   `artifact_store.base_dir.resolve()`.\n3. Require a valid artifact metadata sidecar for read helpers. Files not\n   created by `FileSystemArtifactStore.store()` should not be readable through\n   artifact tools.\n4. Apply the same containment check to `load()`, `head()`, `tail()`, `grep()`,\n   `chunk()`, and `delete()`.\n5. Avoid returning absolute host paths in prompt-visible artifact references\n   when an opaque artifact ID would suffice.\n\nMinimal containment helper:\n\n```python\ndef _resolve_artifact_path(self, path: str) -\u003e Path:\n    resolved = Path(path).expanduser().resolve()\n    base = self.base_dir.resolve()\n    try:\n        resolved.relative_to(base)\n    except ValueError as exc:\n        raise PermissionError(\"Artifact path is outside artifact storage\") from exc\n    return resolved\n```\n\nThis helper should be paired with metadata-sidecar validation so arbitrary\nnon-artifact files placed under the base directory are not automatically\ntreated as valid artifacts.","aliases":["CVE-2026-56834","GHSA-j7qx-p75m-wp7g"],"modified":"2026-07-23T15:00:17.165873403Z","published":"2026-07-23T11:41:40.539949Z","references":[{"type":"WEB","url":"https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-j7qx-p75m-wp7g"},{"type":"PACKAGE","url":"https://github.com/MervinPraison/PraisonAI"},{"type":"PACKAGE","url":"https://pypi.org/project/praisonai"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-j7qx-p75m-wp7g"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-56834"}],"affected":[{"package":{"name":"praisonai","ecosystem":"PyPI","purl":"pkg:pypi/praisonai"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.8.1"},{"fixed":"4.6.59"}]}],"versions":["3.10.0","3.10.1","3.10.10","3.10.11","3.10.12","3.10.13","3.10.14","3.10.15","3.10.16","3.10.17","3.10.18","3.10.19","3.10.2","3.10.20","3.10.21","3.10.22","3.10.23","3.10.24","3.10.25","3.10.26","3.10.27","3.10.3","3.10.4","3.10.5","3.10.6","3.10.7","3.10.8","3.10.9","3.11.0","3.11.1","3.11.10","3.11.11","3.11.12","3.11.13","3.11.14","3.11.2","3.11.3","3.11.4","3.11.8","3.11.9","3.12.0","3.12.1","3.12.2","3.12.3","3.8.1","3.8.10","3.8.11","3.8.12","3.8.13","3.8.14","3.8.16","3.8.17","3.8.18","3.8.19","3.8.2","3.8.20","3.8.21","3.8.22","3.8.3","3.8.4","3.8.5","3.8.6","3.8.7","3.8.8","3.8.9","3.9.0","3.9.1","3.9.10","3.9.11","3.9.12","3.9.13","3.9.14","3.9.15","3.9.16","3.9.17","3.9.18","3.9.19","3.9.2","3.9.20","3.9.21","3.9.22","3.9.23","3.9.24","3.9.25","3.9.26","3.9.27","3.9.28","3.9.29","3.9.3","3.9.30","3.9.31","3.9.32","3.9.33","3.9.34","3.9.35","3.9.4","3.9.5","3.9.6","3.9.7","3.9.8","3.9.9","4.0.0","4.1.0","4.2.0","4.2.1","4.2.2","4.2.3","4.2.4","4.3.0","4.3.1","4.4.0","4.4.10","4.4.11","4.4.12","4.4.2","4.4.3","4.4.4","4.4.5","4.4.6","4.4.7","4.4.8","4.4.9","4.5.0","4.5.1","4.5.10","4.5.100","4.5.101","4.5.102","4.5.103","4.5.104","4.5.105","4.5.106","4.5.107","4.5.108","4.5.109","4.5.11","4.5.110","4.5.111","4.5.112","4.5.113","4.5.114","4.5.115","4.5.117","4.5.118","4.5.119","4.5.12","4.5.120","4.5.121","4.5.122","4.5.123","4.5.124","4.5.125","4.5.126","4.5.127","4.5.128","4.5.129","4.5.13","4.5.130","4.5.131","4.5.132","4.5.133","4.5.134","4.5.135","4.5.136","4.5.137","4.5.139","4.5.14","4.5.140","4.5.143","4.5.144","4.5.145","4.5.149","4.5.15","4.5.16","4.5.18","4.5.19","4.5.2","4.5.20","4.5.21","4.5.22","4.5.23","4.5.24","4.5.25","4.5.26","4.5.27","4.5.28","4.5.29","4.5.3","4.5.30","4.5.31","4.5.32","4.5.33","4.5.34","4.5.35","4.5.36","4.5.37","4.5.38","4.5.39","4.5.40","4.5.41","4.5.42","4.5.43","4.5.44","4.5.45","4.5.46","4.5.48","4.5.49","4.5.5","4.5.51","4.5.52","4.5.54","4.5.55","4.5.56","4.5.57","4.5.58","4.5.59","4.5.6","4.5.60","4.5.62","4.5.63","4.5.64","4.5.65","4.5.67","4.5.68","4.5.69","4.5.7","4.5.70","4.5.71","4.5.72","4.5.73","4.5.74","4.5.76","4.5.77","4.5.78","4.5.79","4.5.8","4.5.80","4.5.81","4.5.82","4.5.83","4.5.85","4.5.87","4.5.88","4.5.89","4.5.9","4.5.90","4.5.93","4.5.94","4.5.95","4.5.96","4.5.97","4.5.98","4.6.10","4.6.11","4.6.12","4.6.13","4.6.14","4.6.15","4.6.16","4.6.18","4.6.19","4.6.20","4.6.21","4.6.22","4.6.23","4.6.24","4.6.25","4.6.26","4.6.27","4.6.28","4.6.29","4.6.30","4.6.31","4.6.32","4.6.33","4.6.34","4.6.35","4.6.36","4.6.37","4.6.38","4.6.39","4.6.40","4.6.41","4.6.42","4.6.43","4.6.44","4.6.45","4.6.46","4.6.47","4.6.48","4.6.50","4.6.51","4.6.52","4.6.53","4.6.54","4.6.55","4.6.56","4.6.57","4.6.58","4.6.9"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/praisonai/PYSEC-2026-3514.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"}]}