Node.js Brotli Flaw Draws a Working Exploit One Day After Patch
Node.js shipped fixes on 21 October 2026 for a memory-corruption flaw in its bundled Brotli decoder, and Imperva demonstrated a working exploit against an official build the next day. CVE-2026-111497 affects the 22.x, 24.x and 26.x lines.
A flaw in Node.js's bundled Brotli decoder let attacker-supplied data run code inside the process. The fix shipped on 21 October 2026. A working exploit against the official Linux arm64 image landed the next day.
Node.js Brotli Flaw Draws a Working Exploit One Day After Patch
Washington, D.C. — The patch landed on a Wednesday. A working exploit against an official build landed the next day. What sits between those two dates is the part nobody planned for.
A Decoder Nobody Watched
Node.js does not decode Brotli on its own. That is the first thing to understand. The runtime ships a copy of Google's Brotli decoder, and that copy only runs when application code calls it. Vercel's bulletin states the exposure plainly: "Node.js does not decode Brotli data on its own. Your application is exposed when its code, or a library it uses, decompresses Brotli data an attacker controls, whether in a request body or in a response it fetches."
The decoder lives in a C file, c/dec/decode.c, inside a vendored dependency. The CVE record published by Google as the numbering authority lists two weakness types: CWE-787, out-of-bounds write, and CWE-252, unchecked return value. A buffer overflow in that decoder is the whole of the defect.
Vendored native code is the pattern here. The upstream project fixed it. The downstream runtime carried its own copy and shipped that fix months later. If you run a Node service, this one is yours.
Eight Days From Warning to Working Exploit
On 14 October 2026, Antoine du Hamel posted a pre-alert to the oss-security list. New 22, 24 and 26 releases would land on or shortly after 21 October, with the highest severity rated High. No component names. No CVE numbers. That is normal for Node.js.
The releases arrived on 21 October 2026, a Wednesday: v22.23.4, v24.22.1 and v26.12.1. Ten vulnerabilities fixed, four High, three Medium, three Low. The advisory credits the report of the Brotli flaw to Khai Tran and Sylvie Mayer of OpenAI Codex Security, and the fix to Matteo Collina and Antoine du Hamel.
Imperva published its analysis on 22 October 2026, the next day. Ron Masas of Imperva Threat Research named it Node2Shell, and describes Node2Shell as its analysis of CVE-2026-111497, not a second vulnerability.
Eight days from the pre-alert to Imperva's public working exploit. That is the timeline.
What the Decoder Got Wrong
Imperva's root-cause account is specific. The stream announces the block types it will use as decoding runs. The test input reached a block change that the stream's own structure ruled out. The vulnerable decoder read that as a request for more input and kept going in an invalid state.
A separate memory-sanitizer run showed what happens next. Continuing from that state wrote past an intended buffer.
The upstream fix is a cherry-pick of google/brotli commit 0ed4268a4401abcb44720963, whose message reads "Detect and report invalid stream earlier." Node.js imported that patch. The decoder now stops with a format error instead of continuing.
The advisory lists no workaround inside Node.js. There is no configuration flag that closes this. The patch is the remedy.
Two Doors Into the Same Room
There are two paths to the decoder, and they are not the same path.
The inbound door: Express 5's body parsers — express.json(), express.urlencoded(), express.text() and express.raw() — decode Brotli request bodies when applied to a route, alongside gzip and deflate. A plain Node.js HTTP server does not decode a Brotli request body just by receiving one.
The outbound door: your code fetches a response, and that response carries a compressed Brotli body. Node decodes it. Vercel's wording is exact: "Only the runtime patch covers fetched responses."
That second door is why a web application firewall is not a fix. Vercel says its WAF rules, deployed the same day, "block the inbound attack vector for every project on Vercel," and that a WAF "reduces exposure but does not replace patching." It does not cover responses your functions fetch from other servers.
A Score of 9.2 and a Label That Says High
The severity is disputed. Both labels are in the record, and neither is the whole truth.
The CVE record published by Google scores it CVSS v4.0 9.2, critical. The vector is CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N — network attack vector, no privileges required, no user interaction.
The Node.js advisory rates the same flaw high and publishes no score. Nopal Cyber's advisory notes the split directly: Google's numbering authority scores 9.2 critical while Node.js rates it high with no score attached.
NVD had published no CVSS 3.1 score, status "Received," as of 22 October 2026. The NVD entry was created at 14:17 UTC that day, 42 minutes after the CVE record appeared at 13:35 UTC.
Two scoring authorities. Two labels. The record does not resolve them.
The Exploit That Took Several Tries
Exploitability moved in stages, and the stages matter.
The first public proof of concept reached code execution only on a deliberately weakened, non-PIE test build. On a standard build, its author reported a crash and a limited read of process memory, not code execution, and said a working attack on a normal build was not achieved. A second public repository claims remote code execution, but its documented output shows only the decoder continuing past an invalid block switch.
Then Imperva's Node2Shell ran code on the official Node.js 24.22.0 Linux arm64 image. No debugger. No instrumented binary. The same inputs were rejected by 24.22.1. In the working proof of concept, the test application had no code to create Imperva's marker file. After it received the Brotli data, the file appeared.
Imperva states execution "took multiple attempts in our setup." It did not test 22.x or 26.x at all, and tested Express 5.0.0 to 5.3.0 with body-parser 2.0.0 to 2.3.0 at pinned versions rather than every release in between.
The Fix That Waited 236 Days
The upstream change first went into Brotli's source on 27 February 2026. It did not ship in a Node.js security release until 21 October 2026. That is 236 days.
Brotli 1.2.1, 1.1.1 and 1.0.10 came out the same day as the Node.js release. The CVE record lists affected upstream ranges: 1.0.3 before 1.0.10; 1.1.0 before 1.1.1; 1.2.0 before 1.2.1.
Node.js 24.22.0 was released on 14 October 2026 with Brotli 1.2.0, which the CVE lists as affected. The fix arrived a week later in 24.22.1.
Sesame Disk names the pattern: the upstream fix was available for roughly eight months before Node's security release carried it, the normal failure mode of vendored native dependencies. A fix for a memory-safety bug sat in a public repository for 236 days, and an AI security tool is credited with the finding.
Patching does not change a running process. TuxCare states the constraint: "a running process keeps the old decoder in memory."
The Lines That Get Nothing
Node.js 20 reached end of life and will not receive this fix. Vercel projects on 20.x must change version and redeploy.
The advisory is blunt about the rest: "It's important to note that End-of-Life versions are always affected when a security release occurs."
Hosting accounts carry a second layer of delay. CloudLinux ships Node.js to hosting accounts through Node.js Selector as alt-nodejs packages, plus ea-nodejs22 on cPanel. A fix from nodejs.org reaches those accounts only when CloudLinux rebuilds the packages and the account installs them.
CloudLinux states: "Updating the system brotli package does not help, because alt-nodejs packages carry their own copy of Brotli." At last update, 31 of 33 CloudLinux builds and 76 of 99 TuxCare builds had released. Still pending: alt-nodejs22 for CloudLinux 7 and the standard EasyApache 4 build of ea-nodejs22.
Debian's own tracker listed its nodejs and brotli packages as vulnerable and unfixed in unstable at the time of review. On a shared server, the Node.js version belongs to each account's application, not to the host.
The Second Fix Buried in the Same Release
Eight emSign intermediate CA certificates were revoked for CA compromise on 18 October 2026. Six issue TLS certificates. Two sign code.
Their P-384 private keys were whole numbers below 256 — roughly eight bits of strength. The key for "EM OV TLS CA - G3C" had the value 4. Scott Helme recovered all eight on a laptop in under a second: 4, 56, 66, 115, 155, 156, 230 and 237.
The eight intermediates came out of an eMudhra key ceremony on 30 April 2025 using hardware security modules. eMudhra's account on Mozilla bug 2083449 blames a flaw in the ceremony script that created a second, weak set of keys and similar labels that led staff to use the wrong pair.
Node.js added a key-based blocklist for the eight intermediates in the same 21 October release. The disclosure analysis warns that Debian 12 and 13, and therefore the official node:22, node:24 and node:26 images, ship AAA Certificate Services as a TLS-trusted root, and all eight cross-signed copies verify against it. Removing the emSign roots changes nothing for that path.
No malicious use was confirmed as of 21 October 2026, though the keys had been recoverable since April 2025 — 535 days before the discovery.
What to Do Before You Close This Tab
Verify the runtime, not the build file. Run node -v inside the container image, serverless layer, build agent or host that actually executes the service. A package inventory can miss a Node binary copied into /usr/local/bin, and a base-image tag can point at a rebuilt layer.
Do not verify by reading the bundled Brotli version string. Sesame Disk states it directly: "Node 24.22.1 still reports Brotli 1.2.0 because the project cherry-picked the fix rather than bumping the vendored version. The reliable marker is the Node.js release: 22.23.4, 24.22.1, or 26.12.1 and newer."
Inventory running processes with ps -eo pid,lstart,args | grep -E '[n]ode( |$)', then restart. Check body-parser's inflate option, which defaults to true; setting inflate: false rejects compressed bodies instead of decoding them.
Check anything that used crypto.randomUUID() or crypto.randomInt() from a startup snapshot or single executable application. TuxCare warns those entropy caches were saved into the artifacts, so every process launched from the same binary produced the same values. Anything using those UUIDs as tokens or nonces deserves a look.
No exploitation in the wild had been reported as of 22 October. The CVE is not in the CISA Known Exploited Vulnerabilities catalog. Patch anyway.
By Jessica Ali, Staff Writer
This article was produced with AI-assisted research and editorial support. Sources: the Node.js project security advisory for 21 October 2026; the CVE record published by Google as numbering authority; Imperva Threat Research; The Hacker News; Vercel; TuxCare; Nopal Cyber; CloudLinux; Sesame Disk; and, for the eMudhra item, Bleeping Computer and the Mozilla bug tracker. Photographs: data-centre server racks by BalticServers.com, CC BY-SA 3.0; a security analyst at a workstation by the U.S. Diplomatic Security Service, public domain; source code on a monitor by Markus Spiske, CC0. All three cropped to 16:9.
What's Your Reaction?
Like
0
Dislike
0
Love
0
Funny
0
Wow
0
Sad
0
Angry
0
Comments (0)