Context
Spun out of the adversarial review of PR LeanerCloud/cloud-commitments-cli#1231 (SEC-04, hardened HTTP client for JWKS warmup and fetch). The PR moves the existing Azure IMDS-blocking implementation into pkg/httpclient and applies it to the scheduled-task OIDC validator. PR LeanerCloud/cloud-commitments-cli#1231 also picked up a parity fix (canonical/expanded IPv6 form comparison via net.IP.Equal). The gaps below are larger and were carved out as follow-ups.
Gaps in the current block-list
pkg/httpclient.blockIMDSDialer.DialContext only rejects the dial when the literal IP in the URL parses to the AWS IMDS IPv4 or IPv6 endpoint. Several SSRF-adjacent paths are still reachable:
- Loopback (
127.0.0.0/8, ::1): a JWKS URL pointed at http://127.0.0.1:6379/ or http://localhost:8080/ reaches whatever loopback services run on the same host (Redis, Prometheus, sidecar admin endpoints).
- RFC 1918 private space (
10/8, 172.16/12, 192.168/16): intranet pivot.
- All link-local (
169.254.0.0/16): only 169.254.169.254 is blocked. Sibling link-local addresses are also reachable.
- Other IPv6 link-local (
fe80::/10): not blocked.
- Hostname-based SSRF: a URL whose hostname is
metadata.google.internal, instance-data.ec2.internal, etc., or a hostile DNS record that resolves to a metadata IP, bypasses the block-list. The current dialer only inspects the literal host string off net.SplitHostPort and does not look at the resolved IP.
(1) through (4) follow the standard SSRF deny-list pattern.
(5) requires resolving the hostname before the connect, validating the resolved IP against the deny-ranges, and dialing the specific IP (so a DNS-rebinding attack cannot swap the IP between resolution and connect).
Fix sketch
In pkg/httpclient:
- Replace the IMDS list with a set of denied
net.IPNet ranges covering link-local, loopback, RFC 1918, multicast, unspecified, and IPv6 site-local/ULA-metadata.
- Wrap
DialContext so it pre-resolves the host (via net.DefaultResolver.LookupNetIP with the dial ctx), rejects any resolved IP that falls in a denied range, and dials the chosen IP literal (preventing DNS rebinding between resolution and connect).
- Keep a per-call test seam (resolver injection) so the deny-range behavior can be asserted in unit tests without going through real DNS.
Test coverage
- Each denied range gets its own positive test (
127.0.0.1, 127.0.0.2, 10.0.0.1, 192.168.1.1, 169.254.1.1, fe80::1, ::1).
- Each denied range gets a hostname-shaped variant via a stub resolver that returns the denied IP.
- Negative tests for non-denied public IPs and hostnames.
Closing-issue trailer for the fix PR
Closes #<this issue number>
Findings from the 2026-09-02 codebase audit
Added by an automated audit of 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd (tip of origin/main). Each item below was reported by one reviewer and independently confirmed by a second that did not write it. Full report: docs/audits/codebase-audit-2026-09-02.md.
A08b-002 (high)
Two addresses to add to the deny-set in this issue, from finding A08b-002. The ranges listed here (loopback, RFC 1918, 169.254.0.0/16, fe80::/10) do not cover Azure's WireServer / host-agent endpoint 168.63.129.16, which serves instance configuration and extension settings and is a plain routable address, nor Alibaba's 100.100.100.200. http://168.63.129.16/machine?comp=goalstate is reachable today from every Azure service client, all of which carry a managed-identity token. Worth adding both as explicit single-address entries alongside the CIDR set, since a range-only deny-list will not catch them.
Context
Spun out of the adversarial review of PR LeanerCloud/cloud-commitments-cli#1231 (SEC-04, hardened HTTP client for JWKS warmup and fetch). The PR moves the existing Azure IMDS-blocking implementation into
pkg/httpclientand applies it to the scheduled-task OIDC validator. PR LeanerCloud/cloud-commitments-cli#1231 also picked up a parity fix (canonical/expanded IPv6 form comparison vianet.IP.Equal). The gaps below are larger and were carved out as follow-ups.Gaps in the current block-list
pkg/httpclient.blockIMDSDialer.DialContextonly rejects the dial when the literal IP in the URL parses to the AWS IMDS IPv4 or IPv6 endpoint. Several SSRF-adjacent paths are still reachable:127.0.0.0/8,::1): a JWKS URL pointed athttp://127.0.0.1:6379/orhttp://localhost:8080/reaches whatever loopback services run on the same host (Redis, Prometheus, sidecar admin endpoints).10/8,172.16/12,192.168/16): intranet pivot.169.254.0.0/16): only169.254.169.254is blocked. Sibling link-local addresses are also reachable.fe80::/10): not blocked.metadata.google.internal,instance-data.ec2.internal, etc., or a hostile DNS record that resolves to a metadata IP, bypasses the block-list. The current dialer only inspects the literal host string offnet.SplitHostPortand does not look at the resolved IP.(1) through (4) follow the standard SSRF deny-list pattern.
(5) requires resolving the hostname before the connect, validating the resolved IP against the deny-ranges, and dialing the specific IP (so a DNS-rebinding attack cannot swap the IP between resolution and connect).
Fix sketch
In
pkg/httpclient:net.IPNetranges covering link-local, loopback, RFC 1918, multicast, unspecified, and IPv6 site-local/ULA-metadata.DialContextso it pre-resolves the host (vianet.DefaultResolver.LookupNetIPwith the dial ctx), rejects any resolved IP that falls in a denied range, and dials the chosen IP literal (preventing DNS rebinding between resolution and connect).Test coverage
127.0.0.1,127.0.0.2,10.0.0.1,192.168.1.1,169.254.1.1,fe80::1,::1).Closing-issue trailer for the fix PR
Findings from the 2026-09-02 codebase audit
Added by an automated audit of
3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd(tip oforigin/main). Each item below was reported by one reviewer and independently confirmed by a second that did not write it. Full report:docs/audits/codebase-audit-2026-09-02.md.A08b-002 (high)
Two addresses to add to the deny-set in this issue, from finding A08b-002. The ranges listed here (loopback, RFC 1918, 169.254.0.0/16, fe80::/10) do not cover Azure's WireServer / host-agent endpoint
168.63.129.16, which serves instance configuration and extension settings and is a plain routable address, nor Alibaba's100.100.100.200.http://168.63.129.16/machine?comp=goalstateis reachable today from every Azure service client, all of which carry a managed-identity token. Worth adding both as explicit single-address entries alongside the CIDR set, since a range-only deny-list will not catch them.