CVE-2026-52776
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.
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
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 High
CVSS: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 High
CVSS: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 bysocket.getaddrinfoasIPv6Addressobjects;IPv6Address in IPv4Network('169.254.0.0/16')returnsFalse, so the_check_blocked_networksand_check_private_networkspredicates do not match. - IPv4 unspecified address
0.0.0.0is not inALWAYS_BLOCKED_NETWORKS(which covers127.0.0.0/8but not0.0.0.0/8); on Linux + Docker,0.0.0.0routes 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:
-
No IPv4-mapped IPv6 normalization.
socket.getaddrinfo('::ffff:169.254.169.254', None)returns anIPv6Address. Python'sipaddressmodule raisesTypeErrorif mixed types are compared, and theinoperator suppresses that toFalse. The validator never calls.ipv4_mappedto canonicalize before the membership check, so any always-blocked IPv4 range is bypassable via the[::ffff:N.N.N.N]literal. -
METADATA_HOSTNAMESis an exact-string set. The hostname forhttps://[::ffff:169.254.169.254]/is::ffff:169.254.169.254, which is not in the set. -
0.0.0.0is not blocked.0.0.0.0is not in any of the fourALWAYS_BLOCKED_NETWORKSranges. On Linux and inside containers, connecting to0.0.0.0routes to local services on any interface (a common SSRF technique against Docker / orchestrator agents on0.0.0.0:PORT). -
DNS rebinding ribbon is only one IP deep.
_resolve_hostnamerecords the firstgetaddrinforesult set, but a hostname with mixed records can still serve a private IP on…
- Record
GHSA-h47f-gmjp-m7rr- CVSS 4.0
- 8.6 High
CVSS: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 High
CVSS: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 High
CVSS: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 bysocket.getaddrinfoasIPv6Addressobjects;IPv6Address in IPv4Network('169.254.0.0/16')returnsFalse, so the_check_blocked_networksand_check_private_networkspredicates do not match. - IPv4 unspecified address
0.0.0.0is not inALWAYS_BLOCKED_NETWORKS(which covers127.0.0.0/8but not0.0.0.0/8); on Linux + Docker,0.0.0.0routes 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:
-
No IPv4-mapped IPv6 normalization.
socket.getaddrinfo('::ffff:169.254.169.254', None)returns anIPv6Address. Python'sipaddressmodule raisesTypeErrorif mixed types are compared, and theinoperator suppresses that toFalse. The validator never calls.ipv4_mappedto canonicalize before the membership check, so any always-blocked IPv4 range is bypassable via the[::ffff:N.N.N.N]literal. -
METADATA_HOSTNAMESis an exact-string set. The hostname forhttps://[::ffff:169.254.169.254]/is::ffff:169.254.169.254, which is not in the set. -
0.0.0.0is not blocked.0.0.0.0is not in any of the fourALWAYS_BLOCKED_NETWORKSranges. On Linux and inside containers, connecting to0.0.0.0routes to local services on any interface (a common SSRF technique against Docker / orchestrator agents on0.0.0.0:PORT). -
DNS rebinding ribbon is only one IP deep.
_resolve_hostnamerecords the firstgetaddrinforesult set, but a hostname with mixed records can still serve a private IP on…
- Record
GHSA-h47f-gmjp-m7rr- CVSS 4.0
- 8.6 High
CVSS: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 bysocket.getaddrinfoasIPv6Addressobjects;IPv6Address in IPv4Network('169.254.0.0/16')returnsFalse, so the_check_blocked_networksand_check_private_networkspredicates do not match. - IPv4 unspecified address
0.0.0.0is not inALWAYS_BLOCKED_NETWORKS(which covers127.0.0.0/8but not0.0.0.0/8); on Linux + Docker,0.0.0.0routes 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:
-
No IPv4-mapped IPv6 normalization.
socket.getaddrinfo('::ffff:169.254.169.254', None)returns anIPv6Address. Python'sipaddressmodule raisesTypeErrorif mixed types are compared, and theinoperator suppresses that toFalse. The validator never calls.ipv4_mappedto canonicalize before the membership check, so any always-blocked IPv4 range is bypassable via the[::ffff:N.N.N.N]literal. -
METADATA_HOSTNAMESis an exact-string set. The hostname forhttps://[::ffff:169.254.169.254]/is::ffff:169.254.169.254, which is not in the set. -
0.0.0.0is not blocked.0.0.0.0is not in any of the fourALWAYS_BLOCKED_NETWORKSranges. On Linux and inside containers, connecting to0.0.0.0routes to local services on any interface (a common SSRF technique against Docker / orchestrator agents on0.0.0.0:PORT). -
DNS rebinding ribbon is only one IP deep.
_resolve_hostnamerecords the firstgetaddrinforesult set, but a hostname with mixed records can still serve a private IP on…
- Record
PYSEC-2026-3659- CVSS 4.0
- 8.6 High
CVSS: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
| Package | Affected | Fixed |
|---|---|---|
compliance-trestlePyPI |
before 4.1.0 | 4.1.0 |
compliance-trestlePyPI |
before 4.1.0, 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 |
4.1.0 |
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-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
github.com/oscal-compass/compliance-trestle/security/advisories/GHSA-h47f-gmjp-m7rr
github.com/oscal-compass/compliance-trestle/commit/5335ff873a2a68eb7de43df029bea09cadff22fd
github.com/oscal-compass/compliance-trestle/commit/d107cd16efe8eb15d46be3c1d97f1ec73d32447c
github.com/advisories/GHSA-h47f-gmjp-m7rr
github.com/CVEProject/cvelistV5/tree/main/cves/2026/52xxx/CVE-2026-52776.json
nvd.nist.gov/vuln/detail/CVE-2026-52776
github.com/oscal-compass/compliance-trestle
pypi.org/project/compliance-trestle
Left out here: 54 versions and the rest of 3 source descriptions. The JSON record has them.