Broccoli2Shell Is the Node.js Bug Your Dependency Scanner Can't See
OpenAI's Codex Security reported a remote-code-execution flaw in the compression decoder inside Node.js. Fixing it means rebuilding runtimes, not bumping packages, and one common check gives the wrong answer.
Most security alerts arrive with a package name attached. Your scanner reads the lockfile, finds some-library@1.4.2, and opens a pull request that bumps it to 1.4.3. You merge it and move on.
Broccoli2Shell doesn't work like that. The bug sits in a piece of C code that ships inside the Node.js binary itself, so it never appears in your package.json. Upgrading the web framework that triggers it does nothing. And on at least one patched Node release, the version number you'd naturally check still reports the old, affected library.
That gap between "fixed upstream" and "fixed on your machine" is the real story here. It matters more every month, because AI tools are now finding bugs like this one faster than the rest of the supply chain ships the fixes. The short version: get every Node to 22.23.4, 24.22.1 or 26.12.1, and check it by the Node version.
What Broccoli2Shell actually is
Start with the name. Brotli is a compression format, the same family of idea as gzip: a web server squeezes a response down before sending it, and the client unpacks it on arrival. The code that does the unpacking is called the decoder.
Broccoli2Shell, tracked as CVE-2026-111497, is an out-of-bounds write in Google's Brotli decoder. Picture a form with an eight-character box, and a pen that keeps writing after the box ends, over whatever is printed next to it. That's an out-of-bounds write. In a program, the "whatever is next to it" is other data the process depends on.
The Node.js advisory describes it precisely: an invalid block switch "was reported as a temporary need for more input instead of a permanent format error, so the streaming decoder kept appending input bytes to an eight-byte internal buffer." An attacker who can feed malformed Brotli data to an app that decompresses it with node:zlib "can corrupt process memory, which can lead to a crash, disclosure of process memory, or remote code execution."
Node rated it high. The GitHub advisory scores it 9.2 out of 10, critical, on the CVSS 4.0 severity scale, and lists Brotli 1.0.3 through 1.2.0 as affected.
The credit line is worth reading too. Node thanks "OpenAI Codex Security - Khai Tran and Sylvie Mayer for reporting this vulnerability." Codex Security is OpenAI's security product, and the advisory doesn't say how much of the finding was the tool and how much was the two researchers. Either way, the report was the fast part. People still had to fix it, release it, and get it onto your servers.
Why this one reaches agents
You might think compression bugs are a server problem. The exposure list says otherwise.
According to the Broccoli2Shell tracker, "Node's built-in fetch() decodes Content-Encoding: br responses automatically." Python's requests/urllib3 and aiohttp do the same "whenever a Brotli package is installed," and httpx does it through its httpx[brotli] extra.
Think about what a browsing agent or a web-fetch MCP tool (a plug-in that gives an AI agent a "fetch this URL" ability) does all day. It requests URLs that a model picked, often from search results, and reads whatever comes back. If that tool runs on Node's built-in fetch(), or on a Python client with a Brotli package installed, then a hostile server can choose the compressed bytes your process unpacks. Under the tracker's description, that's the exact condition the bug needs.
Servers get hit from the other side. The tracker says "Express 5.0.0–5.3.0 and body-parser 2.0.0–2.3.0 inflate Brotli request bodies in parsers such as raw() and json()," so a request body can carry the payload in. Then comes the line that breaks the usual playbook: "Express and body-parser don't contain the bug. They're just paths to it, so upgrading them won't help."
The fix lives in three places, none of them your lockfile
Because the decoder is bundled, the fix has to land wherever the decoder was copied.
The Node.js binary. Node shipped 22.23.4, 24.22.1 and 26.12.1 on October 21, per its security release post. The tracker says lines 25, 23, 20 and older get no fix because they're end of life: "move to 22, 24 or 26." If you're still on Node 20 somewhere, this is the week that stops being a backlog item.
Python wheels. The Python Brotli package reached 1.2.1 on PyPI on October 22 (UTC), and brotlicffi reached 1.2.1.0 the same day. Google tagged the C library itself as 1.0.10, 1.1.1 and 1.2.1 on October 21.
Container images. This is the one teams forget. The tracker puts it plainly: "a container keeps it until you rebuild the image on a patched base and redeploy." A Dockerfile that says FROM node:24 and was built two weeks ago is still running the old binary. The page's example pins a patched release, FROM node:24.22.1-slim.
The tracker also lists the .NET runtime, PHP's brotli extension and DuckDB among projects that bundle the decoder, some still unfixed as of today. If your stack embeds Brotli somewhere else, your package manager may have nothing to tell you.
The version string that lies
Here's the part that turned up while checking this story.
The tracker suggests two Node checks: node --version and node -p "process.versions.brotli". The second one reads like the more direct answer, since it reports the Brotli library version Node was built with. On a patched Node, it isn't.
Running node -p "process.version + ' ' + process.versions.brotli" on Node v22.23.4, a fixed release, prints:
v22.23.4 1.1.0
Brotli 1.1.0 sits inside the affected range (1.0.3 through 1.2.0). The reason is in the v22.23.4 release notes: Node didn't upgrade its bundled Brotli to 1.1.1. It cherry-picked the fix, meaning it copied that one commit onto its old copy of Brotli, listed as "deps: brotli: cherry-pick 0ed4268a4401abcb44720963." The code changed. The version string didn't.
So a script that flags any box whose process.versions.brotli is below 1.1.1 will mark patched Node 22 servers as vulnerable, and it can't tell them apart from unpatched ones. Check the Node version. That's the number that tells you whether the cherry-pick is in. I only ran this on Node 22.23.4; check your own 24 and 26 builds before you trust any reading of that field.
Eight months between the fix and your server
One more detail explains why "AI finds the bug" doesn't mean "you're safe sooner."
The upstream fix commit is titled "Detect and report invalid stream earlier." Nothing in it says security. Per the tracker, citing OpenSourceMalware's analysis, the fix first landed in the Brotli source in February 2026. The runtimes most people actually run got it on October 21.
That's not anyone being lazy. A fix in an upstream C library has to be noticed, released, picked up by every project that vendors a copy (keeps its own copy of the source inside its repo), built into new binaries, packaged into base images, and finally rebuilt into your containers. Each hop is a separate team.
OpenAI's Patch the Planet post from October 21 says "we expect similar findings to increase sharply in the coming months." If that holds, the bottleneck is the chain of rebuilds after the finding, and the only part of that chain you control is your own.
The lesson fits in one line: patch the runtime, not the package. For bundled code, the fix is a version of Node or a Python wheel, and your rebuild is the last hop.
Put this into practice
None of this needs special tooling. It needs you to look in the places your scanner doesn't.
-
Check every Node you run. Run
node --versionon servers, CI runners, build images, serverless runtimes and laptops (the tracker lists all of these). You want 22.23.4, 24.22.1, 26.12.1 or newer in those lines. Anything on 20, 23 or 25 has no patched release. Ignoreprocess.versions.brotlifor this decision. -
Check Python environments. Run
pip show Brotli brotlicffiin each virtualenv and image. If either is installed, you wantBrotli1.2.1 orbrotlicffi1.2.1.0. Per the tracker,requestsandaiohttponly decode Brotli when one of them is installed. If your Python came from an operating-system package manager instead of PyPI, check your distribution's own security advisory, because distributions often backport fixes (apply the patch to an older version without changing its number), andpip showwon't tell you whether that happened. -
Rebuild and redeploy images. Pin the base to a patched tag (
FROM node:24.22.1-slimor your line's equivalent) instead of a floatingnode:24(or pin the image digest, since tags can be moved), rebuild, and redeploy. A merged Dockerfile change does nothing until the new image is running. -
Look at your agents' fetch tools first. Any tool that fetches model-chosen URLs is reading attacker-controlled bytes by design. Make sure those processes run on patched runtimes before anything else, and, as extra defense rather than a verified fix, consider whether they need to accept Brotli responses at all.
-
Search for other embedded copies. The tracker's list includes .NET, PHP's brotli extension and DuckDB. Grep your images and vendored folders for Brotli, then check those projects' own advisories.
Expect step 1 to turn up at least one surprise. A CI runner image nobody has touched since spring is a classic.
What we don't know yet
Be honest about the limits of this picture.
Exploitation is real but not easy, as far as public reporting goes. The tracker says a public proof of concept appeared on October 22 and "reportedly reaches code execution only on a deliberately weakened build." It also reports that Imperva ran attacker-supplied code on an unmodified Node.js 24.22.0 image after several attempts and hasn't released that exploit. As of October 22 the tracker reported no attacks in the wild. That can change without warning.
The Python exposure details come from the tracker rather than from each client library's own docs, and client behavior can depend on version and configuration. Treat the list as a starting map, not proof that a given service is safe.
The version-string finding comes from one release (Node 22.23.4) checked in one environment. The cherry-pick explains it, but Node 24 and 26 may differ.
And look at what the GitHub advisory lists. It was reviewed on October 23 and names two affected packages, the pip packages brotli and brotlicffi. There's no npm entry, because there's no npm package to bump: for Node, the fix is the runtime. A scanner that only reads your lockfile has nothing to match on.
Where this leaves you
You can't control how fast an AI tool finds the next decoder bug, or how long it takes to travel from an upstream commit to a Node release. You can control the last hop. Know which runtimes you run, check them by the number that actually reflects the fix, and rebuild the images that froze an old binary in place. Your scanner will catch up eventually. Your servers don't have to wait for it.
Sources: Node.js October 2026 security releases; Node.js v22.23.4 release notes; GitHub Advisory GHSA-9v8h-qqgm-4m35; Google Brotli releases and fix commit 0ed4268; Broccoli2Shell tracker (updated October 25, 2026); PyPI Brotli 1.2.1 and brotlicffi 1.2.1.0; OpenAI, Patch the Planet. The process.versions.brotli reading was taken on Node v22.23.4 on October 25, 2026.