You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #1038 fixes an SSRF where a malicious registry redirects the token endpoint (realm in a 401 WWW-Authenticate: Bearer challenge) to an internal address such as the cloud metadata service 169.254.169.254. The fix lives in pkg/distribution/oci/remote/transport.go (resolveAndValidateHost / isDisallowedIP, plus the guarded dialer newGuardedAuthClient).
While it stops the metadata SSRF, the chosen approach is a blocklist of private / loopback / link-local IP ranges and internal hostnames. This rejects any token endpoint that resolves to a private IP — including fully legitimate internal/corporate registries — so pulling models from an internal registry now fails.
Current behavior
resolveAndValidateHost (called both at resolve time and again in newGuardedAuthClient.DialContext) rejects the realm when:
the hostname is in internalHostnames (localhost, host.docker.internal, …), or
the literal IP, or any IP returned by net.LookupHost, falls in private (10/8, 172.16/12, 192.168/16), loopback (127/8, ::1), or link-local (169.254/16, fe80::/64) ranges.
Impact
Internal model registries are a very common enterprise deployment (internal Harbor / Artifactory / HuggingFace mirror, or registry mirrors on RFC1918 networks). For such a registry, the realm in the 401 challenge is typically https://auth.corp.internal/token — or even the registry's own hostname — which resolves to a private IP. After PR #1038 those pulls fail with:
realm URL resolves to a disallowed address 10.x.x.x
…even though the registry is trusted and in the same trust domain. Public clouds (Docker Hub auth.docker.io, ECR, GCR, ACR) resolve to public IPs, so they are unaffected — which is likely why this breakage does not surface in public-registry testing.
Why an IP-range blocklist is the wrong primitive
The real threat is a cross-trust-domain pivot: a registry at host A returns a realm pointing at host B that the client can reach but the registry cannot (cloud metadata, internal admin panels). Whether B's IP is "private" is incidental. A legitimate internal registry at registry.corp.internal returning a realm also on registry.corp.internal (or auth.corp.internal) is same-trust-domain and is not the threat — yet it is blocked purely because the IP is private.
Proposed fix: trust-domain validation instead of an IP-range blocklist
Validate that the realm host stays within the trust domain of the registry being pulled, rather than banning private IPs:
The realm host must equal the registry host being pulled, or be in that registry's configured set of allowed auth hosts (mirror / hosts config). This preserves internal registries and still blocks pivots to other internal services.
Keep a narrow safety net that additionally blocks only the cloud-metadata link-local addresses (169.254.169.254, 169.254.170.2) and the IPv6 metadata range, since those are the high-value SSRF targets and should never be a token endpoint.
Retain the existing DNS-rebinding protection (resolveAndValidateHost re-checked at dial time, dialing the already-resolved IP).
This keeps the SSRF protection — a public evil.com registry returning realm=169.254.169.254 is still rejected, and a registry cannot pivot to an arbitrary other internal host — while allowing internal/corporate registries whose token endpoint is on a private network.
Reproduction (conceptual)
Deploy an internal registry whose token auth endpoint resolves to a private IP (e.g. https://registry.corp.internal returns WWW-Authenticate: Bearer realm=https://registry.corp.internal/auth/token).
Summary
PR #1038 fixes an SSRF where a malicious registry redirects the token endpoint (
realmin a401 WWW-Authenticate: Bearerchallenge) to an internal address such as the cloud metadata service169.254.169.254. The fix lives inpkg/distribution/oci/remote/transport.go(resolveAndValidateHost/isDisallowedIP, plus the guarded dialernewGuardedAuthClient).While it stops the metadata SSRF, the chosen approach is a blocklist of private / loopback / link-local IP ranges and internal hostnames. This rejects any token endpoint that resolves to a private IP — including fully legitimate internal/corporate registries — so pulling models from an internal registry now fails.
Current behavior
resolveAndValidateHost(called both at resolve time and again innewGuardedAuthClient.DialContext) rejects the realm when:internalHostnames(localhost,host.docker.internal, …), ornet.LookupHost, falls in private (10/8,172.16/12,192.168/16), loopback (127/8,::1), or link-local (169.254/16,fe80::/64) ranges.Impact
Internal model registries are a very common enterprise deployment (internal Harbor / Artifactory / HuggingFace mirror, or registry mirrors on RFC1918 networks). For such a registry, the
realmin the401challenge is typicallyhttps://auth.corp.internal/token— or even the registry's own hostname — which resolves to a private IP. After PR #1038 those pulls fail with:…even though the registry is trusted and in the same trust domain. Public clouds (Docker Hub
auth.docker.io, ECR, GCR, ACR) resolve to public IPs, so they are unaffected — which is likely why this breakage does not surface in public-registry testing.Why an IP-range blocklist is the wrong primitive
The real threat is a cross-trust-domain pivot: a registry at host A returns a
realmpointing at host B that the client can reach but the registry cannot (cloud metadata, internal admin panels). Whether B's IP is "private" is incidental. A legitimate internal registry atregistry.corp.internalreturning arealmalso onregistry.corp.internal(orauth.corp.internal) is same-trust-domain and is not the threat — yet it is blocked purely because the IP is private.Proposed fix: trust-domain validation instead of an IP-range blocklist
Validate that the
realmhost stays within the trust domain of the registry being pulled, rather than banning private IPs:realmhost must equal the registry host being pulled, or be in that registry's configured set of allowed auth hosts (mirror / hosts config). This preserves internal registries and still blocks pivots to other internal services.169.254.169.254,169.254.170.2) and the IPv6 metadata range, since those are the high-value SSRF targets and should never be a token endpoint.resolveAndValidateHostre-checked at dial time, dialing the already-resolved IP).This keeps the SSRF protection — a public
evil.comregistry returningrealm=169.254.169.254is still rejected, and a registry cannot pivot to an arbitrary other internal host — while allowing internal/corporate registries whose token endpoint is on a private network.Reproduction (conceptual)
https://registry.corp.internalreturnsWWW-Authenticate: Bearer realm=https://registry.corp.internal/auth/token).realm URL resolves to a disallowed address 10.x.x.x.References
realminWWW-Authenticate