JournalCyber Security

HTTP2Shell: How One Brotli Bug Became Four "Shells"

By Sandhu Santhakumar5 min read
HTTP2Shell: one Brotli bug, four names — Node2Shell, Python2Shell, HTTP2Shell, Broccoli2Shell, CVE-2026-111497

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.

Three renames in four days, one CVE: Node2Shell (21 Oct), Python2Shell and HTTP2Shell (23 Oct), Broccoli2Shell (25 Oct), exploit traffic seen (26 Oct)

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.

Vulnerable vs fixed decoder on the same crafted stream: an ambiguous return value lets the 8-byte buffer overflow; the fix returns BLOCK_SWITCH error -17 and stops

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.

Three doors into the same Brotli decoder: inbound request bodies, responses your server fetches, and files your pipeline opens; a WAF sees only the first

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:

  1. Restart everything. The old decoder stays in the running process until you do.
  2. Rebuild images and lockfiles. A Dockerfile pinned to node:24.22.0 stays vulnerable; check the version inside the running container.
  3. 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: br request bodies at your proxy (Cloudflare, Azure, Imperva and Vercel ship rules), or set express.json({ inflate: false }). Inbound only.
  • In Python, uninstall Brotli and brotlicffi where optional. This removes the decoder from both directions. Stripping br from Accept-Encoding does 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.

Sandhu Santhakumar

Written by

Sandhu Santhakumar

Technical Architect

Sandhu is a Technical Architect specializing in scalable cloud infrastructure, distributed architectures, and microservices. With deep hands-on experience in Java, Python, AWS, and modern DevOps practices, he designs and delivers high-performance systems that balance scalability, reliability, and real business impact. His work spans distributed systems design, integrating AI/ML capabilities into production, and architecting platforms built for long-term growth—not just short-term delivery.

Have a project in this territory?

Start a project