Skip to content
← ENGINEERING NOTES
SOFTWARE7 min read

Node.js October 2026 Security Release, Explained

Node.js shipped ten CVE fixes on 21 October 2026, including a Brotli out-of-bounds write. Here is what each bug does, how to check your fleet, and how to patch.

BY ASHLIN DARIUS GOVINDASAMY

Imagine your server decompresses a file, and the file writes past the end of an eight byte buffer. That is the headline bug in the Node.js security release of 21 October 2026, and it is why you should patch this week instead of next quarter. In this post we walk through the Node.js October 2026 security release: ten CVEs, four rated high, fixed in v22.23.4, v24.22.1 and v26.12.1. We cover what each bug does, how to find every Node you run, and how to prove your fix landed.

What the Node.js October 2026 security release fixed

The Node.js project published the advisory on 21 October 2026. The fixed versions are v22.23.4, v24.22.1 (the current LTS) and v26.12.1 (the latest release). The highest severity is High. The advisory lists four high, three medium and three low issues. End-of-life lines are always affected by a security release, and the project does not provide fixes for them.

  • High: CVE-2026-111497, an out-of-bounds write in the Brotli decoder used by node:zlib (22.x, 24.x, 26.x).
  • High: CVE-2026-105482, DNS NUL byte truncation in dns.resolve*() and dns.reverse() (22.x, 24.x, 26.x).
  • High: CVE-2026-105483, tls.checkServerIdentity() fooled by fullwidth digit IP addresses (24.x, 26.x).
  • High: CVE-2026-106069, tls.checkServerIdentity() and leading-zero IPv4 addresses (22.x, 24.x, 26.x).
  • Medium: keep-alive socket response confusion, numeric DNS SANs accepted for IP endpoints, and repeated entropy in snapshots and single executable apps.
  • Low: a ZipFile bug (26.x only) and three Permission Model issues, two of them in file system checks.

The Brotli bug: an eight byte buffer and a lie

Brotli is the compression format your CDN and browser probably negotiate through Content-Encoding: br. Node ships a decoder behind zlib.brotliDecompress and its streaming siblings. According to the advisory, an invalid block switch was reported to the caller as a request for more input instead of as a format error. The streaming decoder therefore kept appending input to an eight byte internal buffer and wrote past its end. Any application that decompresses attacker-supplied Brotli data may face a crash, process memory disclosure or remote code execution.

The takeaway is bigger than one CVE. A decoder that fails to say no, and instead says keep going, turns malformed input into a memory bug. If you accept uploads, webhooks, proxied responses or archives that you decompress server-side, you are in scope.

Three ways an IP address can lie to TLS

Three of the high and medium bugs live in tls.checkServerIdentity(), the function that decides whether a certificate matches the host you connected to. They share a theme: the string you validate is not the address you connect to.

  • Fullwidth digits (CVE-2026-105483): a host such as the fullwidth form of 127.0.0.1 was not recognised as an IP, so it was matched against certificate DNS names and the Common Name. A certificate with DNS:127.0.0.1 could be accepted for an IP endpoint it was not issued for. The advisory calls it a regression after an earlier fix.
  • Leading zeros (CVE-2026-106069): WHATWG URL parsing reads 0127.0.0.1 as octal, giving 87.0.0.1, while the OS resolver on some platforms such as macOS reads it as decimal 127.0.0.1. The certificate is checked against one address and the socket connects to another.
  • Non-canonical IPv4 (CVE-2026-106072, medium): spellings like 0x7f.0.0.1, 2130706433 and 127.1 were matched against DNS SANs. Exploiting it needs a numeric DNS SAN certificate from a CA the client trusts and a resolver that accepts those spellings, for example glibc.

You can see the parser disagreement yourself. This snippet only prints how the WHATWG URL parser normalises several spellings, so it is safe to run anywhere.

js
const spellings = ["127.0.0.1", "0127.0.0.1", "0x7f.0.0.1", "2130706433", "127.1"];

for (const s of spellings) {
  const host = new URL("https://" + s).hostname;
  console.log(s.padEnd(12), "->", host);
}
ip-spellings.mjs: what the URL parser thinks each host is

QUICK CHECK

Why is leading-zero IPv4 such as 0127.0.0.1 dangerous for certificate checks?

DNS, keep-alive and the quieter bugs

CVE-2026-105482 is a classic C string trap. dns.resolve*() and dns.reverse() pass the name to c-ares as a C string, so a hostname containing a NUL byte is silently truncated. Resolving attacker.example followed by a NUL and then .trusted.example queries attacker.example and returns its records without error. If you check a hostname against an allowlist and then resolve it, the records you act on may belong to a different name. The advisory says this is an incomplete fix of an earlier CVE that covered only dns.lookup() and the net and tls host options.

CVE-2026-105481 concerns HTTP keep-alive. The guard that destroys an idle socket on unexpected data only runs when the event loop polls for I/O. If an unsolicited response has already arrived but is unread when the socket is reused, for instance because the event loop was busy with synchronous work, the data can be parsed as the response to your next request. CVE-2026-106074 is a reminder about snapshots: the entropy caches behind crypto.randomUUID(), randomUUIDv7() and randomInt() can be baked into a startup snapshot or single executable app, so processes started from it return the same values, up to 127 UUIDs and about 1,000 randomInt() values.

›Deep dive: the Permission Model fixes

Three low severity bugs touch the experimental permission model. On 26.x only, a wildcard grant sitting directly under the permission tree root could over-match a shorter path, so a grant ending in a wildcard after /etc/passwd also allowed reading /etc/passwd itself. On 22.x, 24.x and 26.x, fs.realpathSync() lacked the read checks under restricted --allow-fs-read, so code could test existence and resolve symlinks outside permitted directories. That is an incomplete fix of an earlier CVE that covered only fs.realpathSync.native(). These are low severity because the permission model is a seatbelt, not a sandbox, but they show why you should keep it patched.

Find every Node you actually run

Patching the Node on your laptop is the easy part. The runtime also hides in container images, CI runners, version managers and single executable builds. An image built before the release keeps the old runtime until you rebuild it on a fresh base, and a single executable embeds the runtime it was built with.

bash
# host
node --version

# every running container that has node
for c in $(docker ps -q); do
  v=$(docker exec "$c" node --version 2>/dev/null) && echo "$c $v"
done

# images on this box
for i in $(docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>'); do
  v=$(docker run --rm --entrypoint node "$i" --version 2>/dev/null) && echo "$i $v"
done

# fixed floors: 22.23.4, 24.22.1, 26.12.1
Inventory and compare against the fixed versions
bash
# nvm
nvm install 24 --latest-npm && nvm alias default 24

# Dockerfile: pin a patch version, then rebuild without cache
#   FROM node:24.22.1-bookworm-slim
docker build --pull --no-cache -t myapp:patched .

# prove it
docker run --rm myapp:patched node --version
Update and rebuild

Hardening while you are in there

  • Cap decompression: set maxOutputLength on zlib calls and limit request body sizes at the proxy, so a hostile payload cannot grow without bound.
  • Reject odd hosts at the edge: refuse hostnames with NUL bytes, non-ASCII digits, leading zero octets or hex and integer IP spellings before they reach DNS or TLS code.
  • Prefer canonical IP handling: parse with net.isIP() and compare numerically instead of by string.
  • Never run snapshot-time code that generates IDs or random numbers you will reuse at runtime.
  • Plan for end of life: if you are on an unsupported line, no fix is coming, so schedule the upgrade.

QUICK CHECK

Your Docker image was built last week from node:24. You restart the container after the release. Are you patched?

Takeaway

This release is a good lesson in parser disagreement: URL parser versus resolver, C string versus JavaScript string, decoder versus caller. Upgrade to 22.23.4, 24.22.1 or 26.12.1, rebuild your images, and then audit the places where you compare a hostname you validated with the address you connect to. Read the advisory yourself for the exact affected lines per CVE, because not every bug touches every line.

SOURCES

  1. Node.js Security Releases: October 21, 2026 — Node.js Project
  2. Node.js security blog index — Node.js Project

Frequently asked questions

Which Node.js versions fix the October 2026 vulnerabilities?

v22.23.4, v24.22.1 and v26.12.1. End-of-life lines are affected and receive no fix, so upgrade to a supported line.

Which bug in the October 2026 release is the most serious?

The advisory rates four issues High. CVE-2026-111497, an out-of-bounds write in the Brotli decoder in node:zlib, is the one that carries remote code execution potential when you decompress attacker-supplied Brotli data.

Does restarting my Docker container apply the Node.js fix?

No. The runtime is part of the image, so you must rebuild the image from a patched base and redeploy it.

Is there a workaround if I cannot upgrade immediately?

The advisory lists no workarounds and recommends upgrading. Limiting untrusted Brotli input and validating hostnames reduce exposure but do not replace the patch.

NEWSLETTER

Get the engineering notes in your inbox.

Kernels, GPUs, networks, AI infrastructure — and the things we broke along the way. No spam, one click to leave.

We store only your email address, to send you new articles. See our privacy policy.

TAGS

Node.jssecurityCVEBrotliTLSDNSpatching

RELATED

ENGINEERING NOTES

Things we built.Things we broke.Things we learned.