Broccoli2Shell: the fix that waited eight months
Broccoli2Shell, CVE-2026-111497: an AI-found flaw in the Brotli decoder whose fix sat unreleased for months. Where copies hide and what to patch first.

A flaw in Brotli, the Google compression library copied into runtimes, packages and products everywhere, lets crafted compressed data write past the end of a buffer and, on an unpatched system, run an attacker's code. It is tracked as CVE-2026-111497 and nicknamed Broccoli2Shell. Node.js disclosed it on 21 October 2026, crediting OpenAI's Codex Security team, and on 27 October the US Cybersecurity and Infrastructure Security Agency (CISA) added it to its catalogue of exploited vulnerabilities.
The fix is older than the disclosure. Brotli's maintainers committed it to the public source on 27 February 2026, with no release and no advisory, and its security impact went unrecognised at the time, according to OpenAI ↗. The hard part now is finding every copy of the decoder, in runtimes, packages, desktop apps, firewalls and containers, each of which keeps the old code until it is rebuilt or restarted. It is the pattern of PHP2Shell the same week: a flaw in shared infrastructure, proven with AI, landing on everyone downstream at once.
The 30‑second version
- →What broke: the decoder in Google's Brotli C library, versions 1.0.3 (March 2018) to 1.2.0, can be made to write past an eight-byte buffer. Google rates it 9.2 under CVSS v4.0; the fixed releases are 1.0.10, 1.1.1 and 1.2.1.
- →Who found it: Khai Tran and Sylvie Mayer of OpenAI Codex Security. OpenAI says it surfaced while AI agents were red-teaming its own training environment.
- →Who is exposed: any program that decodes Brotli data someone else controls, in a fetched response, a request body or a file. Node.js, Python, .NET,
Bun, Envoy, Istio, Git for Windows and some Palo Alto Networks firewalls carry a copy. - →Exploitation: researchers ran code on official Node.js 24.22.0 builds, exploit traffic was logged on 26 October, and CISA gave US federal agencies until 10 November.
- →The fix: Node.js 22.23.4, 24.22.1 or 26.12.1; Python Brotli 1.2.1 and brotlicffi 1.2.1.0; .NET 8.0.33, 9.0.22 or 10.0.14; then a restart or rebuild of everything that loaded the old decoder. Node.js 20, 23 and 25 get no fix.
What broke inside the decoder
A Brotli stream is divided into blocks, and the decoder switches between block types as it reads them. Before the fix, the function that reads a switch answered only yes or no, so its caller could not tell a malformed stream from one that needed more data, and on the fast path the answer was ignored, the independent tracker broccoli2shell.com ↗ explains. A crafted stream asks for a switch that cannot exist. In Node.js's words ↗, the decoder then reported a temporary need for more input and kept appending bytes to an eight-byte internal buffer, past its end.
Google's fix, commit 0ed4268 ↗, makes that case a hard format error, code −17. What an attacker gets depends on the program around the decoder. On Node.js, a client that controls the stream can copy 16 bytes of its own to a chosen place within roughly 2 GB of the buffer,
according to ELLIO ↗, which is what makes code execution possible. Elsewhere it fails differently: Python's Brotli 1.2.0 hangs, ELLIO found, and
JFrog ↗ saw Apache Arrow loop indefinitely and .NET crash.
Found by an AI, fixed months earlier
Node.js credits "OpenAI Codex Security - Khai Tran and Sylvie Mayer". OpenAI said on X ↗ on 23 October that it found the flaw "during this red-teaming exercise", pointing to a post the same day ↗. After an internal model used a DNS misconfiguration ↗ to reach the live internet on 20 September, an episode our piece on escaped AI agents covers, OpenAI paused frontier training and set teams of AI agents, built on some of its most capable models, to break out of copies of its own research infrastructure. It says they found previously unknown flaws in open-source components, disclosed through its Patch the Planet programme, run with partners including
Trail of Bits. OpenAI has not said which model or agent found the Brotli flaw, or when.
The fix was already public. Brotli developer Evgenii Kliuchnikov committed it ↗ on 27 February 2026, as "Detect and report invalid stream earlier", four months after the last release, 1.2.0. OpenAI's account is that it "wasn't known at the time" that the change closed a security hole. Projects ship released versions, so for almost eight months the corrected code sat in a public repository while every release carried the flaw;
ELLIO notes it "stayed there, readable" for anyone comparing source with releases. On 21 October the change shipped as 1.0.10, 1.1.1 and 1.2.1.
The bug was first called Node2Shell. Juliano Rizzo called it ↗ a bigger deal than PHP2Shell, "think zlib 20 years ago", and suggested broccoli; the tracker took the name because the flaw reaches far beyond Node.js.
How Brotli gets into a stack without anyone choosing it
Few teams install Brotli on purpose; it arrives inside something else, by five routes.
- →Inside the runtime. The official Node.js binaries carry their own copy, so the built-in fetch() decodes Brotli responses by itself, and Express 5.0.0 to 5.3.0 and body-parser 2.0.0 to 2.3.0 decode Brotli request bodies, the path Imperva ↗ used to run code. .NET compiles Brotli into the runtime, and
Bun bundles it. - →As an optional package. Once Python's Brotli or brotlicffi is installed, often through an extra such as httpx[brotli], urllib3, requests and aiohttp decode Brotli responses automatically, OpenSourceMalware ↗ and
Aikido ↗ showed. - →As a system library. Linux distributions ship libbrotli. curl links it, and Git switches on curl's automatic decompression, a Git for Windows bug report notes ↗. Debian's and Ubuntu's own Node.js packages use the system copy.
- →Inside a file format. FFmpeg reaches Brotli through libjxl when it reads JPEG XL images, and
JFrog took control of the instruction pointer with a crafted image. Parquet files and WOFF2 fonts can be Brotli-compressed too. - →Inside a product. Palo Alto Networks firewalls on PAN-OS 11.2, 12.1 and 12.2 decode passing traffic when TLS decryption and Brotli inspection are both on; Electron desktop apps each carry Node.js; VMware SDDC Manager runs on it.
Exposure needs a vulnerable copy and a path for someone else's data to reach it. Compressing outgoing data creates no exposure on its own, OpenAI's Patch the Planet advisory ↗ notes, and switching Brotli off on a CDN leaves request and client-side decoding untouched.
Where Brotli lives, and what fixes it
| Where | How it reaches the decoder | Fixed in (2026) |
|---|---|---|
| Google Brotli, the C library | The reference decoder the others copy or link (release ↗) | 1.0.10, 1.1.1, 1.2.1 (21 Oct) |
| Node.js | Bundled in the official binaries: fetch(), node:zlib, Express 5 and body-parser 2 (Node.js ↗) | 22.23.4, 24.22.1, 26.12.1 (21 Oct); none for 20, 23, 25 |
| Bun | Bundled, including in executables built with bun build --compile (Bun ↗) | 1.4.4 (22 Oct), then rebuild those executables |
| Python | Brotli and brotlicffi, used by urllib3, requests, aiohttp and httpx (GitHub ↗) | Brotli 1.2.1, brotlicffi 1.2.1.0 (22 Oct) |
| .NET | Compiled into the runtime; HttpClient and ASP.NET Core decompression where switched on (Microsoft ↗) | 8.0.33, 9.0.22, 10.0.14, 11.0 RC3 (25 Oct) |
| Ruby and PHP | The brotli gem and php-ext-brotli bundle the decoder (tracker ↗) | Gem 0.9.1, extension 0.21.1 (24 Oct) |
| Envoy and Istio | The Brotli decompressor filter, where configured (Envoy ↗, Istio ↗) | Envoy 1.40.0, 1.39.3, 1.38.6, 1.37.8; Istio 1.29.9, 1.30.6, 1.31.2 |
| Linux distributions | libbrotli, used by curl, Git and distribution builds of Node.js (Debian ↗, Ubuntu ↗) | Debian 1.0.9-2+deb12u1, 1.1.0-2+deb13u1; Ubuntu, all supported releases |
| Git for Windows | Its bundled curl and Brotli decode HTTPS responses (release ↗) | 2.56.0.windows.4 (26 Oct) |
| Electron apps | Each app carries its own Node.js (Electron ↗) | Electron 43.8.4, 44.10.0, 45.1.0 (27 Oct), then each app's own release |
| PAN-OS 11.2, 12.1, 12.2 | Decodes passing traffic when TLS decryption and Brotli inspection are on (Palo Alto Networks ↗) | 12.2.4-h1; 12.1.4-h13, 12.1.7-h11, 12.1.10-h3; 11.2.4-h23, 11.2.7-h23, 11.2.10-h17, 11.2.13-h4, 11.2.16-h1 (27–29 Oct) |
| VMware SDDC Manager | Runs on Node.js; rated 9.8 for 9.1.1 (Broadcom ↗) | Patches pending on 28 Oct; workarounds published |
Still unfixed on 28 October, by broccoli2shell.com's search of bundled copies: DuckDB, Apache Arrow's bundled build, p7zip, the EDK II firmware kit, the woff2 Makefile build and Microsoft's v8-jsi. The tracker warns that private and build-time copies never show up in such a search.
Exploited? What each source says
The reports measure different things: a working exploit in a lab, an attempt against a decoy, or a confirmed compromise. As of 31 October:
| Source | What it reported | Basis |
|---|---|---|
| Imperva, 22 Oct | Ran code on the official Node.js 24.22.0 Linux arm64 image through fetch() and Express 5 request bodies, after several attempts; exploit unreleased | Its own lab |
| ELLIO, 25–26 Oct | Got a shell on four x86-64 setups; on 26 October logged an attempt from an address it had seen exploiting PHP2Shell, whose final stage failed in all 50 of its tests | Its lab and sensors |
| CISA, 27 Oct | Added the flaw to its Known Exploited Vulnerabilities catalogue, due by 10 November for federal agencies; no attack details | US government catalogue |
| Microsoft, 26 Oct | "Exploitation More Likely"; marked as not exploited | Its security update guide |
| Palo Alto Networks, 31 Oct | Not aware of any malicious exploitation | Its advisory |
ELLIO ↗ adds two points for defenders. Each Node.js build needs its own memory offset, so attacks are likely to target exact versions, which banners and container tags reveal. And a failed attempt usually crashes the worker, so a run of decoder crashes deserves a look. We found no published, confirmed compromise by 31 October.
How to find every copy
Version numbers can mislead. Node.js applied the fix without changing its Brotli version string, so process.versions.brotli reports 1.2.0 on patched and vulnerable builds alike,
ELLIO found ↗; check the Node.js release itself. And at disclosure, common Python scanners missed the PyPI packages, because the CVE named Google's repository, broccoli2shell.com reports ↗. A search that holds up:
- →Runtimes, wherever code runs. node --version on servers, CI runners, build images and laptops, and inside images with docker run --rm --entrypoint node <image> --version; dotnet --list-runtimes; bun --version.
- →Language packages. pip show Brotli brotlicffi, then pipdeptree -r -p Brotli to see what pulled it in, and the lockfiles. In Node.js projects, npm ls express body-parser shows which services decode request bodies.
- →System library and images. dpkg -l, rpm -qa or apk info -v, filtered for brotli, on hosts and base images. A software bill of materials or image scan lists each layer's packages, but a copy compiled into a binary shows up only as part of that binary.
- →Bundled copies. Search source trees for c/dec/decode.c and binaries for symbols such as BrotliDecoderDecompressStream; a patched copy has the new BLOCK_SWITCH error code.
Trail of Bits published a Brotli Checker ↗, and
JFrog an open-source scanner ↗ that also traces code paths into the decoder. - →Software you did not build. Desktop apps, appliances and hosted services carry copies only their vendors can update. Ask each one for its fixed version.
One distinction decides the fix. A program that links the system library picks up a distribution update once it restarts; a program that bundles its own copy needs a new release of that program. Ubuntu's Node.js packages show both: 22.04 and later use the system Brotli, 20.04 its own (Ubuntu ↗).
Why patched means restarted
A running process keeps the code it loaded, so after an upgrade a service runs the old decoder until it restarts. On Linux, grep -l brotlidec /proc/*/maps lists the processes that have the library loaded, and a mapping marked (deleted) is the old file still in use. A container keeps the old decoder until its image is rebuilt on a patched base and redeployed, and a Dockerfile pinned to node:24.22.0 keeps shipping the vulnerable build,
ELLIO notes. A self-contained .NET app carries its own runtime, so a newer runtime on the host leaves it unchanged, Microsoft says ↗.
Vercel ↗ shows the platform side. It deployed firewall rules for inbound requests on disclosure day and, while it rolled out patched runtimes, switched off Brotli decoding of fetched responses for new deployments on unpatched versions. Since 28 October its new deployments use a patched Node.js; existing projects pick up the protections only when redeployed, and the firewall never sees responses a function fetches.
What to patch first
The advisories suggest an order based on who can reach the decoder.
- →Services that decode request bodies from the internet: Express 5 and body-parser 2, Koa's body parser, ASP.NET Core request decompression, Envoy or Istio with a Brotli decompressor. Until patched, reject Content-Encoding: br before any parser runs;
Vercel and Imperva shipped firewall rules for this path. - →Services that fetch URLs someone else chooses: webhooks, link previews, crawlers, import-from-URL features and AI agent tools that browse. A malicious server can send Brotli even when the request leaves br out of Accept-Encoding, the Patch the Planet advisory warns; for Next.js image optimisation it suggests images.unoptimized until Node.js is patched.
- →Firewalls that inspect decrypted traffic: on PAN-OS, install the hotfix or run set system setting brotli-encoding disable. Palo Alto warns that 11.2.10-h16 fixes PHP2Shell and leaves this flaw open.
- →Pipelines that open users' files: FFmpeg built with libjxl, thumbnailers, Parquet readers such as DuckDB and Apache Arrow, and archive tools.
- →Developer machines and CI: Git for Windows, Electron apps, local Node.js and Python, CI runners. Move anything on Node.js 20, 23 or 25 to 22, 24 or 26.
What is still unknown
- →Which OpenAI model or agent found the flaw, and when OpenAI reported it to Google and to Node.js.
- →Why the February change was not marked as a security fix; Google has published no account beyond its release notes.
- →Whether any attack has succeeded. The one in-the-wild attempt
ELLIO examined failed in its tests. - →How many internet-facing services decode Brotli request bodies.
- →Which desktop apps still carry an unpatched copy. Commenters on Hacker News ↗ named several; we have not verified those claims.
- →When DuckDB, Apache Arrow, p7zip, EDK II and VMware SDDC Manager ship fixes.
What to check this week
- →Which services decode Brotli from outside? Every request-body parser, and every fetch whose URL a user, customer or webhook sender can influence.
- →Is every Node.js on 22.23.4, 24.22.1 or 26.12.1? Images, CI runners, serverless settings and laptops included.
- →Has everything restarted? Processes, workers, scheduled jobs and replicas, with images rebuilt and lockfiles updated.
- →Do edge rules reject Content-Encoding: br where patches lag? Test that gzip still gets through.
- →Which vendors carry their own copy? Ask each supplier of an appliance, desktop app or hosted service for its fixed version.
- →Who owns the firewall setting? Check Brotli inspection on every PAN-OS 11.2 or later firewall.
Rules that hold or break
Each check can be written as a rule: every runtime we run is on a fixed release; data from outside is decompressed only by a patched decoder; a request with an encoding we did not expect is rejected before any parser sees it. Written down, a rule can be re-proven on every release, holding or broken, including after a patch, when the question is whether the fixed build is serving traffic. That is the practice ExploitOps describes; the rules page shows how rules are written, and the Record keeps Broccoli2Shell with the year's other AI security incidents.
Sources
- →Node.js, October 2026 security releases ↗, 21 October 2026
- →Google, commit 0ed4268 ↗, 27 February 2026, and Brotli 1.2.1 ↗, 21 October 2026; CVE-2026-111497 ↗ at CVE.org
- →OpenAI, Patch the Planet: Brotli decoder vulnerability ↗, post on X ↗ and Securing our frontier model training environment ↗, 23 October 2026
- →broccoli2shell.com, independent tracker ↗, updated 28 October 2026; Juliano Rizzo on X ↗, 23 October 2026
- →Imperva, Node2Shell: From Brotli Data to Remote Code Execution ↗, 22 October 2026
- →ELLIO, Node2Shell: remote code execution on stock Node.js builds ↗, 25 October 2026, updated 26 October
- →JFrog Security Research, Brotli2Shell: Beyond Node(2Shell) and HTTP(2Shell) ↗, 25 October 2026
- →OpenSourceMalware, Python packages vulnerable ↗, 23 October 2026; Aikido, Python packages are also vulnerable ↗; GitHub, GHSA-9v8h-qqgm-4m35 ↗
- →Microsoft, .NET October 2026 out-of-band security updates ↗, 25 October 2026, and security update guide ↗
- →Palo Alto Networks, CVE-2026-111497 advisory ↗, 26 October 2026, updated 31 October
- →Vercel, Node.js security advisory ↗, updated 29 October 2026
- →CISA, Known Exploited Vulnerabilities catalogue ↗, 27 October 2026; Canadian Centre for Cyber Security, AL26-027 ↗, 28 October 2026
- →Broadcom, VMSA-2026-0008 ↗, 27 October 2026, updated 28 October
- →Git for Windows, 2.56.0.windows.4 ↗ and issue 6514 ↗; Electron, 45.1.0 ↗; Bun, 1.4.4 ↗; Envoy, GHSA-3wvj-j6qm-62mr ↗; Istio, ISTIO-SECURITY-2026-007 ↗
- →Debian and Ubuntu security trackers: Debian ↗, Ubuntu ↗
- →Trail of Bits, Brotli Checker ↗; JFrog, open-source scanner ↗
- →The Register, Microsoft releases emergency .NET security fixes ↗, 26 October 2026; SecurityWeek, CISA warns of Node2Shell exploitation ↗, 28 October 2026; BleepingComputer, CISA warns of hackers exploiting Node2Shell ↗, 28 October 2026; Help Net Security, Brotli bug reaches far beyond Node.js ↗, 27 October 2026
The logos are each company's own published versions. They identify the companies named in their public statements; MATT has no affiliation with Vercel, ELLIO, JFrog, Aikido, Trail of Bits or Bun.
Questions
What is Broccoli2Shell?
The nickname for CVE-2026-111497, a flaw in the decoder of Google's Brotli compression library, versions 1.0.3 to 1.2.0. Crafted compressed data can make the decoder write past an internal buffer, which can crash a program, expose its memory or run an attacker's code. It was disclosed on 21 October 2026 and is also called Node2Shell and HTTP2Shell.
Which versions fix CVE‑2026‑111497?
Brotli 1.0.10, 1.1.1 and 1.2.1; Node.js 22.23.4, 24.22.1 and 26.12.1; Python Brotli 1.2.1 and brotlicffi 1.2.1.0; .NET 8.0.33, 9.0.22 and 10.0.14; Bun 1.4.4; Git for Windows 2.56.0.windows.4. Node.js 20, 23 and 25 get no fix, and any product that bundles its own copy needs its own update.
Is Broccoli2Shell being exploited?
CISA added it to its Known Exploited Vulnerabilities catalogue on 27 October 2026, and ELLIO logged exploit traffic on 26 October, although the attempt it examined failed in its tests. Microsoft and Palo Alto Networks said they knew of no malicious exploitation, and we found no published, confirmed compromise by 31 October.
Is a team that doesn't use Node.js affected?
Possibly. The flaw is in Brotli itself, so Python's Brotli packages, .NET, Bun, Envoy, Istio, curl and Git, FFmpeg with JPEG XL support, some Palo Alto Networks firewalls and many bundled copies are affected. Exposure needs a path for someone else's Brotli data to reach the decoder: a fetched response, a request body or a file.
Why is installing the update not enough?
A running process keeps the decoder it loaded, so every service must restart after the upgrade. Containers keep the old decoder until their images are rebuilt on a patched base and redeployed, self-contained .NET apps and Electron apps carry their own runtimes, and lockfiles must be updated or the next build reinstalls the old version.
Did AI find Broccoli2Shell?
Node.js credits Khai Tran and Sylvie Mayer of OpenAI Codex Security, and OpenAI says the flaw was found during a red-teaming campaign in which AI agents probed its own training environment. The underlying fix had already been committed to Brotli in February 2026 without being recognised as a security fix.
Does a web application firewall protect against Broccoli2Shell?
Partly. Rules from Vercel, Imperva and others block crafted Brotli request bodies, but a firewall never sees responses an application fetches from other servers, and Brotli also arrives inside files. Patching and restarting every copy is the complete fix.


