On Wednesday, October 21, 2026, the Node.js project shipped security releases for three lines: 26.x, 24.x and 22.x. The advisory fixes 10 vulnerabilities, 4 rated High, 3 Medium and 3 Low. One of the High ones, a memory-corruption bug in the bundled Brotli decoder, is described as allowing remote code execution, and that is the reason to patch this week rather than at the next maintenance window.
The short version
| Release line | Upgrade to at least |
|---|---|
| Node.js 26.x | v26.12.1 |
| Node.js 24.x | v24.22.1 |
| Node.js 22.x | v22.23.4 |
The advisory lists no workarounds for any of the 10 issues, so upgrading is the fix. It also says end-of-life versions are always affected when a security release ships. Node.js 20 and earlier are end of life and get no patch, so on those the only route is moving to a supported line.
Check what you are running
# on a server or CI runner
node --version
# inside a container image
docker run --rm your-image:tag node --version
# are you on Express 5 / body-parser 2?
npm ls express body-parser
# do you call the Brotli API directly?
grep -rn "brotliDecompress\|createBrotliDecompress" src/
An image pinned to an old tag stays vulnerable until you rebuild it. After upgrading, restart every Node.js process, containers included, so the patched runtime is actually loaded. If you use your distribution's Node.js package, also check your distro's advisory: the Aikido write-up notes that packaged builds may use the system Brotli library.
# nvm example
nvm install 24
nvm alias default 24
node --version # expect v24.22.1 or newer
# Docker: rebuild from a fresh base image
docker build --pull -t my-app .
All 10 vulnerabilities
| CVE | Severity | Lines | What it is |
|---|---|---|---|
| CVE-2026-111497 | High | 26, 24, 22 | Brotli decoder out-of-bounds write in node:zlib
|
| CVE-2026-105482 | High | 26, 24, 22 |
dns.resolve*() truncates a hostname at a NUL character |
| CVE-2026-105483 | High | 26, 24 |
tls.checkServerIdentity() fails to treat an IP written with fullwidth digits as an IP |
| CVE-2026-106069 | High | 26, 24, 22 | TLS hostname check bypass when a host "loosely" resolves to an IP address |
| CVE-2026-105481 | Medium | 26, 24, 22 | Keep-alive socket treats data that arrived early as the response to the next request |
| CVE-2026-106072 | Medium | 26, 24, 22 | DNS names in a certificate can authenticate non-canonical IPv4 spellings |
| CVE-2026-106074 | Medium | 26, 24, 22 |
crypto.randomUUID() and randomInt() caches saved into snapshots and single executable apps |
| CVE-2026-106070 | Low | 26 only |
zlib.ZipFile returns attacker-controlled bytes after a failed header validation |
| CVE-2026-106071 | Low | 26 only | Permission Model wildcard grant allows more than intended |
| CVE-2026-106073 | Low | 26, 24, 22 | Permission Model bypass through fs.realpathSync()
|
The release also updates c-ares, undici, the CA certificate bundle (the advisory lists "eMudhra Intermediate CA Compromise"), OpenSSL on 24.x and 22.x, and SQLite on 22.x.
The Brotli bug is the urgent one
CVE-2026-111497 is an out-of-bounds write in the Brotli decoder that Node.js bundles. According to the advisory, an invalid block switch was treated as "need more input" instead of a hard format error, so the streaming decoder kept appending data to a small internal buffer. Malicious Brotli data passed to node:zlib decompression can cause a crash, disclose process memory, or lead to remote code execution.
The question that decides your exposure is whether untrusted Brotli data ever reaches your process. I checked the common paths on Node.js 22.22.0, using harmless data:
-
fetch()decodes Brotli responses on its own. A test server answering withContent-Encoding: brcame back as plain text. So any server your code fetches from, including a user-supplied webhook or URL, can send your app Brotli data. -
A bare
httpserver does not decode request bodies. My test server saw the raw compressed bytes. -
Express 5 and body-parser 2 do. An Express 5.3.0 app using
express.json()(withbody-parser2.4.0, which has abrcase in its body reader) parsed a Brotli-compressed JSON body without any extra setup. So any client can feed Brotli data to such an app. That matches what Aikido reports.
In short: if you run Express 5 or call fetch() against servers you do not control, assume you are exposed.
How bad is it in practice?
Public reports disagree on how easily it can be exploited, so I will not over-claim either way. The Hacker News says a public proof of concept appeared on October 22 and reached code execution only against a deliberately weakened test build, with a crash and a limited memory read on a standard build. Aikido reports later code execution against an unmodified Node.js 24.22.0 image on Linux arm64, not on every attempt, and says 24.22.1 rejects the same input. Both say no exploitation in the wild had been reported as of October 22, and Qualys notes the CVE records did not yet have CVSS scores. A memory-safety flaw in a runtime that handles untrusted network input, with a public proof of concept and a routine version bump as the fix, is a good reason to patch now.
A stopgap if you cannot upgrade today
It does not replace the upgrade, and it does not cover Brotli responses your app fetches from other servers. But for an Express 5 API you can refuse Brotli request bodies before any body parser runs:
app.use((req, res, next) => {
if (/\bbr\b/i.test(req.headers['content-encoding'] ?? '')) {
return res.status(415).send('Unsupported Content-Encoding');
}
next();
});
app.use(express.json()); // must come after the check
I ran this with Express 5.3.0: a plain JSON body and a gzip body returned 200, and a Brotli body returned 415. The same rule can also live at your proxy or load balancer.
The TLS hostname bugs: real, but narrower
Three of the High and Medium issues (CVE-2026-105483, CVE-2026-106069, CVE-2026-106072) are in how tls.checkServerIdentity() decides that a certificate matches the host you connected to. The advisory describes them as IP-address edge cases: an IP written in an unusual way (fullwidth digits, leading zeros, hex or a bare integer) gets matched against DNS names in the certificate instead of its IP entries. For CVE-2026-106072 the advisory says exploitation needs a numeric DNS name in a certificate from a CA the client trusts, plus a resolver that accepts those spellings, such as glibc.
The leading-zero case rests on a real parsing difference. WHATWG URL parsing reads 0127 as octal, and some operating system resolvers read it as decimal:
new URL('http://0127.0.0.1/').hostname // '87.0.0.1' (checked on Node 22.22.0)
So the certificate can be validated against a different address than the one the socket connects to. The practical takeaway: if your services connect to other services by IP address, or take hostnames or URLs from users, these matter. If you only connect to normal DNS names, your exposure is much smaller, but the patch is still worth taking.
The DNS bug: validate, then resolve
CVE-2026-105482 means dns.resolve*() and dns.reverse() cut a name off at an embedded NUL byte, because the name is handed to c-ares as a C string. The advisory's example: attacker.example\0.trusted.example resolves attacker.example. The risky pattern is checking a hostname against an allowlist and then resolving it, which is common in SSRF protection code. The advisory calls it an incomplete fix of CVE-2026-48930, which covered only dns.lookup() and the net/tls host options. The fix is the upgrade. As defence in depth (my suggestion, not from the advisory), reject names containing a NUL byte before you validate them:
if (hostname.includes('\0')) throw new Error('invalid hostname');
The rest, and who can skip them
- Keep-alive socket (CVE-2026-105481): unsolicited data that arrived on an idle reused socket can be parsed as the response to your next request. It is described as an incomplete fix of CVE-2026-48931.
-
randomUUID()/randomInt()(CVE-2026-106074): only matters if you build user-land startup snapshots (--build-snapshot) or single executable apps withuseSnapshot. Every process started from the snapshot returns the same values until the cache refills, so IDs can collide or be predicted. -
Permission Model (CVE-2026-106071, CVE-2026-106073): only matters if you run with
--permissionand--allow-fs-reador--allow-fs-write. 106071 is on 26.x only. -
zlib.ZipFile(CVE-2026-106070): 26.x only.
Frequently asked questions
Is this being exploited?
As of October 22, the sources I read reported no exploitation in the wild. A public proof of concept exists, so do not treat that as permission to wait.
I don't use Brotli. Am I safe?
Not necessarily. Brotli decoding can happen without you calling it: through fetch() responses, or through Express 5 request body parsing, as shown above. And the other nine fixes apply regardless.
Can I stay on Node.js 20?
It is end of life and will not receive these fixes. Plan the move to a supported line.
References
- Node.js: Wednesday, October 21, 2026 Security Releases
- The Hacker News: Node.js Brotli decoder bug
- Qualys: Node.js patches Brotli remote code execution flaw
- Aikido: Node.js Brotli vulnerability and the fix
Originally published on DEV Talk.
Top comments (0)