fix(fetch): close NAT64/6to4 SSRF-guard bypass in isPrivateIPv6#119
Open
dmchaledev wants to merge 1 commit into
Open
fix(fetch): close NAT64/6to4 SSRF-guard bypass in isPrivateIPv6#119dmchaledev wants to merge 1 commit into
dmchaledev wants to merge 1 commit into
Conversation
isPrivateIPv6 only pattern-matched a handful of literal IPv6 prefixes, so an address under the NAT64 well-known prefix (64:ff9b::/96, RFC 6052) or the 6to4 prefix (2002::/16) that embeds a private/metadata IPv4 (e.g. 64:ff9b::a9fe:a9fe for 169.254.169.254) passed through as "not private" — even though the plain IPv4 form of the same host is already blocked. NAT64/464XLAT is the default IPv6-only mode on several cellular carriers and some cloud node pools, so this is reachable in real deployments of the "scan a customer-supplied URL" use case this library advertises. Replaces the fixed-shape regex approach with a general IPv6-to-bytes parser so RFC 5952 zero-run compression — which can swallow zero hextets from *within* the embedded address, not just the routing prefix — doesn't produce a form a rigid pattern would miss. Fixes #118
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) only pattern-matched a handful of literal IPv6 prefixes (::,::1,fe80:,fc/fd,::ffff:a.b.c.d). It did not recognize the NAT64 well-known prefix64:ff9b::/96(RFC 6052) or the 6to4 prefix2002::/16, both of which embed a literal IPv4 address in the low bits.An address like
64:ff9b::a9fe:a9feembeds169.254.169.254(the cloud metadata endpoint) — a host this library already blocks in its plain IPv4 form viaisPrivateIPv4. On a NAT64/464XLAT network (the default IPv6-only mode on several cellular carriers and some cloud node pools this library's stated ASM/scanning use case may run under), that literal is transparently routed to the real private/metadata IPv4 by the network stack, bypassing the SSRF guard entirely.assertPublicUrl→isPrivateOrReservedIP→isPrivateIPv6is the only place this decision is made for IPv6 results ofdns.lookup(), so this closes the gap at its single point of control.checkCSP/rules.tsand the rest of the analyzer are unaffected — this is scoped entirely to the private-IP guard infetch.ts.Approach
Rather than extending the prefix-matching with more fixed-shape regexes (which the original issue flagged as risky — RFC 5952 zero-run compression can swallow zero hextets from within the embedded IPv4, not just from the routing prefix, so a rigid
64:ff9b::(hex):(hex)pattern would miss e.g.64:ff9b::101for0.0.1.1), this adds a small general-purposeipv6ToBytesparser that expands any single-::-compressed address to its 16 raw bytes, then checks the 96/16-bit prefixes byte-for-byte and recurses the embedded IPv4 into the existingisPrivateIPv4check — mirroring how::ffff:(IPv4-mapped) is already handled.Test plan
npm run typecheck— passesnpm test— all 155 tests pass, including 6 new cases intest/fetch.test.ts:64:ff9b::101→0.0.1.1)8.8.8.8) — confirms no over-blockingnpm run build— passesnpm audit --audit-level=high— 0 vulnerabilitiesGenerated by Claude Code