Skip to content

fix(auth): block IPv6 transition addresses in the CIMD SSRF guard - #4784

Open
skoveit wants to merge 1 commit into
mainfrom
cimd-ipv6
Open

fix(auth): block IPv6 transition addresses in the CIMD SSRF guard#4784
skoveit wants to merge 1 commit into
mainfrom
cimd-ipv6

Conversation

@skoveit

@skoveit skoveit commented Aug 7, 2026

Copy link
Copy Markdown
Member

What this change solves

The CIMD (Client ID Metadata Document) SSRF guard only inspected an address's IPv6 bits, so IPv6 transition addresses such as 64:ff9b::a9fe:a9fe passed every net.IP predicate while still routing to 169.254.169.254. This change decodes the IPv4 address embedded in NAT64, IPv4-compatible, IPv4-translated, 6to4, and Teredo addresses and blocks the address if either the outer IPv6 address or the embedded IPv4 address lands in private space.


Change Type

Bug fix


Description

  • Extract a single isBlockedIP(net.IP) bool predicate and use it at all three guard sites: the literal-IP path and the DNS-resolution path of the renamed isBlockedHost, and the DialContext hook in newSafeHTTPClient.
  • Add embeddedIPv4 to decode the IPv4 address wrapped by NAT64 (RFC 6052), IPv4-compatible (RFC 4291), IPv4-translated (RFC 6145), 6to4 (RFC 3056), and Teredo (RFC 4380) addresses; isBlockedIP tests both the outer address and the decoded one.
  • Block 64:ff9b:1::/48 (NAT64 local use, RFC 8215) wholesale, since the embedded IPv4 offset varies with the deployment's prefix length.
  • Widen the blocked predicate set with IsUnspecified and IsMulticast, replacing the narrower IsLinkLocalMulticast.
  • Rename isPrivateOrLoopback to isBlockedHost to reflect that it now covers more than private and loopback ranges.
  • Add TestIsBlockedIP covering each transition format in both blocked and allowed directions, and extend TestIsBlockedHost with a NAT64-wrapped cloud-metadata case.

Diagram Walkthrough

flowchart LR
  A["ResolveCIMDClient"] --> B["ValidateCIMDURL"]
  B -->|"hostname"| C["isBlockedHost"]
  C -->|"literal IP"| D["isBlockedIP"]
  C -->|"LookupIPAddr"| D
  A --> E["FetchCIMDMetadata"]
  E --> F["newSafeHTTPClient<br/>DialContext"]
  F -->|"resolved IPs"| D
  D --> G["embeddedIPv4<br/>NAT64 / 6to4 / Teredo / ::ffff:0:"]
  G --> D
Loading

File Walkthrough

  Relevant files
Bug fix 1 files cimd.goAdds embeddedIPv4 and isBlockedIP, renames isPrivateOrLoopback to isBlockedHost, routes all three guard sites through the shared predicate +67/-10
cimd.goAdds embeddedIPv4 and isBlockedIP, renames isPrivateOrLoopback to isBlockedHost, routes all three guard sites through the shared predicate +67/-10
Tests 1 files cimd_internal_test.goAdds TestIsBlockedIP covering each IPv6 transition format, renames and extends TestIsBlockedHost +116/-3
cimd_internal_test.goAdds TestIsBlockedIP covering each IPv6 transition format, renames and extends TestIsBlockedHost +116/-3

@skoveit
skoveit marked this pull request as ready for review August 7, 2026 19:25
@skoveit skoveit self-assigned this Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant