On 21 October 2026 Node.js fixed ten vulnerabilities across every supported release line, four of them rated high. The most serious is a memory corruption bug in the bundled Brotli decoder that Node.js says permits remote code execution, and it is reachable through fetch and common body parsers, not only direct zlib calls. Upgrade to 22.23.4, 24.22.1 or 26.12.1. Node.js 20 and older receive no fix.
Reflects what was publicly known on the last updated date. Every fact links to a primary source at the end of the page.
If you run Node.js anywhere, yes. The rows below set the order to work in.
You run Node.js 22, 24 or 26
Servers, CLIs, build tools, serverless functions
Upgrade to 22.23.4, 24.22.1 or 26.12.1. All three lines carry at least one high-severity fix. Fixed versions
You decompress Brotli you do not control
fetch to third-party servers, Express 5 APIs, proxies
Upgrade these services first. This is the bug Node.js says permits remote code execution, and common libraries reach it without any explicit zlib call in your code. CVE-2026-111497
You run Node.js 20 or older
Including 21, 23 and 25
No fix is coming. Node.js 20 reached end of life on 30 April 2026. Treat these systems as carrying known, unfixed vulnerabilities and migrate to Node.js 22 or 24. Details
You ship Node.js inside images or binaries
Docker images, single executable applications, startup snapshots
Upgrading the host is not enough. Rebuild every image and binary on the fixed version, because each one carries its own copy of the runtime.
You rely on the Permission Model
Processes started with --permission
Two low-severity bypasses are fixed. Node.js documents the Permission Model as a safeguard for trusted code, not a sandbox for untrusted code.
This is an upgrade, not a configuration change. Node.js has published no workarounds for any of the ten CVEs.
Inventory every Node.js runtime you run
node -p "[process.version, 'openssl ' + process.versions.openssl,
'c-ares ' + process.versions.ares, 'undici ' + process.versions.undici].join('\n')"Upgrade services that handle untrusted Brotli first
Rebuild container images on a fresh base
node images were updated on 21 October. An image built before then keeps the old runtime until you rebuild it on a fresh pull of the base.docker run --rm node:22 node --version # expect v22.23.4 or later
docker run --rm node:24 node --version # expect v24.22.1 or later
docker run --rm node:26 node --version # expect v26.12.1 or laterRebuild single executables and startup snapshots
randomUUID or randomInt. See CVE-2026-106074.Turn on the post-upgrade detections
Schedule the move off end-of-life lines
Version first, then reachability. A service on a fixed runtime is done; a service on an old runtime is urgent in proportion to which of the code paths below it uses.
Hosts and CI runners. Version managers and system packages often leave more than one node on a machine. List them all.
# Every node binary on PATH, with its version
for n in $(which -a node); do printf '%s %s\n' "$n" "$("$n" --version)"; doneContainers. The runtime inside an image is fixed at build time. Ask each image directly rather than trusting its tag.
# Node.js version inside each image used by a running container
docker ps --format '{{.Image}}' | sort -u | while read -r img; do
printf '%s ' "$img"
docker run --rm --entrypoint node "$img" -p process.version 2>/dev/null || echo "no node"
doneAWS Lambda. By default, Lambda applies managed runtime updates automatically, and the runtime version ARN appears in the INIT_START line of each function’s logs. Functions pinned to a runtime version, and functions deployed as container images, are not updated for you: container-based functions need a rebuild on the updated base image and a redeploy.
# Functions on a managed Node.js runtime
aws lambda list-functions \
--query "Functions[?starts_with(Runtime, 'nodejs')].[FunctionName,Runtime]" \
--output table
# Whether a function takes runtime updates automatically
aws lambda get-runtime-management-config --function-name <name>Single executable applications. An SEA carries the runtime it was built with. Check the Node.js version in the pipeline that builds each binary, not on the machine that runs it.
Direct Undici dependencies. If your application installs the undici package itself, upgrading Node.js does not change it. Upgrade it to 6.29.1, 7.30.1 or 8.12.2. The npm package still decodes Brotli with the runtime’s zlib, so the runtime upgrade is needed either way.
# A direct undici dependency is not updated by upgrading Node.js
npm ls undici --all
pnpm why undiciUse this to order the upgrade, not to skip it. Dependencies reach these APIs too, so a clean search of your own code does not prove a service is unexposed.
| Bug | Reachable when | Look for |
|---|---|---|
| Brotli decoder | Brotli input from a source you do not control: zlib Brotli calls, fetch responses with Content-Encoding: br, Express 5 or body-parser 2.x request bodies, proxies that decompress. | brotliDecompress, createBrotliDecompress, fetch to user-supplied or third-party URLs, express@5, body-parser@2 |
| DNS NUL truncation | User-supplied names checked as strings, then passed to a resolve method. dns.lookup is not affected. | dns.resolve*, dns.reverse, Resolver, allowlists built on endsWith |
| TLS hostname bypass | TLS connections to host strings an attacker can influence: webhooks, URL previews, SSRF-prone fetchers, proxies. | tls.connect, https.request or fetch with a user-supplied host, custom checkServerIdentity |
| Keep-alive response smuggling | HTTP clients with keep-alive talking to an untrusted server, or plain HTTP across an untrusted network. | new http.Agent({ keepAlive: true }) |
| Snapshot entropy reuse | Only single executable applications built with useSnapshot, or user-land startup snapshots. | useSnapshot in SEA config, --build-snapshot |
| Permission Model bypasses | Only processes started with --permission. | --permission, --allow-fs-read, --allow-fs-write |
rg -n --type js --type ts \
-e 'brotliDecompress|createBrotliDecompress' \
-e 'dns\.(resolve|reverse)|new (dns\.)?Resolver' \
-e 'checkServerIdentity|keepAlive:\s*true' \
-e 'useSnapshot|build-snapshot|--permission' .No public exploit or indicators of compromise had been published as of 21 October 2026. These are starting points built from how the fixes behave, not signatures for a known campaign.
On an unpatched runtime, an out-of-bounds write is most likely to surface as a Node.js process dying while it handles compressed input: a segmentation fault on a host, or exit code 139 in a container. Correlate unexplained crashes of services that accept or fetch Brotli with the requests they were handling at the time.
On a fixed runtime, the decoder rejects the malformed streams the bug relied on with the error code ERR__ERROR_FORMAT_BLOCK_SWITCH (errno -17), according to the fix’s regression test. Legitimate encoders do not produce these streams, so the code is worth an alert. Make sure the error, the client address and the request path reach your logs.
// Log rejected Brotli streams with enough context to investigate.
stream.on("error", (err) => {
if (err.code === "ERR__ERROR_FORMAT_BLOCK_SWITCH") {
logger.warn({ err, source: req.ip, path: req.url }, "rejected brotli stream");
}
});If your load balancer or WAF records request headers, also look at requests that arrive with Content-Encoding: br at endpoints that never expected compressed bodies.
The two TLS hostname bypasses need a host string that only becomes an IP address after normalisation. On a fixed runtime these connections fail with ERR_TLS_CERT_ALTNAME_INVALID. Before upgrading, you can flag such hosts in outbound request logs or at the point where user-supplied URLs enter your application.
// Flags host strings that only become IPv4 addresses after normalisation,
// such as 0127.0.0.1, 0x7f.0.0.1, 127.1 or fullwidth digits.
const net = require("node:net");
function nonCanonicalIpHost(host) {
if (/[\uFF0E\uFF10-\uFF19\u3002]/.test(host)) return true;
return (
net.isIP(host) === 0 &&
/^(0x[0-9a-f]*|\d+)(\.(0x[0-9a-f]*|\d+)){0,3}\.?$/i.test(host)
);
}A fixed runtime throws ERR_INVALID_ARG_VALUE when a resolve call receives a name containing a NUL character. In web logs, a NUL usually arrives encoded as %00 in a parameter that later becomes a hostname. Either is a strong sign of deliberate probing, since no valid DNS name contains one.
Status as of 21 October 2026, from the Node.js release schedule. Any later release in the same line also contains the fixes.
| Release line | Status | Fixed in |
|---|---|---|
| Node.js 26 | Current, becomes LTS on 28 October 2026 | 26.12.1 |
| Node.js 24 (Krypton) | LTS, in maintenance since 20 October 2026 | 24.22.1 |
| Node.js 22 (Jod) | Maintenance LTS until 30 April 2027 | 22.23.4 |
| Node.js 25, 23 and 21 | End of life | No fix |
| Node.js 20 (Iron) and older | End of life since 30 April 2026 | No fix |
The Node.js security post states that end-of-life versions are always affected when a security release occurs. Node.js 20 reached end of life on 30 April 2026 and Node.js 25 on 1 June 2026. The Node.js project recommends that production applications use only Active LTS or Maintenance LTS releases.
Ten CVEs, ordered by severity. Node.js has published only a title and rating for each, so the explanations below come from the fix commits and their tests, and say so where they go beyond what Node.js has stated.
| CVE | Title | Severity | Affected lines |
|---|---|---|---|
| CVE-2026-111497 | Brotli decoder out-of-bounds write | High | 22.x, 24.x, 26.x |
| CVE-2026-105482 | DNS resolve truncates names at a NUL character | High | 22.x, 24.x, 26.x |
| CVE-2026-106069 | TLS hostname bypass with non-canonical IP addresses | High | 22.x, 24.x, 26.x |
| CVE-2026-105483 | TLS hostname bypass with fullwidth digits | High | 24.x, 26.x |
| CVE-2026-106072 | DNS names in certificates accepted for IP endpoints | Medium | 22.x, 24.x, 26.x |
| CVE-2026-105481 | Keep-alive socket accepts a response sent too early | Medium | 22.x, 24.x, 26.x |
| CVE-2026-106074 | Repeated random values in snapshots and SEAs | Medium | 22.x, 24.x, 26.x |
| CVE-2026-106070 | ZipFile returns stray bytes after a failed header check | Low | 26.x |
| CVE-2026-106071 | Permission Model over-grants with wildcard paths | Low | 26.x |
| CVE-2026-106073 | Permission Model bypass through fs.realpathSync | Low | 22.x, 24.x, 26.x |
Affects 22.x, 24.x, 26.x. Fix: Node.js commit, google/brotli commit
The bug is in the Brotli library that Node.js bundles, not in Node.js’s own code. A crafted compressed stream that declares a single block type and then switches blocks was not rejected, and Node.js says the result is an out-of-bounds write that permits remote code execution. Node.js applied the upstream fix, which makes the decoder stop with an error on such streams.
Who is exposed: anything that decompresses Brotli data it does not control. zlib.brotliDecompress, its sync form and zlib.createBrotliDecompress all reach the decoder. So do two common libraries that call them for you: the built-in fetch decodes responses sent with Content-Encoding: br, and body-parser 2.x, the default in Express 5, decompresses br request bodies unless inflate is turned off. Node.js has not commented on either path; both use the patched decoder, so we treat them as exposed.
Affects 22.x, 24.x, 26.x. Fix: Node.js commit
The DNS resolver hands names to c-ares as C strings, which end at the first NUL character. A name such as example.com\0.trusted.corp passes a JavaScript check like endsWith(".trusted.corp"), but the query actually goes out for example.com. The fix commit uses this example.
Who is exposed: applications that pass user-supplied names to dns.resolve, any resolve* method, dns.reverse or a Resolver instance after checking them as strings, for example to enforce an allowlist. After the fix these calls throw ERR_INVALID_ARG_VALUE for names containing a NUL. The change does not touch dns.lookup.
Affects 22.x, 24.x, 26.x. Fix: Node.js commit
Before checking a server’s certificate, Node.js normalised the hostname. Strings such as 0127.0.0.1, 0x7f.0.0.1 or 127.1 could be checked against the certificate as one address while the operating system’s resolver connected to another. The fix’s test pairs 0127.0.0.1, which the resolver reads as octal and connects to 87.0.0.1, with a certificate check against 127.0.0.1.
Who is exposed: applications that open TLS connections to host strings an attacker can influence, such as webhook senders, URL previewers and proxies. Based on the fix’s tests, the attacker also needs a certificate the client trusts whose DNS name is the IP address written normally. Connections to fixed, ordinary hostnames are not affected. After the fix, tls.checkServerIdentity rejects these hosts with ERR_TLS_CERT_ALTNAME_INVALID.
Affects 24.x, 26.x. Fix: Node.js commit
A related regression in the same hostname check. A host written with fullwidth digits, such as 127.0.0.1, was normalised into an ordinary IP address and could then match a certificate issued for that address. The fix rejects any host that only becomes an IP address after normalisation. Node.js 22 does not have the regression.
Node.js rates this high, but the fix commit itself describes it as a low-severity bug that is not easy to exploit. We show the rating from the security release post.
Affects 22.x, 24.x, 26.x. Fix: Same commit as CVE-2026-106069
A second report of the same underlying problem, fixed by the same change. A certificate whose DNS name reads 127.0.0.1 could authenticate a connection made to a non-canonical form of the address, such as 0177.0.0.1. Upgrading for CVE-2026-106069 fixes this too.
Affects 22.x, 24.x, 26.x. Fix: Node.js commit
When the HTTP client reuses a kept-alive connection, a server could write a response onto the idle socket before the next request was sent, and Node.js would treat it as the answer to that request. An earlier guard only caught this when the event loop had polled the socket. The fix asks the operating system whether unread data is waiting before reusing a socket, and opens a new connection if it is.
Who is exposed: clients using an http.Agent with keepAlive enabled that talk to a server which is malicious or compromised, or to a plain HTTP server through an attacker on the network path.
Affects 22.x, 24.x, 26.x. Fix: Node.js commit
crypto.randomUUID() and crypto.randomInt() keep a small cache of random values. When an application built a startup snapshot, the unused part of that cache was saved into it, so every process started from the snapshot produced the same first values. The fix clears the caches when a snapshot is written and when it is loaded.
Who is exposed: only applications built as single executable applications with useSnapshot enabled, or that use user-land startup snapshots. Ordinary applications are not. Node.js has not published rotation guidance. We recommend rebuilding affected binaries on a fixed version and rotating any tokens, nonces or other secrets they generated with these functions, since those values may be predictable.
Affects 26.x. Fix: Node.js commit
zlib.ZipFile is an experimental API added in 26.8.0 for reading ZIP archives. When a ZIP entry failed header validation, a later read of that entry could return bytes from the wrong position in the file. Only 26.x has this API.
Affects 26.x. Fix: Node.js commit
With the Permission Model enabled, a wildcard path in --allow-fs-read or --allow-fs-write could grant access to more paths than it should. Node.js has not described which paths were over-granted.
Affects 22.x, 24.x, 26.x. Fix: Node.js commit
fs.realpathSync() skipped the Permission Model check that its siblings, such as fs.promises.realpath() and fs.existsSync(), already performed, so it could be used on paths the process had not been granted.
On both Permission Model issues: the Node.js documentation describes the Permission Model as a seat belt that stops trusted code from accidentally touching files it was not granted, and states that it does not protect against malicious code. Do not rely on it to contain untrusted code, patched or not.
The releases also update libraries that Node.js bundles. You get these by upgrading Node.js; there is nothing to install separately.
| Library | 22.23.4 | 24.22.1 | 26.12.1 | What it fixes |
|---|---|---|---|---|
| c-ares (DNS resolver) | 1.34.9 | 1.34.9 | 1.34.9 | Eleven upstream advisories, mostly use-after-free bugs when lookups are cancelled or servers change during a callback, and one where a reply from a different server than the one queried could be accepted. c-ares 1.34.9 |
| OpenSSL | 3.5.9 | 3.5.9 | Unchanged | CVE-2026-84782 (high), a DTLS retransmission bug that can leak heap memory or crash, plus low-severity fixes. Node.js exposes TLS over TCP, not DTLS, so we expect few applications to reach the high-severity path. OpenSSL vulnerabilities |
| Undici (fetch) | 6.29.1 | 7.30.1 | 8.12.2 | Includes CVE-2026-109383 (high), where function-valued TLS options such as a custom checkServerIdentity were silently dropped by Agent, CVE-2026-87018 (high), a crash through TLS 1.2 renegotiation, and several denial-of-service fixes. 7.x and 8.x also fix a cache poisoning bug, CVE-2026-109829. Undici 6.29.1, Undici 8.12.2 |
| SQLite | 3.53.4 | Unchanged | Unchanged | Brings 22.x up to the SQLite version already in 24.x. Node.js lists it as a dependency update and names no CVE. |
| Root certificates | NSS 3.131.1 | NSS 3.131.1 | NSS 3.131.1 | Rejects chains containing the eight compromised eMudhra intermediates (#66871) and removes three emSign roots (#66877). See our eMudhra advisory. |
Dates are UTC. Upstream dependency releases are included because the Node.js fixes depend on them.
2026-10-20
Node.js merges the eMudhra intermediate blocklist and the NSS 3.131.1 root store update.
2026-10-21
Node.js releases 22.23.4, 24.22.1 and 26.12.1 and publishes the security release post.
Node.js 22.23.4, 24.22.1 or 26.12.1, or any later release in the same line. These are the first releases that contain the October 2026 fixes. Rebuild container images and single executable applications as well, because each carries its own copy of the runtime.
Yes, and it receives no fix. Node.js 20 reached end of life on 30 April 2026, and the Node.js project states that end-of-life versions are always affected when a security release occurs. Move to Node.js 22 or 24.
CVE-2026-111497, an out-of-bounds write in the Brotli decoder bundled with Node.js, which Node.js rates high and says permits remote code execution. Any code that decompresses Brotli data from a source it does not control can reach it.
Node.js has not said, but both use the affected decoder. The built-in fetch, provided by Undici, decodes responses sent with Content-Encoding: br using zlib.createBrotliDecompress. body-parser 2.x, the default in Express 5, decompresses br request bodies by default. Treat fetch calls to servers you do not trust, and Express 5 endpoints that accept request bodies, as exposed until you upgrade.
On an unpatched runtime, a successful trigger is likely to look like a Node.js process crashing while handling compressed input. After upgrading, the fixed decoder rejects these streams with the error code ERR__ERROR_FORMAT_BLOCK_SWITCH (errno -17). Alert on that code in application logs and keep the request that caused it. No public exploit or indicators of compromise had been published as of 21 October 2026.
Usually not. The release notes list no breaking changes. Connections to hosts written as non-canonical IP addresses, such as 0127.0.0.1, now fail certificate checks, and DNS resolve calls with a NUL character in the name now throw. Single executable applications and startup snapshots must be rebuilt with the fixed version.
Yes. The node images on Docker Hub were published for 22.23.4, 24.22.1 and 26.12.1 on 21 October 2026. Rebuild your images on a fresh pull so they pick up the new base.
Yes. All three releases reject certificate chains that contain the eight compromised eMudhra intermediates and update the bundled root store to NSS 3.131.1, which removes three emSign roots.