Commit 9731991b3c8 for nodejs
commit 9731991b3c8c8ea1c917cdec7c1049cbed63d943
Author: James M Snell <jasnell@gmail.com>
Date: Sat Sep 19 20:56:11 2026 +0000
tls: fix ip normalization regression
The regression is a low-severity bug that's not easy to
exploit. Basically it just means the hostname check can
be bypassed.
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs-private/node-private/pull/985
Fixes: https://hackerone.com/reports/4038287
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
CVE-ID: CVE-2026-105483
diff --git a/lib/tls.js b/lib/tls.js
index a1d978c50f2..42562108eaa 100644
--- a/lib/tls.js
+++ b/lib/tls.js
@@ -456,14 +456,26 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
let valid = false;
let reason = 'Unknown reason';
- // An IP literal must not be IDNA-normalized: domainToASCII() returns '' for
- // an IPv6 literal (it is not a domain), so matching against the normalized
- // host would skip IP-SAN matching for IPv6 entirely. Match IP hosts against
- // the original hostname (net.isIP() rejects non-ASCII, so there is no IDNA
- // confusion to guard against here); the normalized form is kept for the
- // DNS-name path below.
+ // Decide whether the host is an IP address by checking both forms of it,
+ // because neither form is enough on its own:
+ //
+ // - The original hostname is needed for IPv6. domainToASCII() returns '' for
+ // an IPv6 literal such as '::1' (it is not a domain).
+ // - The normalized hostname is needed for hosts that net.isIP() rejects but
+ // that normalize to an IPv4 address: '\uFF11\uFF12\uFF17.0.0.1' (fullwidth
+ // digits), '127\u30020.0.1' (ideographic full stop) and '127.1' all become
+ // '127.0.0.1'. The resolver treats them as that address too, so they must
+ // be matched against IP SANs. Letting them fall through to the DNS-name
+ // path would match an IP address against DNS names and the CN instead.
+ let ip = '';
if (net.isIP(hostname)) {
- valid = ips.includes(canonicalizeIP(hostname));
+ ip = hostname;
+ } else if (net.isIP(hostnameASCIIWithoutFQDN)) {
+ ip = hostnameASCIIWithoutFQDN;
+ }
+
+ if (ip) {
+ valid = ips.includes(canonicalizeIP(ip));
if (!valid) {
reason =
`IP: ${hostname} is not in the cert's list: ` + ips.join(', ');
diff --git a/test/parallel/test-tls-check-server-identity.js b/test/parallel/test-tls-check-server-identity.js
index e21f39ccc76..61a2513f81d 100644
--- a/test/parallel/test-tls-check-server-identity.js
+++ b/test/parallel/test-tls-check-server-identity.js
@@ -394,6 +394,47 @@ const tests = [
},
error: 'IP: 127.0.0.1 is not in the cert\'s list: '
},
+ // A host that net.isIP() rejects but that normalizes to an IPv4 address is
+ // an IP address, not a DNS name: it is matched against "IP Address:" SANs,
+ // never against DNS names or the CN.
+ {
+ host: '\uFF11\uFF12\uFF17.0.0.1', // "127.0.0.1" with fullwidth digits.
+ cert: { subjectaltname: 'DNS:127.0.0.1', subject: {} },
+ error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: '
+ },
+ {
+ host: '\uFF11\uFF12\uFF17.0.0.1',
+ cert: { subjectaltname: 'DNS:*.0.0.1', subject: {} },
+ error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: '
+ },
+ {
+ host: '\uFF11\uFF12\uFF17.0.0.1',
+ cert: { subject: { CN: '127.0.0.1' } },
+ error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: '
+ },
+ {
+ host: '\uFF11\uFF12\uFF17.0.0.1',
+ cert: { subjectaltname: 'IP Address:127.0.0.1', subject: {} }
+ },
+ {
+ host: '\uFF11\uFF12\uFF17.0.0.1',
+ cert: { subjectaltname: 'IP Address:127.0.0.2', subject: {} },
+ error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: 127.0.0.2'
+ },
+ {
+ host: '127\u30020.0.1', // "127.0.0.1" with an ideographic full stop.
+ cert: { subjectaltname: 'DNS:127.0.0.1', subject: {} },
+ error: 'IP: 127\u30020.0.1 is not in the cert\'s list: '
+ },
+ {
+ host: '127.1',
+ cert: { subjectaltname: 'DNS:127.0.0.1', subject: {} },
+ error: 'IP: 127.1 is not in the cert\'s list: '
+ },
+ {
+ host: '127.1',
+ cert: { subjectaltname: 'IP Address:127.0.0.1', subject: {} }
+ },
{
host: 'localhost', cert: {
subjectaltname: 'DNS:a.com',