Lack of data validation - Path Traversal In compliance-trestle
Description
compliance-trestle Profile Import has an Arbitrary File Read via trestle:// URI and Relative Path Traversal
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 ../...
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...
Root Cause:
Path(trestle_root / "../../etc/passwd").resolve() = /etc/passwd
No is_relative_to(trestle_root) check after resolve
TRESTLE_HREF_REGEX defined at const.py:253 but 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"...
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()) ...
Expected: 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() ...
Same 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.
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Package | Affected version | Patched versions |
|---|---|---|---|
pypi | 4.0.3, 3.12.2 |
Aliases
References