Node.js October 2026 Security Releases: Brotli RCE, TLS Hostname Bypasses and How to Patch
Ten CVEs fixed in Node.js 26.12.1, 24.22.1 and 22.23.4, including a Brotli decoder out-of-bounds write and several TLS hostname-verification bypasses. Who is exposed, how to audit Docker fleets, and what to do about end-of-life lines and dependencies stuck on old Node.
On 21 October 2026 the Node.js project shipped security releases for all three supported lines: v26.12.1, v24.22.1 and v22.23.4. They fix ten vulnerabilities, four rated High, including an out-of-bounds write in the Brotli decoder that can lead to remote code execution and three separate ways to get a TLS certificate accepted for a host it was not issued for. End-of-life lines (20.x, 18.x and older) are affected too and get no fix.
If you run Node.js on a server that talks to the outside world, and especially one that fetches content from third parties, schedule this upgrade now rather than in the next maintenance window.
TL;DR
- What: 10 CVEs. The headline is CVE-2026-111497, a Brotli decoder out-of-bounds write leading to RCE. Three High-severity bugs weaken hostname checks in DNS and TLS, and a Medium one makes
crypto.randomUUID()repeat the same values across processes started from a snapshot or a single executable application. - Who: every Node.js 22, 24 and 26 install, plus every end-of-life install. Some bugs only affect 26 or 26 and 24 (see the table).
- Do now: upgrade to 26.12.1, 24.22.1 or 22.23.4. In containers, a floating tag is not enough: rebuild the image and recreate the container. If you are still on Node 20 or older, there is no patch; move to 22 or 24.
The vulnerabilities
| CVE | Severity | Lines | Issue |
|---|---|---|---|
| CVE-2026-111497 | High | 26, 24, 22 | Brotli decoder out-of-bounds write leading to remote code execution |
| CVE-2026-105482 | High | 26, 24, 22 | dns.resolve truncates names containing a NUL character |
| CVE-2026-105483 | High | 26, 24 | TLS certificate hostname bypass when the IP is written with fullwidth digits |
| CVE-2026-106069 | High | 26, 24, 22 | TLS certificate hostname bypass when the host loosely resolves to an IP address |
| CVE-2026-105481 | Medium | 26, 24, 22 | Keep-alive sockets process a response written before the request was sent |
| CVE-2026-106072 | Medium | 26, 24, 22 | Improper certificate validation for non-canonical IPv4 hostnames (DNS SANs authenticate IP endpoints) |
| CVE-2026-106074 | Medium | 26, 24, 22 | crypto.randomUUID() / randomInt() entropy caches saved into startup snapshots and SEAs, so every process repeats the same values |
| CVE-2026-106070 | Low | 26 | ZipFile returns attacker-controlled prefix bytes after failed header validation |
| CVE-2026-106071 | Low | 26 | Permission Model filesystem over-grant via wildcard radix lookup |
| CVE-2026-106073 | Low | 26, 24, 22 | Permission Model bypass via fs.realpathSync |
The advisory also lists dependency updates: c-ares, ca-cert (removal of trust following the compromise of an eMudhra intermediate CA), OpenSSL (24 and 22), SQLite (22) and Undici (26, 24, 22). It does not publish CVSS vectors or exploit details, and neither does this article.
Why the Brotli bug matters more than it looks
“Decoder bug” sounds like something that only matters if you call zlib.brotliDecompress() yourself. In practice, most Node.js servers decode Brotli without any code asking for it.
The built-in fetch (backed by Undici) and many HTTP clients send Accept-Encoding with br and transparently decompress responses served with Content-Encoding: br. Whoever controls the response controls the compressed bytes that reach the decoder. That puts any server that makes outbound requests to URLs it does not fully trust in scope:
- RSS and feed aggregators, link previewers and unfurlers;
- webhook consumers that follow callbacks, and OAuth or OpenID clients fetching remote metadata;
- scrapers, crawlers, price checkers and monitoring probes;
- API clients talking to third-party services, or to anything a user can point them at.
The usual SSRF defences (allow-lists, blocking internal ranges) limit where your server connects. They do nothing here: a perfectly legitimate public host can serve a malicious body. The fix is the upgrade.
The TLS and DNS hostname bugs
Four of the fixes concern the same question: is this certificate valid for the host I am talking to? Hostnames that are IP addresses written in unusual forms (fullwidth digits, non-canonical IPv4 notations, strings that loosely parse as an IP) could make Node.js compare the certificate against the wrong identity, or let a DNS name in the certificate authenticate an IP endpoint. Separately, dns.resolve silently truncated names containing a NUL byte, so the name that was resolved was not the name that was validated.
These bugs are most relevant when the hostname comes from input: user-supplied URLs, configuration editable by tenants, service discovery records. If your code builds an https:// URL from something an attacker can influence, treat the TLS check as unreliable until you are patched.
The keep-alive bug (CVE-2026-105481) is a related class of problem: on a reused socket, a response the server wrote before the request was sent could be accepted as the answer to that request. Again, the advisory gives no further detail, and the remedy is the update.
Snapshots, SEAs and repeated UUIDs
CVE-2026-106074 deserves a specific check if you use startup snapshots (--build-snapshot) or single executable applications. The entropy cache behind crypto.randomUUID() and crypto.randomInt() could be captured into the snapshot, so every process started from it produced the same sequence of values. If you used those values as identifiers, tokens or nonces, upgrading stops the repetition but does not undo it: review what was generated by snapshot-based binaries before the fix, and rotate anything security-relevant.
Am I affected?
Check every runtime, not just your laptop:
node -v
On servers, look for every Node binary, including those shipped inside tools (nvm installs, vendored runtimes, desktop apps on build agents):
which -a node; ls ~/.nvm/versions/node 2>/dev/null
Docker fleets
Containers are where most outdated Node.js lives. A floating tag such as node:24-alpine already points to the fixed release, but a running container keeps the binary it was built with until the image is rebuilt and the container recreated. Ask every running container directly:
for c in $(docker ps --format '{{.Names}}'); do
v=$(docker exec "$c" node -v 2>/dev/null) && echo "$c $v"
done
Anything below v26.12.1, v24.22.1 or v22.23.4 needs a rebuild:
docker compose build --pull <service>
docker compose up -d --no-deps <service>
docker exec <container> node -v
Containers without node in the output are either not Node-based or, very often, multi-stage builds that use Node only to build static assets and then serve the result with nginx. Those are not exposed at runtime, although your CI builders still are, so update the build image too.
Pinned tags (node:24.22.0-alpine) need an explicit bump. Images you consume from third parties that embed Node.js only pick up the fix when their maintainers rebuild them.
End-of-life lines
Node.js 20 and older are affected by these vulnerabilities and will never receive a release that fixes them. The only remedy is to move to a supported line: 22 or 24 (or 26). In our own fleet the move from 20 to node:24 was uneventful, including for an application with a native module (mediasoup) that simply recompiled on the new image. Native add-ons are usually the scary part of a Node major upgrade; in our case a clean rebuild was enough, but do run your tests.
When a dependency pins you to an old Node
The hard case is the dependency that cannot run on a supported Node line. We hit exactly that with soketi 1.6.1, a popular self-hosted, Pusher-compatible WebSocket server. It pins uWebSockets.js 20.10, which ships prebuilt binaries only for Node 14, 16 and 18, so it cannot leave Node 18, an end-of-life line. The soketi-reborn fork moves to uWS 20.43, but at the time of writing still does not support Node 22.
Rather than run an unpatchable runtime facing the internet, we replaced it with Sockudo: a Pusher-protocol server written in Rust, MIT-licensed and actively maintained. It was a drop-in replacement for us: same app id, key and secret, and no change to clients using laravel-echo + pusher-js or to backends publishing through the Pusher HTTP API.
Two gotchas worth knowing before you try it:
- Production mode and push storage. With
ENVIRONMENT=production, Sockudo refuses to start when its push-notification storage uses in-memory drivers. If you do not use its mobile push feature, settingPUSH_ALLOW_MEMORY_DRIVERS=trueis the way through; if you do, configure a persistent backend instead. - Per-IP WebSocket rate limiting behind a proxy. The default limit is 20 WebSocket connections per minute per IP. Behind a reverse proxy that does not forward the client IP (or that Sockudo is not configured to trust), every visitor appears to come from the proxy’s address and all your users get throttled together. Configure the trusted proxy hops so it sees real client IPs, or disable that limiter and enforce limits at the proxy.
One observation from our own migration test, offered cautiously: on our setup, the old soketi accepted an HTTP API publish carrying an invalid signature, while Sockudo rejected the same request with 401. We have not investigated it further and it is not a published soketi CVE, but if you still run soketi, it is worth testing your own instance’s signature checking.
The broader lesson: when a security release lands, the question is not only “can I update Node?” but “what is stopping me?”. Inventory the dependencies that pin a runtime version (native modules with limited prebuilt binaries are the usual suspects) before you need to move fast.
Checklist
- Inventory every Node.js runtime: hosts,
nvminstalls, every container, CI and build images. - Upgrade to 26.12.1, 24.22.1 or 22.23.4.
- For containers, rebuild with
--pulland recreate; checknode -vinside each one afterwards. - Move anything on Node 20 or older to 22 or 24. There is no fix for end-of-life lines.
- Prioritise servers that fetch third-party content (Brotli) and code that builds TLS URLs from external input.
- If you ship snapshot-based or SEA binaries, rotate security-relevant values generated with
randomUUID()/randomInt()before the fix. - Identify dependencies that pin you to an old Node line, and plan their replacement.
Sources
- Node.js, October 2026 security releases: nodejs.org/en/blog/vulnerability/october-2026-security-releases
- Sockudo, Pusher-compatible WebSocket server: github.com/sockudo/sockudo