Summary
adcp.signing.resolve_and_validate_host accepts destinations in 100.64.0.0/10. AdCP 3.1.1 lists that range explicitly in the reserved-range deny list a fetcher MUST apply before any outbound fetch to a counterparty-controlled URL.
Spec: dist/docs/3.1.0/building/by-layer/L1/security.mdx, "Webhook URL validation (SSRF)", step 2 — "IPv4: RFC 1918 (...), RFC 6598 CGNAT (100.64.0.0/10), loopback (...)".
Cause
The validator classifies reserved addresses with Python ipaddress flags:
ip.is_private or ip.is_loopback or ip.is_link_local
or ip.is_multicast or ip.is_reserved or ip.is_unspecified
is_private is False across 100.64.0.0/10 — RFC 6598 designates shared address space, not private space, so Python does not classify it. No other flag covers it either.
Reproduce
from unittest.mock import patch
from adcp.signing import resolve_and_validate_host
with patch("adcp.signing.jwks.socket.getaddrinfo",
return_value=[(2, 1, 6, "", ("100.64.0.1", 0))]):
print(resolve_and_validate_host("https://buyer-supplied.example/hook"))
# ("buyer-supplied.example", "100.64.0.1", 443) -- accepted
Impact
CGNAT space is routable inside carrier networks and is commonly used by container/overlay networks, so a buyer-supplied push_notification_config.url or jwks_uri resolving there reaches an operator internal address. It is pre-auth-reachable on the JWKS path.
Partially masked today: Alibaba metadata 100.100.100.200 sits inside this range and is caught by name via BLOCKED_METADATA_IPS. Every other address in the /10 is reachable.
Also affected
192.88.99.0/24 (RFC 7526 deprecated 6to4 relay anycast) falls through the same flag set. Traffic there is tunnelled by whichever relay answers, so the destination is unattributable.
Fix
PR follows: adds an explicit _EXTRA_BLOCKED_NETWORKS tuple for the ranges the flags miss, behind the existing allow_private gate.
Summary
adcp.signing.resolve_and_validate_hostaccepts destinations in100.64.0.0/10. AdCP 3.1.1 lists that range explicitly in the reserved-range deny list a fetcher MUST apply before any outbound fetch to a counterparty-controlled URL.Spec:
dist/docs/3.1.0/building/by-layer/L1/security.mdx, "Webhook URL validation (SSRF)", step 2 — "IPv4: RFC 1918 (...), RFC 6598 CGNAT (100.64.0.0/10), loopback (...)".Cause
The validator classifies reserved addresses with Python
ipaddressflags:is_privateisFalseacross100.64.0.0/10— RFC 6598 designates shared address space, not private space, so Python does not classify it. No other flag covers it either.Reproduce
Impact
CGNAT space is routable inside carrier networks and is commonly used by container/overlay networks, so a buyer-supplied
push_notification_config.urlorjwks_uriresolving there reaches an operator internal address. It is pre-auth-reachable on the JWKS path.Partially masked today: Alibaba metadata
100.100.100.200sits inside this range and is caught by name viaBLOCKED_METADATA_IPS. Every other address in the /10 is reachable.Also affected
192.88.99.0/24(RFC 7526 deprecated 6to4 relay anycast) falls through the same flag set. Traffic there is tunnelled by whichever relay answers, so the destination is unattributable.Fix
PR follows: adds an explicit
_EXTRA_BLOCKED_NETWORKStuple for the ranges the flags miss, behind the existingallow_privategate.