Node.js Brotli and TLS Flaws: What to Patch
Node.js fixed ten flaws on 21 October, including a Brotli bug reachable through fetch() and Express 5. Who's exposed, what to update, and the eMudhra CA block.
On 21 October, the same day PHP shipped its emergency PHP2Shell fixes, the Node.js project released Node.js 26.12.1, 24.22.1 and 22.23.4 to fix ten vulnerabilities across all three supported release lines (Node.js). Four are rated high. The one that matters most is a bug in the Brotli decompressor that Node ships, which Node says permits remote code execution and which ordinary code reaches without trying: fetch() decompresses Brotli responses automatically, and Express 5 does the same for request bodies.
This is not PHP2Shell. Nobody has reported attacks, and the public proof of concept that appeared on 22 October only ran code on a deliberately weakened build of Node. But a bug that crashes your server on demand is already a problem, and the gap between "crashes it" and "runs code" is the kind attackers work to close. The same release also blocks eight compromised keys belonging to the Indian certificate authority eMudhra, which matters more for Node than for most software because Node carries its own list of trusted certificate authorities.
Below: what was fixed, who is actually exposed to each bug, and what to do. If you also run PHP, our PHP2Shell post covers the more urgent of the two releases.
What was fixed#
The fixed versions are Node.js 26.12.1, 24.22.1 and 22.23.4. Node 20 and Node 25 have reached end of life and will not get a fix (Node.js).
| CVE | What it is | Severity | Lines affected |
|---|---|---|---|
| CVE-2026-111497 | Out-of-bounds write in the Brotli decoder; Node says it permits remote code execution | High | 22, 24, 26 |
| CVE-2026-106069 | TLS hostname check bypassed by hosts that only become IP addresses after normalisation | High | 22, 24, 26 |
| CVE-2026-105483 | TLS hostname check bypassed by IP addresses written in full-width digits | High | 24, 26 |
| CVE-2026-105482 | dns.resolve truncates names at a NUL character |
High | 22, 24, 26 |
| CVE-2026-105481 | Keep-alive connections can accept a response sent before the request | Medium | 22, 24, 26 |
| CVE-2026-106072 | Non-canonical IPv4 hostnames let DNS names in certificates vouch for IP addresses | Medium | 22, 24, 26 |
| CVE-2026-106074 | crypto.randomUUID() and randomInt() repeat values in startup snapshots and single-executable apps |
Medium | 22, 24, 26 |
| CVE-2026-106070 | ZipFile returns attacker-controlled bytes after a failed header check | Low | 26 |
| CVE-2026-106071 | Permission Model wildcard lookup over-grants filesystem access | Low | 26 |
| CVE-2026-106073 | Permission Model bypass through fs.realpathSync |
Low | 22, 24, 26 |
The release also updates the libraries Node bundles: undici (the HTTP client behind fetch()), c-ares (DNS), OpenSSL on the 22 and 24 lines and SQLite on 22, plus Node's built-in list of trusted certificate authorities.
The Brotli bug: the one to care about#
Brotli is the compression format many websites use to shrink what they send. Node bundles its own copy of Google's Brotli library, and the bug is in the decompressor: malformed compressed data makes it write past the end of a buffer. Writing outside a buffer is the classic way a memory bug becomes code execution, which is why Node rates it high and describes it as permitting remote code execution. The fix is an upstream Brotli patch, also released in Brotli 1.2.1, 1.1.1 and 1.0.10 on 21 October, so other software that bundles Brotli may need the same update (The Hacker News).
Node only decompresses Brotli when something asks it to. As Vercel puts it, your application is exposed "when its code, or a library it uses, decompresses Brotli data an attacker controls" (Vercel). In practice that covers a lot of code:
fetch(). Node's built-infetch()decompresses Brotli responses automatically, so any server your code fetches from can send a malicious one: webhook targets, link previews, scrapers, image proxies, or a third-party API that has itself been compromised. An independent test found that asking for an uncompressed response doesn't help, because the server can send Brotli anyway (lilting.ch).- Express 5. Its body parsers decompress compressed request bodies, Brotli included, by default (Express documentation). A public Express 5 endpoint therefore decompresses Brotli from anyone who sends a request, no login needed (HOL).
- Direct
zlibcalls, such aszlib.brotliDecompress()on uploaded files or on data pulled from a queue.
Code that only compresses with Brotli isn't an entry point, and JavaScript running in a browser uses the browser's own decoder, not Node's.
Exploitation: none reported. A public proof of concept appeared on 22 October. It achieved code execution only on a deliberately weakened test build; on a normal build it crashed Node and read a little of the process's memory, and patched versions reject its input.
If you can't upgrade today: Node offers no workaround. For inbound traffic you can refuse Brotli until you do: reject requests with Content-Encoding: br at your proxy or load balancer, or set inflate: false on Express's body parsers, which makes them reject compressed bodies outright. Vercel deployed firewall rules on 21 October that block the inbound attack for every project on its platform, but they don't cover responses your code fetches from other servers. There is no simple switch for fetch(); outbound requests to servers you don't control stay exposed until you upgrade.
Who's exposed: Node 22, 24 and 26 apps, and anything on an end-of-life line such as Node 20 or 25, that call fetch() against servers they don't control, run Express 5 on a public endpoint, or decompress Brotli from users. That describes a lot of production Node code.
The TLS and DNS bugs#
Three more high-rated bugs weaken checks that code relies on to decide who it is talking to.
Two TLS hostname bypasses#
When Node connects over TLS, it checks that the server's certificate matches the hostname it asked for. Both bugs involve IP addresses written in unusual ways: shortened forms like 127.1, a leading zero that some code reads as octal, or digits typed in full-width characters. Node could check the certificate against one address while actually connecting to another. In one published test, a certificate for 87.0.0.1 was accepted for a connection that really went to 127.0.0.1. Node now rejects any host that only becomes an IP address after normalisation, and the full-width variant (CVE-2026-105483) was a regression that only affected the 24 and 26 lines.
Exploiting either needs a certificate whose DNS name looks like an IP address, issued by an authority the client trusts. Public certificate authorities shouldn't issue those, and the commit fixing the full-width variant calls it "not easy to exploit". The realistic risk sits with internal certificate authorities and with code that connects to hosts named in user input.
dns.resolve stopping at a NUL character#
Node's dns.resolve*() functions hand names to the c-ares library, which stops reading at a NUL character. So a name like example.com\0.trusted.corp passes a check that it ends in .trusted.corp, then resolves example.com. That is a common pattern in allowlists meant to stop server-side request forgery, where an attacker tricks your server into fetching internal addresses for them. Node now rejects names containing NUL. If you can't upgrade today, reject any hostname from user input that contains \0 before resolving it.
Who's exposed: apps that make TLS connections or DNS lookups to hostnames that come from outside input, such as webhook senders, link previewers and URL allowlists, and services inside networks that run a private certificate authority.
The rest of the release#
- Keep-alive response confusion (CVE-2026-105481, medium). Node's HTTP client reuses idle connections. A server could send a response on one before Node had sent its next request, and Node would treat it as the answer to that request. This matters if your app talks to servers that might be hostile or compromised.
- Repeated random values (CVE-2026-106074, medium).
crypto.randomUUID()andcrypto.randomInt()keep a cache of random bytes. In a startup snapshot or a single-executable application, that cache was saved into the build, so every process started from it produced the same "random" values. If you ship either and those values protect anything, such as session IDs, tokens or file names, rebuild with the fixed Node and replace what was generated. - Permission Model bypasses and ZipFile (low). These matter only if you run Node with
--permissionas a security boundary, or use Node 26's ZipFile API. - Bundled libraries. The undici, c-ares, OpenSSL and SQLite updates fix publicly known issues in those libraries, including three moderate-rated undici advisories. If your app also installs the
undicipackage from npm, update that separately: upgrading Node doesn't touch yournode_modules. It is the same dependency discipline we describe in our guide to securing AI-built apps.
The eMudhra certificate block#
On 17 October, browsers started blocking eight intermediate certificates belonging to eMudhra, an Indian certificate authority. Their private keys hadn't been stolen. Google's security teams found they had been "generated with too little randomness", so anyone could compute them from the public certificates (The Hacker News). Six of the eight could sign website certificates, which means anyone holding the keys could have issued a certificate for any website that software trusting eMudhra would accept. No misuse has been confirmed, and nobody can rule it out.
Browsers and operating systems blocked the keys within days. Node is different: unless you have configured it otherwise, it doesn't use the operating system's list of trusted authorities but its own built-in one, so an operating system update doesn't protect a Node app. The 21 October releases reject any certificate chain that contains one of the eight keys and remove three emSign roots from Node's built-in list. End-of-life versions don't get the block, and connections opened before you upgraded aren't checked again until they close, so restart long-running processes.
Who's exposed: any Node app that makes outgoing HTTPS connections, which is nearly all of them. The risk is an attacker who can intercept your traffic presenting a certificate signed with one of the compromised keys.
What to do now#
1) Find every Node.js you run#
Servers and containers are the obvious places. Also check serverless functions, CI runners and build images, and software that bundles its own Node: Electron desktop apps, command-line tools shipped as single executables, and vendor products built on Node. On each one:
node -v
2) Upgrade to 22.23.4, 24.22.1 or 26.12.1#
- Official builds and version managers: install the new patch release for your line, for example
nvm install 24.22.1. - Containers: pull the new
nodeimage tag, rebuild your images withdocker build --pull, and recreate the containers. A running container never updates itself. - Linux distribution packages: check your vendor's advisory. Distribution Node packages often lag the upstream releases.
- Managed platforms: Vercel says it is rolling the patched versions out across its managed Node 22 and 24 runtimes, and that projects still on Node 20 must switch to 22 or 24 and redeploy. On another platform, check its status page rather than assuming.
3) Restart, and rebuild what embeds Node#
Restart every long-running Node process so it runs the new binary and makes fresh TLS connections. Rebuild single-executable apps and startup snapshots with the new version, since they are tied to the Node binary they were built with. Electron apps get the fix only when their vendor ships an Electron update.
4) If you're on Node 20 or 25#
Both lines are past end of life, and Node's own policy is blunt: users on those lines who run the affected code "will be immediately vulnerable to attacks exploiting these disclosed vulnerabilities". Move to Node 22 or 24. Commercial extended-support offerings exist for old lines, but Node itself describes them as "a temporary solution".
Summary#
- Upgrade to Node.js 22.23.4, 24.22.1 or 26.12.1, and restart every Node process.
- The Brotli bug is the priority. It is reachable through
fetch(), Express 5 body parsing and directzlibuse. If you can't upgrade today, refuse Brotli request bodies (Content-Encoding: br) at the proxy or setinflate: falseon Express's body parsers; nothing equivalent protectsfetch(). - Rebuild what carries its own Node: containers, single-executable apps and startup snapshots. Update the npm
undicipackage if you depend on it directly. - On Node 20 or 25, move to a supported line. No fix is coming.
- The upgrade also blocks the compromised eMudhra keys. Operating system updates don't cover Node's built-in certificate list.
- If your code resolves or connects to hostnames from user input, the TLS and DNS fixes matter too. Reject NUL characters in hostnames.
Not sure which of your services run Node.js, or what they decompress and connect to? Get in touch - bluwarden can help you inventory your internet-facing services, check what is actually patched, and review the code paths these bugs reach. See what our services cover.
Published on 22 October 2026, based on the Node.js project's 21 October advisory and public reporting up to that date. Node's advisory gives few technical details, so some descriptions above come from independent testing and vendor analysis, and details may change.