Node.js CVE-2026-111497: Brotli Decoder Out-of-Bounds Write Opens RCE on 22.x, 24.x and 26.x

Introduction

Node.js shipped its October 21 security release with ten CVEs, and the one that matters most is CVE-2026-111497: an out-of-bounds write in the bundled Brotli decoder that the project says can permit remote code execution. Every supported line — 22.x, 24.x and 26.x — is affected, there is no documented workaround, and a public proof-of-concept surfaced within a day. Upgrade to 22.23.4, 24.22.1 or 26.12.1, then rebuild anything that bakes the runtime in.

What Happened

On Wednesday, October 21, 2026, the Node.js project published fixes across its three supported release lines. The headline bug, CVE-2026-111497, is rated High and was reported by OpenAI Codex Security (Khai Tran and Sylvie Mayer). Matteo Collina and Antoine du Hamel landed the fix, which cherry-picks an upstream Brotli commit so the decoder now returns a new error, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, instead of carrying on and writing past its buffer.

Exploitation status as of October 22:

  • A public PoC appeared shortly after the release. Reporting on it says that on a standard Node.js build it produced a process crash and a limited read of process memory; full code execution was only shown against a deliberately weakened build.
  • Neither the Node.js project nor the outlets covering the release have reported in-the-wild exploitation.
  • No CVSS score had been published at the time of writing.

The same release fixed nine other issues, several of which are worth caring about on their own:

CVE Severity Issue Lines
CVE-2026-111497 High Brotli decoder out-of-bounds write (RCE) 26, 24, 22
CVE-2026-106069 High TLS hostname check bypass when host loosely resolves to an IP 26, 24, 22
CVE-2026-105483 High TLS hostname bypass with fullwidth-digit IPs 26, 24
CVE-2026-105482 High dns.resolve truncates names containing NUL 26, 24, 22
CVE-2026-105481 Medium Keep-alive socket processes a response written before the request 26, 24, 22
CVE-2026-106072 Medium DNS SANs can authenticate IP endpoints with non-canonical IPv4 26, 24, 22
CVE-2026-106074 Medium randomUUID() / randomInt() entropy caches frozen into snapshots and SEAs 26, 24, 22
CVE-2026-106070 Low ZipFile returns attacker-controlled prefix bytes 26
CVE-2026-106071 Low Permission Model wildcard over-grant 26
CVE-2026-106073 Low Permission Model bypass via fs.realpathSync 26, 24, 22

Bundled dependencies were also bumped: c-ares, Undici, OpenSSL (24.x/22.x), SQLite (22.x), and the CA store, which drops the compromised eMudhra intermediate that browsers blocked between October 17 and 19.

Why It Matters

Brotli decoding is not an exotic code path you opted into. Node's built-in fetch() (Undici) transparently decompresses Content-Encoding: br responses, and Axios advertises Brotli in Accept-Encoding by default. Coverage of the bug also points out that Express 5's body-parser 2.x accepts Brotli-encoded request bodies with inflation on by default. That gives the bug two directions:

  • Inbound — any service that decompresses client request bodies.
  • Outbound — any backend that calls a third-party API, webhook target, or object store whose responses an attacker can influence. The attacker never has to reach your server directly.

Today's PoC stops at a crash on stock builds, which is already a cheap denial-of-service against API gateways, BFF layers and CI tooling. The distance from "controlled out-of-bounds write plus memory read" to "code execution" is the kind of gap that tends to close in days, not months. And because Node.js 20.x and older are end-of-life, a large tail of Lambda functions, container images and hosting-panel runtimes will not get an upstream fix at all.

The snapshot entropy bug (CVE-2026-106074) deserves its own line item: if you ship single-executable apps or user-land startup snapshots, every process started from that artifact may have generated the same "random" UUIDs and integers.

Who Is Affected

  • Node.js 22.x before 22.23.4, 24.x before 24.22.1, 26.x before 26.12.1.
  • Node.js 20.x and older: affected and end-of-life — no upstream patch. Some vendors (CloudLinux alt-nodejs12 through alt-nodejs20) are backporting; node --version will not change on those builds, so verify by package release.
  • Single-executable applications (SEA) and startup snapshots built with a vulnerable runtime — they carry the old decoder until rebuilt.
  • Container images pinned to vulnerable node: tags, Electron or other products that embed Node.js, and serverless runtimes until the provider rolls the patch.
  • Updating the OS brotli / libbrotli package does not fix Node.js; Node bundles its own copy in deps/brotli.

How to Protect Yourself

1. Inventory the runtimes that actually serve traffic

The version in your shell is not the version in your workers. Check running processes and images:

# running node processes and the binary they came from
ps -eo pid,args | grep -E '[n]ode( |$)' 
for p in $(pgrep -x node); do echo "$p $(readlink /proc/$p/exe) $(/proc/$p/exe --version 2>/dev/null)"; done

# container images in use
docker ps --format '{{.Image}}' | sort -u | while read i; do
  echo "$i -> $(docker run --rm --entrypoint node "$i" --version 2>/dev/null)"
done

# Kubernetes: images that look like node runtimes
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' | grep -i node | sort -u

2. Upgrade to a fixed release

Fixed builds: v22.23.4, v24.22.1, v26.12.1.

# nvm
nvm install 24.22.1 && nvm alias default 24.22.1

# Dockerfiles: bump the base tag and rebuild
grep -rnE '^FROM .*node:' --include=Dockerfile* .
# e.g. FROM node:24.22.1-bookworm-slim

# CloudLinux alt-nodejs
rpm -qa 'alt-nodejs*'
yum update 'alt-nodejs*'

Restart long-running processes afterwards — PM2, systemd units and hosting-panel Node selectors keep the old binary in memory until they restart.

3. Rebuild SEAs, snapshots and anything that embeds Node

A patched host runtime does nothing for an artifact that already contains the old one. Rebuild single-executable apps and --build-snapshot blobs with the fixed version, and redeploy. This also clears the frozen-entropy issue from CVE-2026-106074.

4. Find where you decode Brotli

There is no configuration switch that disables the vulnerable path, and maxOutputLength is not documented as a mitigation. Map your exposure so you know which services to patch first:

# direct use in your code
grep -rnE 'brotliDecompress|createBrotliDecompress|BrotliDecompress' --include='*.js' --include='*.ts' --include='*.mjs' . | grep -v node_modules

# inbound body parsing (Express 5 / body-parser 2.x)
npm ls express body-parser 2>/dev/null

# outbound HTTP clients that auto-decompress
npm ls undici axios node-fetch got 2>/dev/null

Until a service is patched, reducing risk means not decompressing Brotli from sources you do not control: strip or reject Content-Encoding: br on inbound requests at the reverse proxy, and avoid advertising br in outbound Accept-Encoding where you can set it. That narrows exposure; it does not close it.

5. Watch for crashes

The public PoC crashes stock builds, so unexplained Node worker restarts are your earliest signal:

journalctl -u 'your-node-service*' --since '2026-10-21' | grep -Ei 'segmentation fault|SIGSEGV|SIGABRT|core dumped'
pm2 logs --lines 500 | grep -Ei 'SIGSEGV|exited with code 139'
kubectl get pods -A --field-selector=status.phase=Running -o json | jq -r '.items[] | select(any(.status.containerStatuses[]?; .restartCount > 3)) | .metadata.namespace + "/" + .metadata.name'

6. Retire end-of-life lines

If you are still on Node.js 20.x or older, this is the release that should end that. Move to 22.x or 24.x LTS; vendor backports are a bridge, not a plan.

Source