CVE-2026-101913: ip-address: Address6.isLinkLocal() recognizes fe80::/64 rather than fe80::/10, allowing SSRF and trust-boundary bypass to on-link hosts

Published Sep 28, 2026
·
Updated

Summary

Address6.isLinkLocal() recognizes fe80::/64 rather than fe80::/10. Link-local unicast is the whole /10 under RFC 4291 §2.4 and the IANA IPv6 Special-Purpose Address Registry, so the method returns false for every link-local address outside the one /64 that stateless address autoconfiguration happens to use. new Address6('fe81::1').isLinkLocal() is false.

The library contradicts itself on the same object: for fe81::1, getType() returns 'Link-local unicast', getScope() returns 'Link local', and isHostInSubnet(new Address6('fe80::/10')) returns true, while isLinkLocal() returns false.

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() in src/ipv6.ts compares the first 64 bits of the address against a literal string:

ts // Zeroes are required, i.e. we can't check isHostInSubnet with 'fe80::/10' if ( this.getBitsBase2(0, 64) === '1111111010000000000000000000000000000000000000000000000000000000' ) { return true; }

The comparison requires the first 64 bits to be exactly fe80:0000:0000:0000, so it accepts 2⁶⁴ of the 2¹¹⁸ addresses in fe80::/10. The comment states a premise the library disproves: getType() classifies the same range with isHostInSubnet against the 'fe80::/10': 'Link-local unicast' entry in src/v6/constants.ts, and Address4.isLinkLocal() is a plain isHostInSubnet test against 169.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 /64 comparison.

The IPv4-mapped and NAT64 well-known paths of isLinkLocal() are unaffected: ::ffff:169.254.169.254 and 64:ff9b::a9fe:a9fe are classified by their embedded IPv4 address and report true.

Affected versions

<= 10.5.0. The comparison has had this shape since Address6.isLinkLocal() was introduced, so every release exposing the method is affected.

Impact

Every well-formed address in fe80::/10 outside fe80::/64 is parsed successfully, isValid() is true, and the classifier reports something untrue about it. No other classifier catches these addresses: isPrivate() covers ULA (fc00::/7), not link-local.

| Address | isLinkLocal() | getType() | getScope() | |---|---|---|---| | fe80::1 | true | Link-local unicast | Link local | | fe81::1 | false | Link-local unicast | Link local | | fe8f::1 | false | Link-local unicast | Link local | | febf::1 | false | Link-local unicast | Link local | | fe80:0:0:1::1 | false | Link-local unicast | Link local | | fe80::1:0:0:0:1 | false | Link-local unicast | Link local |

Python's ipaddress module, the IN6ISADDRLINKLOCAL macro in netinet6/in6.h, and Linux's ipv6addrtype() 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:

js const { Address6 } = require('ip-address');

// A guard of the shape the library documents. function isBlocked(host) { const a = new Address6(host); return a.isLoopback() || a.isLinkLocal() || a.isPrivate() || a.isMulticast() || a.isUnspecified(); }

for (const h of ['fe80::1', 'fe81::1', 'febf::1', 'fe80:0:0:1::1']) { const a = new Address6(h); console.log(isBlocked(h) ? 'BLOCK' : 'ALLOW', h, '-> getType()', a.getType()); }

On affected versions:

BLOCK fe80::1 -> getType() Link-local unicast ALLOW fe81::1 -> getType() Link-local unicast ALLOW febf::1 -> getType() Link-local unicast ALLOW fe80:0:0:1::1 -> getType() Link-local unicast

Remediation

Upgrade to the patched release. In the fix, isLinkLocal() tests the address against fe80::/10 with the same isHostInSubnet predicate getType() and Address4.isLinkLocal() use. The same release adds 2001::/32 to the type table so getType() reports 'Teredo' for the addresses isTeredo() returns true for; that is a consistency correction with no security effect.

If you cannot upgrade immediately, test the range directly:

js const LINKLOCAL = new Address6('fe80::/10'); const linkLocal = new Address6(host).isHostInSubnet(LINKLOCAL);

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.

Other sources

ip-address is a library for parsing and manipulating IPv4 and IPv6 addresses in JavaScript. Prior to 10.5.1, the Address6 isLinkLocal method in src/ipv6.ts recognizes only fe80::/64 instead of the complete fe80::/10 IPv6 link-local range. An attacker-controlled address elsewhere in fe80::/10 can therefore pass a trust-boundary check that relies on isLinkLocal. The same address is identified as link-local by getType and getScope, exposing the inconsistent classification. A successful bypass can reach an on-link host outside the intended trust boundary. This issue is fixed in version 10.5.1.

— MITRE

Affected Software

2 affected componentsFixes available
npm/ip-address<10.5.1
npm/ip-address<=10.5.0
10.5.1

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/ip-address to a version that resolves this vulnerability.

    Fixed in 10.5.1
  2. Upgrade

    Upgrade ip-address to a version that resolves this vulnerability.

    Fixed in 10.5.1
  3. Compensating control

    For SSRF defenses, resolve the hostname and validate the resolved IP address against the socket destination, while accounting for DNS rebinding and redirects.

Event History

Sep 28, 2026
CVE Published
via MITRE·05:49 PM
Data Sourced
via MITRE·05:49 PM
DescriptionWeakness
Data Sourced
via NVD·06:17 PM
DescriptionSeverityWeakness
Advisory Published
via GitHub·08:43 PM
Data Sourced
via GitHub·08:43 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

Which applications are exposed to this issue?

Applications using ip-address before 10.5.1 are exposed if they rely on Address6.isLinkLocal() to enforce a trust boundary for IPv6 addresses. The bypass applies to attacker-controlled addresses in the link-local fe80::/10 range that fall outside fe80::/64.

2

What must an attacker be able to do to exploit the flaw?

An attacker must be able to supply or influence an IPv6 address that is evaluated by a trust-boundary check using Address6.isLinkLocal(). A successful bypass can allow access to an on-link host outside the intended boundary.

3

How can I identify affected validation logic?

Look for uses of Address6.isLinkLocal() in code that permits, blocks, or routes requests based on whether an IPv6 address is link-local. Classification inconsistencies are an indicator: getType and getScope identify the full fe80::/10 range as link-local, while the affected isLinkLocal method does not.

4

What is the remediation?

Upgrade ip-address to version 10.5.1, which fixes the incorrect link-local range recognition.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203