L
o
a
d
i
n
g
.
.
.

security

Node.js October security release: Brotli, hostname checks and the eMudhra bundle

By Michele Zonca

#security

#nodejs

#tls

23 October 2026

6 minutes to read

October 23, 2026

On 21 October the Node.js project released 22.23.4, 24.22.1 and 26.12.1, which fix ten CVEs. I have not reproduced any of them. What follows is my reading of the project’s advisory, with the one exploit demonstration I know of attributed to the people who published it.

The Brotli decoder

The most serious one is CVE-2026-111497, rated high. According to the advisory, the Brotli decoder bundled in node:zlib treats an invalid block switch as a temporary need for more input instead of a permanent format error. The streaming decoder then keeps appending to an 8-byte buffer, which gives an out-of-bounds write. Depending on the data this can end in a crash, memory disclosure or remote code execution, when an application decompresses untrusted Brotli data.

The condition matters. A server that only compresses responses is not in this path. A client that decompresses what a remote server sends, or a server that accepts Content-Encoding: br on request bodies, is. The advisory lists no workaround, so the practical options are to upgrade or to avoid decompressing untrusted Brotli input until then.

Imperva researchers published a proof of concept called Node2Shell that, as reported by The Hacker News, ran code on the official 24.22.0 Linux arm64 image through this bug. I have not checked it. As of 22 October I found no report of exploitation in the wild and no entry in the CISA catalog.

Three hostname problems

The other three high-severity issues are all about deciding which host a name or an address refers to.

  • CVE-2026-105482: dns.resolve*() and dns.reverse() truncate the hostname at a NUL byte, because it is passed to c-ares as a C string. attacker.example\0.trusted.example resolves attacker.example. This is an incomplete fix of CVE-2026-48930, which had covered dns.lookup() and the net and tls host options.
  • CVE-2026-105483: tls.checkServerIdentity() does not recognize an address written with fullwidth digits, such as 127.0.0.1, as an IPv4 literal. It matches it against DNS SANs and the Common Name instead, so a certificate with DNS:127.0.0.1 can be accepted for that address. The advisory calls it a regression after the fix for CVE-2026-48618.
  • CVE-2026-106069: WHATWG URL parsing reads 0127.0.0.1 as octal, which is 87.0.0.1, while the operating system resolver may read it as decimal, 127.0.0.1. On some platforms, macOS among them, the certificate can be validated for one address while the connection goes to another.

There is also a medium one in the same family, CVE-2026-106072: spellings like 0x7f.0.0.1, 2130706433 or 127.1 are normalized and matched against DNS SANs. That one needs a numeric DNS SAN issued by a trusted CA and a resolver that accepts those forms, such as glibc, so the bar is higher.

What I notice is the pattern. Two of these are described as incomplete fixes or regressions of earlier CVEs, in the same function. Parsing an address is done in at least three places (the URL parser, the TLS check, the system resolver), and each has its own idea of what counts as an address. The bugs live in the gaps between those ideas. Fixing one spelling at a time leaves the others, and I would expect more of these until the check compares addresses after a single canonical parse.

The rest

  • CVE-2026-105481 (medium): the guard that destroys idle keep-alive sockets on unexpected data runs only when the event loop polls for I/O. If unsolicited data has arrived but has not been read when the socket is reused, it can be parsed as the response to the next request. Another incomplete fix, of CVE-2026-48931.
  • CVE-2026-106074 (medium): the entropy caches behind crypto.randomUUID(), crypto.randomUUIDv7() and crypto.randomInt() are saved into --build-snapshot images and single executable applications built with useSnapshot. Every process started from the same image returns the same values until the cache refills, up to 127 UUIDs and about 1,000 randomInt() values.
  • CVE-2026-106070, 106071 and 106073 (low): a ZIP reader that can show a scanner different bytes than another tool extracts (26.x only), and two cases where the Permission Model grants more than it should, through a wildcard prefix and through fs.realpathSync().

The snapshot one is the reason this is not only an upgrade. A binary that already contains the cached state stays affected after the runtime on the machine is updated, so those applications have to be rebuilt with a fixed Node.js version. The advisory does not say this in so many words, it is my inference from how the bug works.

The CA bundle

The release also updates the bundled CA certificates, and the advisory names the reason: the eMudhra intermediate CA compromise. That is the issue I wrote about two days ago. Scott Helme’s test there showed Node reporting authorized: true for a certificate chaining to one of the weak intermediates, and here is the corresponding fix on the Node side. For anyone who relied on Node’s built-in bundle, upgrading is what removes that exposure. If a process uses NODE_EXTRA_CA_CERTS or the system store, the bundle on the machine has to be updated separately.

What I take from it

  • The fixes are in the runtime and in bundled C and C++ code (the Brotli decoder, c-ares, OpenSSL, Undici), so updating an npm package changes nothing. The Node.js binary has to change, and container images have to be rebuilt on top of it.
  • Versions that have reached end of life, 20 and older, are affected and will not get a patch. Anyone still running them has to move to a supported line.
  • Anything that decompresses Brotli from the network deserves a look first, since it is the only item with a plausible remote path to code execution. For the others, the real exposure depends on whether the application connects to addresses taken from untrusted input, which many do not.

Source: Node.js, “Wednesday, October 21, 2026 Security Releases”.