
Updated Oct 28, 2026. Status claims are accurate to that date and will move.
On Wednesday 21 October 2026, the Node.js project shipped a security release, and by the weekend the same bug had been renamed three times. Each new name marked another corner of the internet researchers found it could reach. What caught my attention was that the fix had been sitting in Google's Brotli repository since February. Nobody had labelled it a security issue, so almost nobody had pulled it in.
Brotli is supposed to be boring: compression, the thing that makes HTTP responses smaller in transit.
The problem is that the little C library that decompresses it, libbrotlidec, had a memory-corruption bug: feed it a carefully broken stream and it writes past the memory it was handed. Node.js, Python, Bun, Envoy and .NET all unpack Content-Encoding: br for you before your code sees a byte. It's CVE-2026-111497, rated 9.2 Critical.
One bug, four names
Wednesday, 21 October 2026: Node2Shell. Node.js shipped its fix (reported by OpenAI Codex Security); within a day Imperva had a shell on an unmodified Node build.
Friday, 23 October 2026: Python2Shell, then HTTP2Shell. Aikido and OpenSourceMalware showed the PyPI Brotli packages compile the same C decoder, and Python's HTTP clients load it automatically. By evening, anything decoding Brotli over HTTP was in scope.
Sunday, 25 October 2026: Broccoli2Shell. A tracker at broccoli2shell.com began cataloguing every project that bundles its own copy of the decoder, HTTP or not.

The names changed because researchers kept finding more ways to reach the same decoder. The bug didn't change at all. One CVE, and despite the name, nothing to do with HTTP/2; the "2" is a pun carried over from Node2Shell.
It is also not PHP2Shell, two unrelated bugs in PHP core (CVE-2026-109618/109619) fixed the same Wednesday.
So what actually breaks?
Think of the decoder as a small machine with a tiny scratchpad for bytes it hasn't processed yet. When it needs more data it says so, and the caller obliges. The vulnerable code had a problem: "this data is incomplete" and "this data is invalid" both came back as the same plain false. An attacker builds a stream that puts the decoder into that confused state, the caller reads the answer as "give me more", and the decoder keeps writing into a scratchpad that never empties. Eventually there's nowhere left to write.

The fix is surprisingly small: commit 0ed4268 gives the function a real error code and stops. It was written on 27 February 2026. Nobody connected it to code execution until October 2026, which is exactly why nobody pulled it in.
Why this is bigger than Node.js
Your application doesn't have to use Brotli. Something else in the stack does the decoding for you.
Someone sends you compressed data
Express 5 inflates br request bodies by default, so app.use(express.json()) puts the decoder in front of the request before your code sees it. If the route needs no login, that's anyone on the internet.
Your server fetches something
fetch(), HTTP clients and AI agents receive responses from servers they don't control, and many decode Brotli automatically. An inbound WAF won't see this path. It's the door I'd check first.
Your application opens a file
Brotli is also inside Parquet files and WOFF2 fonts, which reach the decoder with no network involved.

Same decoder. Different door.
Is this theoretical?
No.
Code execution: demonstrated. Imperva got a shell on an official, unmodified Node.js build on 22 October 2026.
Exploit traffic: observed. ELLIO saw scanning and a working exploit in the wild on 25–26 October 2026.
Confirmed compromise: none public. No victim had been reported, and the CVE was not in CISA's KEV catalogue, as of 28 October 2026.
If you're responsible for a production system
| Component | Affected | Fixed in |
|---|---|---|
| Google Brotli (C library) | 1.0.3 – 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
| Node.js 26 / 24 / 22 | below the fix | 26.12.1 / 24.22.1 / 22.23.4 |
| Node.js 25, 23, 20 and older | all | End of life: no security release |
| Bun | 1.0.21 – 1.4.3 | 1.4.4 |
Python Brotli / brotlicffi |
1.2.0 / 1.2.0.x and earlier | 1.2.1 / 1.2.1.0 |
Also fixed: .NET 8.0.33 / 9.0.22 / 10.0.14, Ruby, PHP, Envoy and Brotli4j. DuckDB, ClickHouse and Arrow still had no release on 26 October 2026; broccoli2shell.com keeps the list.
First, upgrade to a fixed release and verify what shipped: node --version (22.23.4+, 24.22.1+, 26.12.1+), bun --version (1.4.4+), pip show Brotli brotlicffi (1.2.1 / 1.2.1.0). Check the runtime version, not process.versions.brotli, which Node left unchanged. Then:
- Restart everything. The old decoder stays in the running process until you do.
- Rebuild images and lockfiles. A Dockerfile pinned to
node:24.22.0stays vulnerable; check the version inside the running container. - Retire end-of-life runtimes. Node 20, 23 and 25 aren't getting a fix.
Scanners miss bundled copies and flag patched distro packages whose version string never changed, so re-run pip-audit and Dependabot too.
- If you can't patch today, reject
Content-Encoding: brrequest bodies at your proxy (Cloudflare, Azure, Imperva and Vercel ship rules), or setexpress.json({ inflate: false }). Inbound only. - In Python, uninstall
Brotliandbrotlicffiwhere optional. This removes the decoder from both directions. StrippingbrfromAccept-Encodingdoes nothing; a hostile server sends Brotli anyway.
The bug isn't the interesting part
The interesting part is how long it sat there. The fix existed in February 2026; nobody knew it was a security fix until October 2026. You don't choose the vulnerable code. You inherit it.
A public Express API is a very different problem from an offline batch job decompressing your own files, and a version scanner can't tell them apart. Same CVE. Same 9.2. Very different risk.
So the question isn't just "am I running the vulnerable version?" It's "can an attacker get data into the vulnerable code?" That's the one I'd ask first.
Sources: CVE-2026-111497 · GHSA-9v8h-qqgm-4m35 · Node.js · Microsoft · Imperva · ELLIO · Aikido · Tenable.