BREAKING: Node.js CVE-2026-111497 lets one malformed Brotli body corrupt memory through fetch(), Express 5 and zlib

BREAKING: Node.js CVE-2026-111497 lets one malformed Brotli body corrupt memory through fetch(), Express 5 and zlib

How to fix CVE-2026-111497: upgrade Node.js to 22.23.4, 24.22.1 or 26.12.1. A malformed Brotli stream makes the bundled decoder write past a buffer, and fetch(), Express 5 body parsing and zlib all reach it.

6 min read1,333 words
Contents

A malformed Brotli stream can make Node.js write past the end of a buffer while it decompresses, and most Node apps decompress Brotli without anyone deciding to. fetch() unpacks any response sent with Content-Encoding: br. Express 5 unpacks request bodies sent that way. axios asks servers for Brotli by default. The Node.js project fixed the bug as CVE-2026-111497 in its October 21, 2026 security release and rates it high, with remote code execution as the stated impact. Every supported line (22.x, 24.x, 26.x) is affected, and so is every end-of-life line.

This is our operator read of the Node.js October 2026 security advisory, the release notes and the two commits behind the fix. The CVE record and NVD entry were not public when we published.

How to fix

Upgrade the Node.js runtime. There is no npm package to bump: the decoder is compiled into the node binary.

# nvm
nvm install 24.22.1    # or 22.23.4, or 26.12.1

# Docker: pin the patched tag, rebuild, redeploy
FROM node:24.22.1-bookworm-slim    # node:22.23.4-*, node:26.12.1-* also published

# Confirm what actually runs (inside the container, not on your laptop)
node -v
You runUpgrade to
26.x26.12.1 (Current)
24.x24.22.1 (LTS)
22.x22.23.4 (LTS)
20.x or olderNo fix. Move to 22.x or 24.x.

Node 20 is not out of scope. It is out of support, so it stays vulnerable. Vercel says projects still pinned to Node 20 on its platform must switch to 22.x or 24.x and redeploy, while its managed 22.x and 24.x runtimes get the patch without a settings change.

One operator check

The fix added a new decoder error, and Node exposes it as a constant. That gives you a one-line test that does not rely on reading version tables:

node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH"
# -17        patched (22.23.4, 24.22.1, 26.12.1)
# undefined  vulnerable

Run it in every image and CI runner you ship, since a laptop on the right version says nothing about a base image pulled in August. After you upgrade, a patched process rejects the bad stream with error code ERR__ERROR_FORMAT_BLOCK_SWITCH. Seeing that code in your logs means someone sent that shape of stream, or a server you fetch from is sending broken data. Both are worth a look.

Where your app decompresses Brotli

Node's core http module does not decompress anything on its own. Vercel's bulletin puts it plainly: you are exposed when your code, or a library it uses, decompresses Brotli data an attacker controls. In practice that list is long:

  • fetch(). Node's built-in fetch comes from undici. Its fetch code pipes any br response through zlib.createBrotliDecompress(). A server you call, or anything that can sit in the middle of that call, chooses the encoding.
  • Express 5 and body-parser 2.x. body-parser added Brotli in 2.0.0 and decompresses request bodies by default. Express 5.x depends on body-parser 2.x, so express.json() and express.urlencoded() accept a Content-Encoding: br upload from anyone who can reach the route.
  • axios. In Node it sends Accept-Encoding: gzip, compress, deflate, br and decompresses the answer.
  • Self-hosted Next.js. Vercel says Next.js does not block Brotli responses in image optimization, so upstream image hosts are part of the path.
  • Your own zlib.brotliDecompress(), brotliDecompressSync() or createBrotliDecompress() calls, for example in a proxy, crawler, webhook receiver or file upload pipeline.

Upgrading undici, axios or body-parser in package.json does nothing for this bug. They all call the same decoder inside the runtime.

What the patch changed

The fix is upstream Brotli commit 0ed4268a, "Detect and report invalid stream earlier," which Node cherry-picked into deps/brotli. Brotli streams can switch between block types mid-stream. When a stream declares only one block type and then asks for a switch anyway, the decoder's DecodeBlockTypeAndLength noticed and returned false. On the fast decoding path, though, the block-switch wrappers were void functions, so that failure was thrown away and decoding carried on. The patch turns those wrappers into functions that return an error, adds error code -17 (BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH), and stops processing when it fires. A second Node commit exposes the constant and adds a 22-byte regression input to Node's test suite.

Google wrote that commit in February 2026. Brotli shipped it as a security release, 1.2.1 with 1.1.1 and 1.0.10 alongside, on October 21. Anything else that bundles its own copy of the Brotli decoder (Python bindings, native modules, other runtimes) needs its own update. This Node release only covers Node.

Who is not in scope

  • Code that only compresses. The patch touches the decoder (c/dec/decode.c) only. brotliCompress() and response compression middleware are not the entry point.
  • Apps that never decode attacker-reachable Brotli. A service that takes no compressed uploads and only fetches from servers you run is far less exposed. Once a third-party API or webhook is in the mix, few apps can claim the second part.
  • Vercel-managed functions on 22.x or 24.x. Vercel says it is rolling the patched runtimes itself. External backends, containers you build and CI runners are still yours to patch.
  • Browsers. Front-end JavaScript uses the browser's own Brotli, not Node's.

What this is not

It is not a confirmed RCE against stock Node in the wild. No exploitation has been reported and it is not in the CISA KEV catalog as of publication. Sources disagree on how far the public work goes: The Hacker News reports that a proof of concept published October 22 got code execution only on a deliberately weakened build, with a crash and a limited memory read on a standard one, while Vercel's bulletin says researchers showed memory disclosure and command execution on unpatched 22.x, 24.x and 26.x. A crash is still a remote denial of service on any route that decodes Brotli. Treat it as a patch-today bug, not a wait-for-NVD bug.

If you cannot upgrade today

The Node advisory offers no workaround. You can narrow the inbound side:

  • Pass { inflate: false } to express.json(), express.urlencoded(), express.text() and express.raw(). body-parser then rejects compressed bodies with a 415 instead of decoding them.
  • Drop or reject request bodies with Content-Encoding: br at your proxy or load balancer. Vercel says its WAF already blocks the inbound pattern for traffic through its network.

The outbound side has no clean lever. Sending your own Accept-Encoding without br asks a server not to use Brotli, but undici's fetch decodes whatever Content-Encoding the response claims, so a hostile server can ignore the request. Only the runtime upgrade covers responses you fetch.

Where HOL Guard fits

HOL Guard pauses risky actions by AI coding agents on developer machines. It does not patch the Node.js runtime or filter Brotli streams, so the fix is still the upgrade. We track sources, timeline and scope for this issue on the HOL Guard security campaign page for CVE-2026-111497.

The rest of the October 21 release

The same releases fix nine more CVEs. The ones worth reading if you run TLS clients against IP addresses or resolve user-supplied names:

  • CVE-2026-106069 and CVE-2026-105483 (high): TLS certificate hostname checks bypassed when a host is a non-canonical IP or an IP written in fullwidth digits. CVE-2026-105483 affects 24.x and 26.x only.
  • CVE-2026-105482 (high): dns.resolve truncated names at an embedded NUL byte.
  • CVE-2026-106074 (medium): crypto.randomUUID() and randomInt() repeat the same values in every process started from a user-land startup snapshot or single executable application.
  • CVE-2026-105481 and CVE-2026-106072 (medium), plus three low-severity issues in ZipFile and the Permission Model.

The release also blocks the eight compromised eMudhra intermediate keys, which we covered in our eMudhra CA key compromise write-up, and moves bundled undici to 7.30.1 on 24.x (6.29.1 on 22.x). For the earlier undici WebSocket issue, see our undici permessage-deflate write-up. One upgrade covers all of it.

References

Continue reading

All posts