Every CVE Search/ Get API key

CVE-2026-52776

Trestle URLSecurityValidator SSRF allowlist bypass via IPv4-mapped IPv6 and 0.0.0.0

8.6 HighFix available

Compliance-trestle (Trestle) is a tooling platform for managing compliance as code. In versions before 3.12.4 and versions 4.0.0 through 4.0.3, the URLSecurityValidator that guards trestle's remote-fetch paths against server-side request forgery can be bypassed to reach loopback, link-local, cloud-metadata, and internal network endpoints it was designed to block. The blocklist does not canonicalize IPv4-mapped IPv6 literals such as [::ffff:169.254.169.254], which resolve to IPv6Address objects that never match the blocked IPv4 ranges, and it does not block the unspecified address 0.0.0.0, which routes to local services on Linux and inside containers. An attacker who can supply or influence an OSCAL artifact that trestle fetches, such as a malicious profile whose imports reference one of these bypass URLs, can cause the HTTPSFetcher and SFTPFetcher paths to contact cloud instance-metadata services, loopback interfaces, or internal hosts. This issue is fixed in versions 3.12.4 and 4.1.0.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
High
Availability
None

Exploit likelihood

0.44%Unchanged since Aug 26, 2026
90%50%10%1%

What each source reports

CVE List Score used 8.6

Trestle URLSecurityValidator SSRF allowlist bypass via IPv4-mapped IPv6 and 0.0.0.0

Compliance-trestle (Trestle) is a tooling platform for managing compliance as code. In versions before 3.12.4 and versions 4.0.0 through 4.0.3, the URLSecurityValidator that guards trestle's remote-fetch paths against server-side request forgery can be bypassed to reach loopback, link-local, cloud-metadata, and internal network endpoints it was designed to block. The blocklist does not canonicalize IPv4-mapped IPv6 literals such as [::ffff:169.254.169.254], which resolve to IPv6Address objects that never match the blocked IPv4 ranges, and it does not block the unspecified address 0.0.0.0, which routes to local services on Linux and inside containers. An attacker who can supply or influence an OSCAL artifact that trestle fetches, such as a malicious profile whose imports reference one of these bypass URLs, can cause the HTTPSFetcher and SFTPFetcher paths to contact cloud instance-metadata services, loopback interfaces, or internal hosts. This issue is fixed in versions 3.12.4 and 4.1.0.

Record
CVE-2026-52776
CVSS 4.0
8.6 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-184, CWE-918
Updated
2026-11-02
EUVDEUVD-2026-66210 8.6

Compliance-trestle (Trestle) is a tooling platform for managing compliance as code. In versions before 3.12.4 and versions 4.0.0 through 4.0.3, the URLSecurityValidator that guards trestle's remote-fetch paths against server-side request forgery can be bypassed to reach loopback, link-local, cloud-metadata, and internal network endpoints it was designed to block. The blocklist does not canonicalize IPv4-mapped IPv6 literals such as [::ffff:169.254.169.254], which resolve to IPv6Address objects that never match the blocked IPv4 ranges, and it does not block the unspecified address 0.0.0.0, which routes to local services on Linux and inside containers. An attacker who can supply or influence an OSCAL artifact that trestle fetches, such as a malicious profile whose imports reference one of these bypass URLs, can cause the HTTPSFetcher and SFTPFetcher paths to contact cloud instance-metadata services, loopback interfaces, or internal hosts. This issue is fixed in versions 3.12.4 and 4.1.0.

Record
EUVD-2026-66210
CVSS 4.0
8.6 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-h47f-gmjp-m7rr 8.6

compliance-trestle has an URLSecurityValidator SSRF allowlist bypass via IPv4-mapped IPv6 and 0.0.0.0

Summary

compliance-trestle 4.0.3 (latest) ships an URLSecurityValidator in trestle/core/remote/security.py to block SSRF to loopback / link-local / cloud-metadata endpoints from the HTTPSFetcher and SFTPFetcher remote-fetch paths. The allowlist is incomplete and can be bypassed by four equivalent address representations that resolve to the same blocked host but evade the validator's checks:

  • IPv4-mapped IPv6 literals ([::ffff:169.254.169.254], [::ffff:127.0.0.1], [::ffff:10.0.0.1]) are returned by socket.getaddrinfo as IPv6Address objects; IPv6Address in IPv4Network('169.254.0.0/16') returns False, so the _check_blocked_networks and _check_private_networks predicates do not match.
  • IPv4 unspecified address 0.0.0.0 is not in ALWAYS_BLOCKED_NETWORKS (which covers 127.0.0.0/8 but not 0.0.0.0/8); on Linux + Docker, 0.0.0.0 routes to local services on any interface, and on dual-stack-mapped sockets it also reaches loopback listeners.

A malicious OSCAL profile referencing one of these URLs in imports[*].href or back-matter.resources[*].rlinks[*].href causes HTTPSFetcher.__init__ and _do_fetch (which both invoke validator.validate_url) to pass the URL through to requests.get, contacting cloud-metadata services, loopback admin interfaces, or RFC 1918 internal networks (with TRESTLE_BLOCK_PRIVATE_IPS=true set) that the validator was specifically designed to block.

Affected versions

compliance-trestle (PyPI) versions <= 4.0.3 are affected. 4.0.3 (released 2026-05-20) is the latest release and the one that introduced URLSecurityValidator; prior releases had no SSRF guard at all.

Privilege required

Network-position attacker who can supply or influence an OSCAL artifact (profile / catalog / SSP / component-definition) that compliance-trestle subsequently fetches via HTTPSFetcher or SFTPFetcher. The most realistic vector is a malicious OSCAL profile whose imports[*].href references one of the bypass URLs; the artifact then flows through trestle href add / trestle import / trestle assemble / trestle author / any workflow that resolves the profile's imports.

Root cause

trestle/core/remote/security.py (4.0.3, lines 56-71 + 156-167):

ALWAYS_BLOCKED_NETWORKS = [
    ipaddress.ip_network('127.0.0.0/8'),     # IPv4 loopback only
    ipaddress.ip_network('::1/128'),         # IPv6 loopback (single address)
    ipaddress.ip_network('169.254.0.0/16'),  # IPv4 link-local only
    ipaddress.ip_network('fe80::/10'),       # IPv6 link-local
]

METADATA_HOSTNAMES = {
    '169.254.169.254',          # IPv4 literal only
    'metadata.google.internal',
    'metadata.azure.com',
    '100.100.100.200',
}

def _check_blocked_networks(self, ip_addr, hostname):
    for network in ALWAYS_BLOCKED_NETWORKS:
        if ip_addr in network:  # IPv6Address in IPv4Network -> False
            raise TrestleError(...)

Four independent gaps:

  1. No IPv4-mapped IPv6 normalization. socket.getaddrinfo('::ffff:169.254.169.254', None) returns an IPv6Address. Python's ipaddress module raises TypeError if mixed types are compared, and the in operator suppresses that to False. The validator never calls .ipv4_mapped to canonicalize before the membership check, so any always-blocked IPv4 range is bypassable via the [::ffff:N.N.N.N] literal.

  2. METADATA_HOSTNAMES is an exact-string set. The hostname for https://[::ffff:169.254.169.254]/ is ::ffff:169.254.169.254, which is not in the set.

  3. 0.0.0.0 is not blocked. 0.0.0.0 is not in any of the four ALWAYS_BLOCKED_NETWORKS ranges. On Linux and inside containers, connecting to 0.0.0.0 routes to local services on any interface (a common SSRF technique against Docker / orchestrator agents on 0.0.0.0:PORT).

  4. DNS rebinding ribbon is only one IP deep. _resolve_hostname records the first getaddrinfo result set, but a hostname with mixed records can still serve a private IP on…

Record
GHSA-h47f-gmjp-m7rr
CVSS 4.0
8.6 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-184, CWE-918
Updated
2026-11-02
NVD 8.6

Compliance-trestle (Trestle) is a tooling platform for managing compliance as code. In versions before 3.12.4 and versions 4.0.0 through 4.0.3, the URLSecurityValidator that guards trestle's remote-fetch paths against server-side request forgery can be bypassed to reach loopback, link-local, cloud-metadata, and internal network endpoints it was designed to block. The blocklist does not canonicalize IPv4-mapped IPv6 literals such as [::ffff:169.254.169.254], which resolve to IPv6Address objects that never match the blocked IPv4 ranges, and it does not block the unspecified address 0.0.0.0, which routes to local services on Linux and inside containers. An attacker who can supply or influence an OSCAL artifact that trestle fetches, such as a malicious profile whose imports reference one of these bypass URLs, can cause the HTTPSFetcher and SFTPFetcher paths to contact cloud instance-metadata services, loopback interfaces, or internal hosts. This issue is fixed in versions 3.12.4 and 4.1.0.

Record
CVE-2026-52776
CVSS 4.0
8.6 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/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-184, CWE-918
Updated
2026-11-02
OSV 8.6

Trestle URLSecurityValidator SSRF allowlist bypass via IPv4-mapped IPv6 and 0.0.0.0

Compliance-trestle (Trestle) is a tooling platform for managing compliance as code. In versions before 3.12.4 and versions 4.0.0 through 4.0.3, the URLSecurityValidator that guards trestle's remote-fetch paths against server-side request forgery can be bypassed to reach loopback, link-local, cloud-metadata, and internal network endpoints it was designed to block. The blocklist does not canonicalize IPv4-mapped IPv6 literals such as [::ffff:169.254.169.254], which resolve to IPv6Address objects that never match the blocked IPv4 ranges, and it does not block the unspecified address 0.0.0.0, which routes to local services on Linux and inside containers. An attacker who can supply or influence an OSCAL artifact that trestle fetches, such as a malicious profile whose imports reference one of these bypass URLs, can cause the HTTPSFetcher and SFTPFetcher paths to contact cloud instance-metadata services, loopback interfaces, or internal hosts. This issue is fixed in versions 3.12.4 and 4.1.0.

Record
CVE-2026-52776
CVSS 4.0
8.6 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-184, CWE-918
Updated
2026-11-02
OSVGHSA-h47f-gmjp-m7rr 8.6

compliance-trestle has an URLSecurityValidator SSRF allowlist bypass via IPv4-mapped IPv6 and 0.0.0.0

Summary

compliance-trestle 4.0.3 (latest) ships an URLSecurityValidator in trestle/core/remote/security.py to block SSRF to loopback / link-local / cloud-metadata endpoints from the HTTPSFetcher and SFTPFetcher remote-fetch paths. The allowlist is incomplete and can be bypassed by four equivalent address representations that resolve to the same blocked host but evade the validator's checks:

  • IPv4-mapped IPv6 literals ([::ffff:169.254.169.254], [::ffff:127.0.0.1], [::ffff:10.0.0.1]) are returned by socket.getaddrinfo as IPv6Address objects; IPv6Address in IPv4Network('169.254.0.0/16') returns False, so the _check_blocked_networks and _check_private_networks predicates do not match.
  • IPv4 unspecified address 0.0.0.0 is not in ALWAYS_BLOCKED_NETWORKS (which covers 127.0.0.0/8 but not 0.0.0.0/8); on Linux + Docker, 0.0.0.0 routes to local services on any interface, and on dual-stack-mapped sockets it also reaches loopback listeners.

A malicious OSCAL profile referencing one of these URLs in imports[*].href or back-matter.resources[*].rlinks[*].href causes HTTPSFetcher.__init__ and _do_fetch (which both invoke validator.validate_url) to pass the URL through to requests.get, contacting cloud-metadata services, loopback admin interfaces, or RFC 1918 internal networks (with TRESTLE_BLOCK_PRIVATE_IPS=true set) that the validator was specifically designed to block.

Affected versions

compliance-trestle (PyPI) versions <= 4.0.3 are affected. 4.0.3 (released 2026-05-20) is the latest release and the one that introduced URLSecurityValidator; prior releases had no SSRF guard at all.

Privilege required

Network-position attacker who can supply or influence an OSCAL artifact (profile / catalog / SSP / component-definition) that compliance-trestle subsequently fetches via HTTPSFetcher or SFTPFetcher. The most realistic vector is a malicious OSCAL profile whose imports[*].href references one of the bypass URLs; the artifact then flows through trestle href add / trestle import / trestle assemble / trestle author / any workflow that resolves the profile's imports.

Root cause

trestle/core/remote/security.py (4.0.3, lines 56-71 + 156-167):

ALWAYS_BLOCKED_NETWORKS = [
    ipaddress.ip_network('127.0.0.0/8'),     # IPv4 loopback only
    ipaddress.ip_network('::1/128'),         # IPv6 loopback (single address)
    ipaddress.ip_network('169.254.0.0/16'),  # IPv4 link-local only
    ipaddress.ip_network('fe80::/10'),       # IPv6 link-local
]

METADATA_HOSTNAMES = {
    '169.254.169.254',          # IPv4 literal only
    'metadata.google.internal',
    'metadata.azure.com',
    '100.100.100.200',
}

def _check_blocked_networks(self, ip_addr, hostname):
    for network in ALWAYS_BLOCKED_NETWORKS:
        if ip_addr in network:  # IPv6Address in IPv4Network -> False
            raise TrestleError(...)

Four independent gaps:

  1. No IPv4-mapped IPv6 normalization. socket.getaddrinfo('::ffff:169.254.169.254', None) returns an IPv6Address. Python's ipaddress module raises TypeError if mixed types are compared, and the in operator suppresses that to False. The validator never calls .ipv4_mapped to canonicalize before the membership check, so any always-blocked IPv4 range is bypassable via the [::ffff:N.N.N.N] literal.

  2. METADATA_HOSTNAMES is an exact-string set. The hostname for https://[::ffff:169.254.169.254]/ is ::ffff:169.254.169.254, which is not in the set.

  3. 0.0.0.0 is not blocked. 0.0.0.0 is not in any of the four ALWAYS_BLOCKED_NETWORKS ranges. On Linux and inside containers, connecting to 0.0.0.0 routes to local services on any interface (a common SSRF technique against Docker / orchestrator agents on 0.0.0.0:PORT).

  4. DNS rebinding ribbon is only one IP deep. _resolve_hostname records the first getaddrinfo result set, but a hostname with mixed records can still serve a private IP on…

Record
GHSA-h47f-gmjp-m7rr
CVSS 4.0
8.6 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-184, CWE-918
Updated
2026-11-02
OSVPYSEC-2026-3659 8.6

compliance-trestle has an URLSecurityValidator SSRF allowlist bypass via IPv4-mapped IPv6 and 0.0.0.0

Summary

compliance-trestle 4.0.3 (latest) ships an URLSecurityValidator in trestle/core/remote/security.py to block SSRF to loopback / link-local / cloud-metadata endpoints from the HTTPSFetcher and SFTPFetcher remote-fetch paths. The allowlist is incomplete and can be bypassed by four equivalent address representations that resolve to the same blocked host but evade the validator's checks:

  • IPv4-mapped IPv6 literals ([::ffff:169.254.169.254], [::ffff:127.0.0.1], [::ffff:10.0.0.1]) are returned by socket.getaddrinfo as IPv6Address objects; IPv6Address in IPv4Network('169.254.0.0/16') returns False, so the _check_blocked_networks and _check_private_networks predicates do not match.
  • IPv4 unspecified address 0.0.0.0 is not in ALWAYS_BLOCKED_NETWORKS (which covers 127.0.0.0/8 but not 0.0.0.0/8); on Linux + Docker, 0.0.0.0 routes to local services on any interface, and on dual-stack-mapped sockets it also reaches loopback listeners.

A malicious OSCAL profile referencing one of these URLs in imports[*].href or back-matter.resources[*].rlinks[*].href causes HTTPSFetcher.__init__ and _do_fetch (which both invoke validator.validate_url) to pass the URL through to requests.get, contacting cloud-metadata services, loopback admin interfaces, or RFC 1918 internal networks (with TRESTLE_BLOCK_PRIVATE_IPS=true set) that the validator was specifically designed to block.

Affected versions

compliance-trestle (PyPI) versions <= 4.0.3 are affected. 4.0.3 (released 2026-05-20) is the latest release and the one that introduced URLSecurityValidator; prior releases had no SSRF guard at all.

Privilege required

Network-position attacker who can supply or influence an OSCAL artifact (profile / catalog / SSP / component-definition) that compliance-trestle subsequently fetches via HTTPSFetcher or SFTPFetcher. The most realistic vector is a malicious OSCAL profile whose imports[*].href references one of the bypass URLs; the artifact then flows through trestle href add / trestle import / trestle assemble / trestle author / any workflow that resolves the profile's imports.

Root cause

trestle/core/remote/security.py (4.0.3, lines 56-71 + 156-167):

ALWAYS_BLOCKED_NETWORKS = [
    ipaddress.ip_network('127.0.0.0/8'),     # IPv4 loopback only
    ipaddress.ip_network('::1/128'),         # IPv6 loopback (single address)
    ipaddress.ip_network('169.254.0.0/16'),  # IPv4 link-local only
    ipaddress.ip_network('fe80::/10'),       # IPv6 link-local
]

METADATA_HOSTNAMES = {
    '169.254.169.254',          # IPv4 literal only
    'metadata.google.internal',
    'metadata.azure.com',
    '100.100.100.200',
}

def _check_blocked_networks(self, ip_addr, hostname):
    for network in ALWAYS_BLOCKED_NETWORKS:
        if ip_addr in network:  # IPv6Address in IPv4Network -> False
            raise TrestleError(...)

Four independent gaps:

  1. No IPv4-mapped IPv6 normalization. socket.getaddrinfo('::ffff:169.254.169.254', None) returns an IPv6Address. Python's ipaddress module raises TypeError if mixed types are compared, and the in operator suppresses that to False. The validator never calls .ipv4_mapped to canonicalize before the membership check, so any always-blocked IPv4 range is bypassable via the [::ffff:N.N.N.N] literal.

  2. METADATA_HOSTNAMES is an exact-string set. The hostname for https://[::ffff:169.254.169.254]/ is ::ffff:169.254.169.254, which is not in the set.

  3. 0.0.0.0 is not blocked. 0.0.0.0 is not in any of the four ALWAYS_BLOCKED_NETWORKS ranges. On Linux and inside containers, connecting to 0.0.0.0 routes to local services on any interface (a common SSRF technique against Docker / orchestrator agents on 0.0.0.0:PORT).

  4. DNS rebinding ribbon is only one IP deep. _resolve_hostname records the first getaddrinfo result set, but a hostname with mixed records can still serve a private IP on…

Record
PYSEC-2026-3659
CVSS 4.0
8.6 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
compliance-trestle
PyPI
before 4.1.0
4.1.0
compliance-trestle
PyPI
before 4.1.0, plus listed versions
0.0.20.0.30.1.00.1.10.10.0
Show 45 moreShow less
0.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
4.1.0

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-52776 is a high vulnerability (CVSS 8.6) in Oscal-compass Compliance-trestle, published August 12, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.44%, the 38th percentile. Fixed in compliance-trestle 4.1.0.

References

This page doesn't list everything

Left out here: 54 versions and the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open