From 52d78ee8d9966077a1f199e34d55615d52c457de Mon Sep 17 00:00:00 2001 From: Rex Liu Date: Tue, 11 Aug 2026 16:39:16 -0700 Subject: [PATCH] sync ghsa-j7qx-p75m-wp7g details --- .../2026/06/GHSA-j7qx-p75m-wp7g/GHSA-j7qx-p75m-wp7g.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/advisories/github-reviewed/2026/06/GHSA-j7qx-p75m-wp7g/GHSA-j7qx-p75m-wp7g.json b/advisories/github-reviewed/2026/06/GHSA-j7qx-p75m-wp7g/GHSA-j7qx-p75m-wp7g.json index 61052dc3f32e..ed926db381db 100644 --- a/advisories/github-reviewed/2026/06/GHSA-j7qx-p75m-wp7g/GHSA-j7qx-p75m-wp7g.json +++ b/advisories/github-reviewed/2026/06/GHSA-j7qx-p75m-wp7g/GHSA-j7qx-p75m-wp7g.json @@ -6,8 +6,8 @@ "aliases": [ "CVE-2026-56834" ], - "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 >= 3.8.1, <= 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) -> 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) -> 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> 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) -> 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.", + "summary": "Dynamic-Context Artifact Tools Read Arbitrary Host Files Outside Artifact Storage", + "details": "## Summary\n\nPraisonAI's Dynamic Context Discovery feature exposes artifact helper tools through `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 explore 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 `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.\n\nAs 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.\n\n## Technical Details\n\n`create_artifact_tools()` creates an artifact store bound to `base_dir`, but the read tools do not use `base_dir` for containment.\n\nFor example, `artifact_head()` accepts `artifact_path` and immediately creates an `ArtifactRef` with that path:\n\n```python\ndef artifact_head(artifact_path: str, lines: int = 50) -> 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 pattern. They trust the caller-supplied path rather than resolving it through an artifact identifier, store lookup, manifest, or base-directory containment check.\n\nThe store methods then read that path directly:\n\n```python\ndef head(self, ref: ArtifactRef, lines: int = 50) -> 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 `artifact_list()`.\n\n### Why This Is Not Intended Behavior\n\nThis 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.\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 path.\n\nPraisonAI'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.\n\n## PoV\n\nRun against the latest PyPI package:\n\n```bash\nuv run --with 'praisonai==4.6.58' \\\n python poc/pov_poc.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 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\": \"poc\",\n \"outside_grep\": \"Found 1 matches:\\\\n\\\\n--- Line 1 ---\\\\n> poc\\\\n second line\",\n \"outside_chunk\": \"poc\",\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 `evidence/pov-pypi-4.6.58-rerun.json`.\n\nThe PoV is local-only. It does not start a server, contact a third-party target, or use real credentials.\n\n## PoC\n\nThe PoV section above contains the local reproduction command, input, and decisive output.\n\n## Impact\n\nIf 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:\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 availability are not claimed for this report.\n\n### Severity\n\nSuggested severity: High.\n\nSuggested CVSS v3.1:\n\n```text\nCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N\n```\n\nRationale:\n\n- `AV:N`: applies when an application exposes a PraisonAI agent over a network chat/API surface, which is a documented PraisonAI deployment pattern.\n- `AC:L`: no race, special environment, or complex path manipulation is required; an absolute readable path is sufficient.\n- `PR:N`: 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`.\n- `UI:N`: the attacker directly supplies the prompt/tool argument to the exposed agent surface.\n- `C:H`: arbitrary readable host files can contain secrets or private data.\n- `I:N/A:N`: this report demonstrates read-only disclosure.\n\n## Suggested Fix\n\nDo not let artifact tools open arbitrary paths. Prefer stable artifact IDs over raw filesystem paths in tool arguments.\n\nRecommended fixes:\n\n1. Change tool schemas to accept `artifact_id` plus optional `run_id` and `agent_id`, then resolve those through the artifact store's metadata/index.\n2. 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()`.\n3. Require a valid artifact metadata sidecar for read helpers. Files not created by `FileSystemArtifactStore.store()` should not be readable through artifact tools.\n4. Apply the same containment check to `load()`, `head()`, `tail()`, `grep()`, `chunk()`, and `delete()`.\n5. Avoid returning absolute host paths in prompt-visible artifact references when an opaque artifact ID would suffice.\n\nMinimal containment helper:\n\n```python\ndef _resolve_artifact_path(self, path: str) -> 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 non-artifact files placed under the base directory are not automatically treated as valid artifacts.\n\n## Affected Package/Versions\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: `1ad58ca02975ff1398efeda694ea2ab78f20cf3e`\n- Current `origin/main` tag validated: `v4.6.58`\n\nSuggested affected range:\n\n```text\npip:praisonai >= 3.8.1, <= 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## Advisory History\n\nVisible PraisonAI advisories and prior submissions were checked. This is distinct from nearby file-read/file-write issues:\n\n- `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`.\n- `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.\n- Public recipe registry path traversal advisories cover recipe publish/pull storage and extraction. This report does not involve the recipe registry.\n- Prior reports do not cover `artifact_head`, `artifact_tail`, `artifact_grep`, `artifact_chunk`, or `FileSystemArtifactStore` path containment.\n\n## References\n\n- PraisonAI Dynamic Context Discovery: `https://docs.praison.ai/docs/features/dynamic-context-discovery`\n- PraisonAI Context Security & Redaction: `https://docs.praison.ai/docs/features/context-security-redaction` `https://github.com/MervinPraison/PraisonAI/security/policy`\n- PraisonAI GitHub advisories: `https://github.com/MervinPraison/PraisonAI/security/advisories`\n- MITRE CWE-22: `https://cwe.mitre.org/data/definitions/22.html`\n- MITRE CWE-200: `https://cwe.mitre.org/data/definitions/200.html`\n- FIRST CVSS v3.1 calculator: `https://www.first.org/cvss/calculator/3.1`\n- Python `pathlib` documentation: `https://docs.python.org/3/library/pathlib.html`\n", "severity": [ { "type": "CVSS_V3", @@ -58,4 +58,4 @@ "github_reviewed_at": "2026-06-18T13:52:51Z", "nvd_published_at": null } -} \ No newline at end of file +}