chore(repo): Update dependency ip-address@<10.3.1 to v10.7.1 [SECURITY] - #10060
renovate[bot] wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-biometrics
@clerk/expo-google-signin
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/mosaic
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
This PR contains the following updates:
10.3.1→10.7.1Warning
Some dependencies could not be looked up. Check the Dependency Dashboard for more information.
ip-address: no classifier recognizes the NAT64 local-use range 64:ff9b:1::/48, allowing SSRF and trust-boundary bypass
CVE-2026-101910 / GHSA-2vr4-cq9g-pvrc
More information
Details
Summary
No classifier on
Address6recognizes the NAT64 local-use range64:ff9b:1::/48(RFC 8215).isPrivate(),isLoopback(),isLinkLocal()and their siblings all returnfalsefor every address in it, so an internal IPv4 destination written through a local-use NAT64 prefix (64:ff9b:1:7f00:0:100::for127.0.0.1,64:ff9b:1:a9fe:a9:fe00::for169.254.169.254) reads as an ordinary global address.getType()names the range'NAT64 (local-use)', so the library knows what the address is and classifies it as nothing.An application that builds a network trust-boundary decision on these checks (for example a filter intended to block Server-Side Request Forgery, or SSRF) will classify an internal target as external and allow the request. SSRF is an attack in which a user-supplied address coaxes the server into making a request to an internal destination the user could not otherwise reach, such as a loopback service or a cloud metadata endpoint.
Details
The fix for GHSA-22jq-vg5j-6vgg (10.2.1) classifies IPv4-mapped (
::ffff:0:0/96) and NAT64 well-known (64:ff9b::/96) addresses by their embedded IPv4 address:embeddedIPv4()insrc/ipv6.tsdecodes the trailing 32 bits and every special-use classifier delegates to the resultingAddress4. Those are the only two rangesembeddedIPv4()handles.The local-use range differs in kind from the well-known prefix, which is why extending the decoder does not fix it. RFC 8215 reserves
64:ff9b:1::/48so an operator can carve their own NAT64 prefix out of it, of any length RFC 6052 allows (/48,/56,/64, or/96). Where the IPv4 address sits depends on that choice:127.0.0.1is64:ff9b:1:7f00:0:100::under a/48prefix and64:ff9b:1::7f00:1under a/96prefix, and each of those decodes to a different address under the other prefix length. There is no single embedded IPv4 address for the library to classify by.What is well-defined is the range as a whole. The IANA IPv6 Special-Purpose Address Registry lists
64:ff9b:1::/48as not globally reachable, and Python'sipaddressmodule reportsis_privateasTrueandis_globalasFalsefor every address in it.isPrivate()covered ULA (fc00::/7) plus the decoded mapped and well-known cases, and nothing in the local-use range.Affected versions
>= 10.2.0, <= 10.5.0. Theis*classification API was extended toAddress6in 10.2.0; releases before it do not expose the method and are not affected through this vector.Impact
Every address in
64:ff9b:1::/48parses successfully,isValid()istrue, and no classifier catches it. Which internal target is reached depends on the NAT64 prefix deployed on the server's network; the well-known prefix column is the patched control from GHSA-22jq-vg5j-6vgg./48prefix)127.0.0.164:ff9b::7f00:164:ff9b:1:7f00:0:100::10.0.0.164:ff9b::a00:164:ff9b:1:a00:0:100::169.254.169.25464:ff9b::a9fe:a9fe64:ff9b:1:a9fe:a9:fe00::192.168.1.164:ff9b::c0a8:10164:ff9b:1:c0a8:1:100::Reachability
Reaching an internal host through one of these addresses requires that the server's network run a NAT64 translator on a prefix inside
64:ff9b:1::/48and that the attacker guess or know the prefix length. That is the same precondition the well-known prefix carries (a translator on64:ff9b::/96), with the additional constraint that the prefix is operator-chosen rather than fixed. The severity reflects that precondition.Proof of concept
npm i ip-address@10.5.0, then:On affected versions:
Remediation
Upgrade to the patched release. In the fix,
isPrivate()returnstruefor every address in64:ff9b:1::/48, alongside ULA and the decoded mapped and well-known cases. The range is reported private as a whole rather than by a decoded IPv4 address, for the reason given above;toAddress4Nat64(prefix)remains the way to decode an address under a known deployment prefix.isLoopback()andisLinkLocal()are unchanged for this range, since without the prefix length the library cannot know which IPv4 address is embedded.If you cannot upgrade immediately, test the range directly:
A note on SSRF defense
These methods are address classifiers, not a complete SSRF defense. Regardless of this fix, a robust SSRF guard must resolve the hostname and validate the resolved IP against the socket it connects to, and account for DNS rebinding and redirects. Treat these checks as one layer, not the only one.
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
ip-address: Address6.isLinkLocal() recognizes fe80::/64 rather than fe80::/10, allowing SSRF and trust-boundary bypass to on-link hosts
CVE-2026-101913 / GHSA-rpw4-54j3-4h4q
More information
Details
Summary
Address6.isLinkLocal()recognizesfe80::/64rather thanfe80::/10. Link-local unicast is the whole/10under RFC 4291 §2.4 and the IANA IPv6 Special-Purpose Address Registry, so the method returnsfalsefor every link-local address outside the one/64that stateless address autoconfiguration happens to use.new Address6('fe81::1').isLinkLocal()isfalse.The library contradicts itself on the same object: for
fe81::1,getType()returns'Link-local unicast',getScope()returns'Link local', andisHostInSubnet(new Address6('fe80::/10'))returnstrue, whileisLinkLocal()returnsfalse.An application that builds a network trust-boundary decision on these checks (for example a filter intended to block Server-Side Request Forgery, or SSRF) will classify a link-local target as unremarkable and allow the request. SSRF is an attack in which a user-supplied address coaxes the server into making a request to an internal destination the user could not otherwise reach.
Details
isLinkLocal()insrc/ipv6.tscompares the first 64 bits of the address against a literal string:The comparison requires the first 64 bits to be exactly
fe80:0000:0000:0000, so it accepts 2⁶⁴ of the 2¹¹⁸ addresses infe80::/10. The comment states a premise the library disproves:getType()classifies the same range withisHostInSubnetagainst the'fe80::/10': 'Link-local unicast'entry insrc/v6/constants.ts, andAddress4.isLinkLocal()is a plainisHostInSubnettest against169.254.0.0/16. RFC 4291 §2.5.6 constrains the format of an autoconfigured link-local address; it does not define the range, and reading it as the definition is the likeliest origin of the/64comparison.The IPv4-mapped and NAT64 well-known paths of
isLinkLocal()are unaffected:::ffff:169.254.169.254and64:ff9b::a9fe:a9feare classified by their embedded IPv4 address and reporttrue.Affected versions
<= 10.5.0. The comparison has had this shape sinceAddress6.isLinkLocal()was introduced, so every release exposing the method is affected.Impact
Every well-formed address in
fe80::/10outsidefe80::/64is parsed successfully,isValid()istrue, and the classifier reports something untrue about it. No other classifier catches these addresses:isPrivate()covers ULA (fc00::/7), not link-local.isLinkLocal()getType()getScope()fe80::1truefe81::1falsefe8f::1falsefebf::1falsefe80:0:0:1::1falsefe80::1:0:0:0:1falsePython's
ipaddressmodule, theIN6_IS_ADDR_LINKLOCALmacro innetinet6/in6.h, and Linux'sipv6_addr_type()all apply a ten-bit prefix test and classify every row above as link-local.A request admitted through a guard built on
isLinkLocal()reaches a link-local host on the server's own segment: a neighboring machine or the on-link router. An IPv6 link-local destination generally needs a zone index and a neighbor on the same link, so the reach is the server's own segment rather than the internet or a universal metadata endpoint, and the severity reflects that.Proof of concept
npm i ip-address@10.5.0, then:On affected versions:
Remediation
Upgrade to the patched release. In the fix,
isLinkLocal()tests the address againstfe80::/10with the sameisHostInSubnetpredicategetType()andAddress4.isLinkLocal()use. The same release adds2001::/32to the type table sogetType()reports'Teredo'for the addressesisTeredo()returnstruefor; that is a consistency correction with no security effect.If you cannot upgrade immediately, test the range directly:
A note on SSRF defense
These methods are address classifiers, not a complete SSRF defense. Regardless of this fix, a robust SSRF guard must resolve the hostname and validate the resolved IP against the socket it connects to, and account for DNS rebinding and redirects. Treat these checks as one layer, not the only one.
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
ip-address: Address6 builds a parse diagnostic proportional to the input with no length bound, allowing a single long string to stall or crash the process
CVE-2026-101911 / GHSA-h3mg-xc3c-68pw
More information
Details
Summary
new Address6()andAddress6.isValid()place no bound on the length of the string they parse. When the string contains a character that cannot appear in an IPv6 address, the parser builds a diagnostic that wraps every such character in a 34-byte<span class="parse-error">, so the work and the memory scale with the input rather than with an address. A 1 MiB string of!costs about 110 MB and 70 ms of synchronous work, 8 MiB costs about 800 MB and half a second, 16 MiB throws aRangeErrorin place of the documentedAddressError, and 32 MiB aborts the Node process.isValid()builds the diagnostic and discards it, so a caller that only asks whether a string is valid pays the full price.An application that validates an attacker-supplied string with these methods can be stalled or crashed by a single oversized request. This is a crash on input that should have been rejected cleanly, which SECURITY.md lists as in scope.
Details
parse()insrc/ipv6.tschecks for characters outside[0-9a-f:/%]and, on finding any, throws anAddressErrorwhoseparseMessageis the whole input with each offending character wrapped:RE_BAD_CHARACTERSis/([^0-9a-f:/%])/gi. For an input made entirely of punctuation thematchallocates one string per character, thejoincopies them all into the message, and thereplaceproduces a string 34 times the input. Nothing before this point looks at the length: the constructor strips a CIDR suffix and a zone identifier and hands the rest toparse(). TheRE_BAD_ADDRESSbranch below it builds its diagnostic the same way at about a third of the ratio, for input in the hex-and-colon alphabet such asfffff:repeated.parse()is reached only through the constructor, so every entry point that constructs anAddress6from a string it has not already bounded is affected:new Address6(),isValid(), both arguments offromAddressAndMask()andfromAddressAndWildcardMask(),fromWildcard(), and the prefix argument offromAddress4Nat64()andtoAddress4Nat64().fromURL()admits only hex, colons, and dots in a host, so it reaches theRE_BAD_ADDRESSbranch and not the other.fromArpa()caps its input at 32 nibbles before constructing anything, andfromBigInt(),fromByteArray(), andfromAddress4()build the string themselves, so those are unaffected.The
replaceis where size becomes fatal. V8 caps a string at 2^29 - 24 characters, so past 16 MiB of input thereplacethrowsRangeError: Invalid string lengthrather than theAddressErrorcallers catch;isValid()swallows it, but a constructor call guarded byinstanceof AddressErrordoes not. Past 32 MiB the replacement builder's internal array exceeds its maximum size and V8 aborts the process withFatal JavaScript invalid size error, which notry/catchintercepts.Affected versions
<= 10.7.0. The diagnostic has had this shape since the parser was written, so every release is affected.Impact
Address6.isValid()on N bytes of!, measured on node 24.19.0:falsefalsefalseRangeError: Invalid string lengthfrom the constructor;isValid()returnsfalseThe parse is synchronous, so the event loop is blocked for the whole of the wall time and nothing else on that process is served. The heap is transient and is reclaimed after the call returns, so memory does not accumulate across requests; the abort at 32 MiB is a single request.
Address4has no diagnostic of this shape and parses the same 8 MiB in a few milliseconds.Reachability
Reaching the sizes above requires that the application hand the parser a string it has not already bounded. A host taken from a URL or an HTTP header is bounded by the server's header limit (Node's default is 16 KB), and at that size the cost is a millisecond. The megabyte sizes need a request body the application accepts at that scale and passes through unchecked. Common body parsers default to between 100 KB and 1 MB, which caps the effect at a stall of under 100 ms per request, and the abort needs 32 MiB in a single field, which no default admits. The severity is scored for the stall, not the abort: an application that accepts 32 MiB bodies into an address parser is the exception, and the reader running one should treat this as a crash.
Proof of concept
npm i ip-address@10.7.0, then:On affected versions (node 24.19.0):
The last line is V8 terminating the process; the script does not reach its end.
Remediation
Upgrade to the patched release. In the fix, the constructor rejects an address longer than the family allows before
parse()runs: 45 characters for IPv6 once the CIDR suffix and zone identifier are stripped (six four-digit groups, six colons, and a dotted quad, the same line CPython'sipaddressmodule draws), and 15 characters for IPv4. The rejection is anAddressErrorwith noparseMessage, soisValid()returnsfalsefor the cost of a length comparison and the 32 MiB input above is rejected in the same time as a 46-character one. A zone identifier is not counted, since it never reaches the diagnostic.This rejects nothing a previous release accepted: every string longer than the limit already failed to parse.
If you cannot upgrade immediately, reject a candidate longer than an address with a zone identifier can be before you parse it:
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
ip-address: isInSubnet() and isHostInSubnet() compare addresses of different families as if they shared an address space, allowing an allowlist check to admit an address outside its range
CVE-2026-101912 / GHSA-j6r3-76f7-8jcv
More information
Details
Summary
isInSubnet()andisHostInSubnet()accept an address of either family and compare masked binary strings without checking that both operands are the same family.Address4pads to 32 bits andAddress6to 128, so whenever the leading bits agree the strings are equal:new Address6('a00::1').isInSubnet(new Address4('10.0.0.0/8'))istrue, andnew Address4('32.0.0.1').isInSubnet(new Address6('2000::/3'))istrue. No IPv4 address is inside an IPv6 network, so both answers are untrue.An application that parses untrusted input as whichever family accepts it and then tests the result against a fixed-family allowlist can admit an address outside the list.
Details
Both methods are in
src/common.ts:mask(n)returns the firstnbits of the address as a string of0and1, taken from a representation padded to the family's width.new Address6('a00::1').mask(8)is'00001010', and so isnew Address4('10.0.0.0/8').mask(), so string equality reports containment. The signature admits either family on either side, so TypeScript raises nothing, and thev4property onAddress6marks IPv4 notation (::ffff:10.0.0.1) rather than family, so it does not discriminate either.Which cross-family pairs coincide depends on the network's prefix length.
mask(n)on anAddress4returns at most 32 bits, so an IPv6 network longer than/32never matches an IPv4 address, and an IPv6 network of/32or shorter matches exactly the IPv4 addresses whose leading bits equal its prefix. An IPv4 network is at most 32 bits and matches every IPv6 address whose leading bits equal its prefix.Affected versions
<= 10.7.0. The comparison has had this shape since the methods were written, so every release is affected.Impact
new Address6('a00::1').isInSubnet(new Address4('10.0.0.0/8'))true00001010new Address4('10.0.0.1').isInSubnet(new Address6('a00::/8'))truenew Address4('32.0.0.1').isInSubnet(new Address6('2000::/3'))true001new Address4('32.1.13.184').isInSubnet(new Address6('2001:db8::/32'))true/32spells an IPv4 addressnew Address6('cb00:7100::1').isInSubnet(new Address4('203.0.113.0/24'))true/24spells an IPv6 prefixnew Address4('10.0.0.1').isInSubnet(new Address6('::ffff:10.0.0.0/104'))false/104is longer than 32 bitsnew Address4('8.8.8.8').isInSubnet(new Address4('10.0.0.0/8'))falseIn the allowlist direction the check admits an address outside the list; in the denylist direction it blocks an address outside the list. What the coincidence can admit is narrow. An IPv6 allowlist of
/32or shorter admits the IPv4 addresses its prefix spells:2001:db8::/32admits exactly32.1.13.184, and2000::/3admits32.0.0.0/3. Those are public IPv4 addresses; the private, loopback, and link-local ranges begin with bit patterns no allocated IPv6 prefix shares. An IPv4 allowlist admits the IPv6 addresses its prefix spells, and those all sit in blocks IANA has not allocated. So a request admitted through this defect reaches an address outside the intended list, not an internal host, and the severity reflects that.Proof of concept
npm i ip-address@10.7.0, then:On affected versions:
The IPv6 rows are right. The IPv4 address whose 32 bits equal the allowlist's prefix is admitted; the one next to it is not.
Remediation
Upgrade to the patched release. In the fix,
isHostInSubnet()returnsfalsewhen the two addresses are of different families, andisInSubnet()inherits the answer. The methods keep accepting either family so existing call sites compile. A caller that means to compare across families converts first, withAddress6.fromAddress4(),to4(), ortoAddress4Nat64():new Address6('::ffff:10.0.0.1').to4().isInSubnet(new Address4('10.0.0.0/8'))istrue, as before.If you cannot upgrade immediately, compare the classes before you compare the addresses:
A note on SSRF defense
These methods are address classifiers, not a complete SSRF defense. Regardless of this fix, a robust SSRF guard must resolve the hostname and validate the resolved IP against the socket it connects to, and account for DNS rebinding and redirects. Treat these checks as one layer, not the only one.
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
beaugunderson/ip-address (ip-address@<10.3.1)
v10.7.1Compare Source
What's Changed
Full Changelog: beaugunderson/ip-address@v10.7.0...v10.7.1
v10.7.0Compare Source
What's Changed
Full Changelog: beaugunderson/ip-address@v10.6.0...v10.7.0
v10.6.0Compare Source
What's Changed
Full Changelog: beaugunderson/ip-address@v10.5.1...v10.6.0
v10.5.1Compare Source
v10.5.0Compare Source
What's Changed
Full Changelog: beaugunderson/ip-address@v10.4.0...v10.5.0
v10.4.0Compare Source
What's Changed
Full Changelog: beaugunderson/ip-address@v10.3.1...v10.4.0
Configuration
📅 Schedule: (in timezone GMT)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.