
Node.js October 2026 fixes show a TLS certificate for one IP spelling authenticating a connection to another. We explain the parser differential and how to test your own address handling.
Node.js decides whether a host name is an IP address in more than one place, and the places can disagree. In the October 21, 2026 security release, three CVEs fixed TLS hostname checks where a certificate valid for one spelling of an address authenticated a connection to a different address. One researcher's test showed 0127.0.0.1 read as 87.0.0.1 by one function and as 127.0.0.1 by another.
The question: how can a certificate for the wrong IP pass hostname verification, and what does the pattern teach about every place your code parses addresses?
Background: hostname checks and IP literals
When a TLS client connects to a name, it checks that the certificate lists that name. For DNS names, it matches DNS: subject alternative name (SAN) entries; for IP addresses, it must match IP Address: entries. The check therefore starts by classifying the host: is this an IP or a name? Node.js's tls.checkServerIdentity() does the check, and Node's net.isIP() answers the classification question for canonical forms.
Operating systems accept many non-canonical IPv4 spellings. The Node.js advisory lists examples such as 0127.0.0.1, 0177.0.0.1 and 127.1, and the WHATWG URL parser also normalizes some inputs (fullwidth digits, for instance) into IP addresses. If the verifier and the connector classify the same string differently, the verification can pass for something other than the endpoint actually reached.
How it works: a parser differential
The Node.js advisory describes three issues, all High or Medium, in the 22.x, 24.x and 26.x lines unless noted:
- CVE-2026-106069 (High): leading-zero IPv4 literals such as
0127.0.0.1are read as octal by WHATWG URL rules but as decimal by the OS resolver, so the certificate can be validated against a different IP than the one connected to. - CVE-2026-105483 (High, 24.x and 26.x):
tls.checkServerIdentity()failed to recognize IPs written with fullwidth digits, so a certificate without a matching IP SAN could authenticate an IPv4 connection. The advisory calls this a regression after CVE-2026-48618. - CVE-2026-106072 (Medium): non-canonical spellings such as
0177.0.0.1or127.1let a DNS SAN authenticate an IP endpoint.
The mechanism, per an independent test write-up by lilting.ch: 0127.0.0.1 is not an IP according to net.isIP(), but url.domainToASCII() normalizes it, reading the leading zero as octal and returning 87.0.0.1. Meanwhile dns.lookup() resolves the same string to 127.0.0.1. Before the fix, Node compared the normalized string against DNS: entries. A certificate containing the DNS entry 87.0.0.1 would therefore pass for a connection that actually goes to 127.0.0.1.
host string net.isIP() domainToASCII() dns.lookup()
"0127.0.0.1" 0 (no) "87.0.0.1" 127.0.0.1
| |
| +-- matched against DNS: entries (the bug)
+-- so it is not treated as an IP literal
Deep analysis: what an attacker needs
The lilting.ch article states the prerequisite: a certificate carrying an IP-shaped string in a DNS: entry, issued by a CA that the client trusts. I did not verify whether public CAs would issue such names, and the Node.js fix commit reportedly says the fullwidth case is not easy to exploit. A realistic target is an environment with a private CA or lax issuance, or a client that connects to user-supplied URLs, where a man-in-the-middle position redirects the connection to an address the attacker's certificate claims to cover. The impact is that TLS identity checks, the thing that makes https:// mean something, are bypassed for these hosts.
The fix, as described by that article, rejects any host that only becomes an IP after normalization, before matching, and errors with a "non-canonical IP address" message on v24.22.1. For fullwidth digits, the patched code compares against IP Address: entries instead.
Tested behavior and its limits
The following table is from lilting.ch's tests of tls.checkServerIdentity() called directly with host and SAN pairs from the fix commit's tests. I did not run these myself.
| Host | Certificate SAN | v24.22.0 | v24.22.1 | v25.3.0 (EOL) |
|---|---|---|---|---|
0127.0.0.1 | DNS:87.0.0.1 | Accepted | Rejected | Rejected |
fullwidth 127.0.0.1 | DNS:127.0.0.1 | Accepted | Rejected | Rejected |
127.1 | DNS:127.0.0.1 | Accepted | Rejected | Rejected |
Caveats from the same article: no live TLS connection was made, tests ran on one macOS machine (the octal reading and the resolver result are platform observations), and 22.x and 26.x were not tested even though the advisory lists them.
Comparison: where to normalize
| Strategy | Strength | Weakness |
|---|---|---|
| Reject non-canonical hosts early (the Node.js fix) | No ambiguity reaches matching | May break legacy configs using 127.1 |
| Canonicalize once, then use the result everywhere | One representation for check and connect | Needs every component to use the same function |
| Normalize inside each component | Easy to bolt on | Components can disagree, the cause of these CVEs |
A practical guard before you can upgrade
The lilting.ch article offers this stopgap, which I have not run: refuse hosts that are not IPs by net.isIP() but become IPs after url.domainToASCII().
const net = require("node:net");
const url = require("node:url");
function isNonCanonicalIP(host) {
return !net.isIP(host) && net.isIP(url.domainToASCII(host)) !== 0;
}
// reject before opening a TLS connection or applying an SSRF allowlist
if (isNonCanonicalIP(targetHost)) throw new Error("non-canonical IP host");
The same check is useful for server-side request forgery filters: an allowlist that tests the string you were given, while the resolver connects to the value it computes, has the same class of bug.
Limitations and failure modes
- The guard covers only forms the article exercised; other exotic spellings may exist.
- Upgrading only the Node binary matters; other languages in your stack may parse the same strings differently.
- End-of-life lines such as 25.x are not patched; lilting.ch's tests show v25.3.0 rejecting these cases, but that is a different code path, not evidence of a fix.
- The vulnerability needs unusual certificates, which lowers practical risk but does not remove it for private-PKI environments.
Engineering recommendations
Upgrade to Node.js 22.23.4, 24.22.1 or 26.12.1 (the security releases listed in the advisory). Audit where your code parses addresses: URL validation, SSRF filters, proxy allowlists and TLS pinning each deserve a test using 127.1, 0177.0.0.1, 0x7f.0.0.1, 2130706433 and fullwidth digits. Make the connection use the exact string that was validated, or resolve once and pin the resulting address.
Conclusion
These CVEs are not about a broken algorithm; they are about two correct parsers disagreeing on one string. Next step: add a unit test to your own HTTP client wrapper that feeds the five spellings above through your validation and your connection path, and fail the build if they are treated differently.
Sources
Information current as of October 28, 2026. I read the Node.js advisory page and the lilting.ch tests; I did not run any code, and CVE-2026-106072's mechanism is described only by the one-line advisory summary.
- Node.js, October 2026 security releases: nodejs.org/en/blog/vulnerability/october-2026-security-releases
- lilting.ch, Node.js v24.22.1 fixes four HIGH CVEs, with tests: lilting.ch/en/articles/nodejs-tls-dns-brotli-security-release
- oss-security post on the TLS fixes (search result, not read): openwall.com/lists/oss-security/2026/10/22/8
- Node.js documentation, tls.checkServerIdentity: nodejs.org/api/tls.html#tlscheckserveridentityhostname-cert



