When your application checks whether an IP address is "internal" or "external," it relies on the parser to speak the same language as the network stack. In ip-address 10.2.0, that assumption broke down: the same logical address could wear two different masks—one IPv4, one IPv6—and slip past defenses designed to stop it.
Affected Versions
| Affected | >= 8.0.0, <= 10.2.0 |
| Fixed in | 10.3.1 |
| Ecosystem | npm |
| CVE / GHSA | CVE-2026-69192 / not assigned |
| CWE | unknown |
The Vulnerability Explained
The ip-address package provides JavaScript implementations of IPv4 and IPv6 address parsing, manipulation, and comparison. Version 10.2.0 introduced—or failed to resolve—a subtle inconsistency in how IPv4-mapped IPv6 addresses are handled.
An IPv4-mapped IPv6 address represents an IPv4 address within the IPv6 address space using the prefix ::ffff:. For example, 127.0.0.1 becomes ::ffff:127.0.0.1 or its full IPv6 form ::ffff:7f00:0001. These are semantically equivalent: they refer to the exact same network endpoint.
The vulnerability arises when applications use ip-address to validate whether a target address is "safe" to connect to. Consider this common SSRF protection pattern:
const { Address4, Address6 } = require('ip-address');
function isInternalIP(ipString) {
let addr;
try {
addr = new Address4(ipString);
} catch {
try {
addr = new Address6(ipString);
} catch {
return false; // Not a valid IP
}
}
// Block loopback, private ranges, etc.
return addr.isLoopback() || addr.isPrivate();
}
// Blocklist check
if (!isInternalIP(userProvidedIP)) {
fetch(`http://${userProvidedIP}/admin`); // "Safe" to request
}
An attacker providing 127.0.0.1 is blocked. But providing ::ffff:127.0.0.1? In 10.2.0, the Address6 parser creates a distinct object whose isLoopback() method may return false or whose comparison against IPv4 loopback ranges fails to match—despite targeting identical infrastructure.
This trust-boundary bypass lets attackers reach:
- Internal admin panels (::ffff:10.0.0.1)
- Metadata services (::ffff:169.254.169.254)
- Database instances (::ffff:192.168.1.100)
The root cause: ip-address 10.2.0 failed to canonicalize IPv4-mapped IPv6 addresses to their IPv4 form before performing semantic comparisons. Two addresses that are network-equivalent were treated as distinct objects.
The Fix
Version 10.3.1 resolves this through stricter normalization. When parsing an IPv4-mapped IPv6 address, the library now converts it to its canonical IPv4 representation before any classification or comparison operations.
The dependency upgrade is enforced across the entire tree using npm's overrides mechanism:
{
"overrides": {
"imapflow": {
"ip-address": "10.3.1"
},
"ip-address": {
"ip-address": "10.3.1"
},
"socks": {
"ip-address": "10.3.1"
}
}
}
This ensures that even transitive dependencies—imapflow for IMAP connections, socks for proxy handling—use the patched version, eliminating the parsing inconsistency wherever IP validation occurs.
The version bump in package-lock.json reflects this coordinated upgrade:
- "version": "10.2.0",
- "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.2.0.tgz",
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz",
Key Takeaways
-
IPv4-mapped IPv6 addresses are not "different" addresses—they're the same endpoint in different clothing. Any IP validation that doesn't canonicalize these forms creates a bypass opportunity.
-
Network-layer equivalence ≠ object equality in your parser. The
ip-addresspackage'sAddress4andAddress6classes being separate types created a semantic gap that attackers could exploit. -
Transitive dependencies need coercion. The
overridesfield inpackage.jsonis essential for forcing security fixes through deeply nested dependency trees where direct control is limited. -
SSRF defenses must normalize before validating. Checking
isLoopback()orisPrivate()on non-canonicalized addresses is insufficient; the normalization step must happen first. -
Dual-stack environments amplify this risk. Applications running on Node.js with IPv6 enabled (the default since Node 17) are more exposed, as the network stack itself will accept and route these mapped addresses.
How Orbis AppSec Detected This
Source: User-influenced input reaching the ip-address parser through HTTP request parameters or configuration values.
Sink: The Address4 and Address6 constructors and their classification methods (isLoopback(), isPrivate(), isInSubnet()) used for SSRF access control decisions.
Missing control: Canonicalization of IPv4-mapped IPv6 addresses to a single comparable form before trust-boundary checks.
CWE: unknown
Fix: Force-upgrade to ip-address 10.3.1 using npm overrides to ensure consistent address normalization across all dependencies.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2026-69192 is a reminder that security boundaries live in the gaps between representations. The ip-address 10.2.0 parser saw 127.0.0.1 and ::ffff:127.0.0.1 as different enough to defeat access controls, while the network stack saw them as identical. The 10.3.1 fix closes this gap by ensuring the parser's worldview matches reality—one canonical form, one truth, no bypass.