fix(fetch): recognize NAT64 and 6to4 IPv6 forms in the SSRF private-IP guard#120
Open
dmchaledev wants to merge 1 commit into
Open
fix(fetch): recognize NAT64 and 6to4 IPv6 forms in the SSRF private-IP guard#120dmchaledev wants to merge 1 commit into
dmchaledev wants to merge 1 commit into
Conversation
…P guard isPrivateIPv6 only matched loopback/link-local/ULA prefixes and the dotted-decimal form of IPv4-mapped addresses, so an attacker-controlled DNS response using the NAT64 Well-Known Prefix (64:ff9b::/96, RFC 6052) or 6to4 (2002::/16) could embed a private/metadata IPv4 address (e.g. 169.254.169.254) and slip past the check on networks that transparently translate those prefixes. Replace the regex prefix-matching with a general IPv6 group expander so embedded-IPv4 forms are checked consistently regardless of textual notation (dotted-decimal or hex). Fixes #118. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ew2xqjd45ft3s8KpVT8Vt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #118.
isPrivateIPv6(src/fetch.ts) previously only recognized a handful of literal string prefixes:::,::1,fe80:,fc/fd, and the dotted-decimal form of IPv4-mapped addresses (::ffff:a.b.c.d). It did not recognize:64:ff9b::/96(RFC 6052), which is transparently translated to the embedded IPv4 address on IPv6-only networks running NAT64/464XLAT (the default IPv6-only mode on several cellular carriers, and a documented migration path for some cloud IPv6-only node pools).2002::/16, which embeds an IPv4 address the same way.::ffff:7f00:1instead of::ffff:127.0.0.1— the old regex only matched the dotted-decimal spelling.Since
assertPublicUrlrelies entirely onisPrivateIPv6to judge whether a resolved IPv6 address is safe to fetch, an attacker who controls DNS for a scan target (the exact untrusted-URL threat model this guard exists for) could return an address like64:ff9b::a9fe:a9fe— which embeds the cloud metadata IP169.254.169.254— and have it sail past the check on an affected network, even though the plain-v4 form of the same host is already correctly blocked.Fix
Rather than adding another one-off regex per prefix, this replaces the ad hoc string matching with a general IPv6 address expander (
expandIPv6) that parses any syntactically valid IPv6 literal (handling::compression and a trailing embedded-IPv4 dotted-decimal suffix) into its 8 16-bit groups.isPrivateIPv6then checks the expanded groups against:::ffff:0:0/96)64:ff9b::/96)2002::/16)and recurses into the existing
isPrivateIPv4check on the embedded address in each case — mirroring how::ffff:was already handled, just generalized. If the address can't be confidently parsed, it now fails closed (true), consistent with the fail-closed behaviorisPrivateOrReservedIPalready uses for unclassifiable addresses.Test plan
test/fetch.test.ts:npm test— 154/154 passingnpm run typecheck— cleannpm run build— cleanGenerated by Claude Code