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.

Illustration: Broccoli2Shell: the fix that waited eight months

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.
Timeline from 2018 to 2026: March 2018, Brotli 1.0.3 ships with the flaw; 27 February 2026, the fix is committed to Brotli's public source with no release and no advisory; from 20 September, OpenAI's AI agents red-team its own training environment, and OpenAI says the Brotli flaw came out of that work; 16 October, Google reserves CVE-2026-111497; 21 October, Node.js 22.23.4, 24.22.1 and 26.12.1 and Brotli 1.0.10, 1.1.1 and 1.2.1 are released; 22 October, the CVE is published, Imperva runs code on official Node.js 24.22.0, and Python Brotli 1.2.1 and Bun 1.4.4 ship; 25 October, Microsoft ships .NET 8.0.33, 9.0.22 and 10.0.14; 26 October, ELLIO logs exploit traffic and Palo Alto Networks confirms PAN-OS is affected; 27 October, CISA adds the flaw to its exploited-vulnerabilities catalogue with a 10 November deadline, and Electron ships Node.js 24.22.1; 29 October, the last listed PAN-OS hotfixes are out.
Sources: google/brotli; OpenAI; CVE.org; Node.js; Imperva; PyPI; Bun; Microsoft; ELLIO; Palo Alto Networks; CISA; Electron.

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.

Diagram of one decoder reaching many products. At the top, Google's Brotli C decoder, affected from 1.0.3 to 1.2.0 and fixed in 1.0.10, 1.1.1 and 1.2.1, which others copy, link or bundle. Below it, four groups with their fixed versions. Runtimes: Node.js 22.23.4, 24.22.1 and 26.12.1; .NET 8.0.33, 9.0.22 and 10.0.14; Bun 1.4.4, then rebuild compiled apps. Language packages: Python Brotli 1.2.1 and brotlicffi 1.2.1.0, used by urllib3, requests, aiohttp and httpx; Ruby gem 0.9.1 and PHP extension 0.21.1. System libraries and tools: libbrotli, fixed by distribution updates and used by curl, Git and distribution builds of Node.js; libjxl, used for JPEG XL by FFmpeg and thumbnailers; DuckDB, Apache Arrow, p7zip and the EDK II firmware kit, unpatched on 28 October. Products: PAN-OS 11.2 to 12.2, hotfixes 27 to 29 October; Envoy 1.40.0, 1.39.3, 1.38.6 and 1.37.8; Istio 1.29.9, 1.30.6 and 1.31.2; Electron apps, Electron 45.1.0 and then each app; Git for Windows 2.56.0.windows.4; VMware SDDC Manager, patch pending. At the bottom, how someone else's data reaches a copy: a response it fetches, a request body it accepts, or a file it opens.
Sources: the vendors' advisories and releases; broccoli2shell.com; Imperva; JFrog; OpenSourceMalware; Aikido; Broadcom. Fixed versions as of 31 October 2026.

Where Brotli lives, and what fixes it

WhereHow it reaches the decoderFixed in (2026)
Google Brotli, the C libraryThe reference decoder the others copy or link (release ↗)1.0.10, 1.1.1, 1.2.1 (21 Oct)
Node.jsBundled 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
BunBundled, including in executables built with bun build --compile (Bun ↗)1.4.4 (22 Oct), then rebuild those executables
PythonBrotli and brotlicffi, used by urllib3, requests, aiohttp and httpx (GitHub ↗)Brotli 1.2.1, brotlicffi 1.2.1.0 (22 Oct)
.NETCompiled 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 PHPThe brotli gem and php-ext-brotli bundle the decoder (tracker ↗)Gem 0.9.1, extension 0.21.1 (24 Oct)
Envoy and IstioThe 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 distributionslibbrotli, 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 WindowsIts bundled curl and Brotli decode HTTPS responses (release ↗)2.56.0.windows.4 (26 Oct)
Electron appsEach 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.2Decodes 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 ManagerRuns 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:

SourceWhat it reportedBasis
Imperva, 22 OctRan code on the official Node.js 24.22.0 Linux arm64 image through fetch() and Express 5 request bodies, after several attempts; exploit unreleasedIts own lab
ELLIO, 25–26 OctGot 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 testsIts lab and sensors
CISA, 27 OctAdded the flaw to its Known Exploited Vulnerabilities catalogue, due by 10 November for federal agencies; no attack detailsUS government catalogue
Microsoft, 26 Oct"Exploitation More Likely"; marked as not exploitedIts security update guide
Palo Alto Networks, 31 OctNot aware of any malicious exploitationIts 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 ↗).

Two-column diagram. Left, linked to the system library: curl and Git on Linux, Debian and Ubuntu 22.04 and later Node.js packages, and distribution builds of Python's Brotli and FFmpeg; one operating-system update, such as Debian 1.1.0-2+deb13u1, fixes them all once each process restarts. Right, bundled inside the program: official Node.js binaries and images, Bun and executables compiled with Bun, self-contained .NET apps, Electron apps, Git for Windows, Python wheels, and tools such as DuckDB and p7zip; each needs a new release of that program, then a rebuild and a restart. A strip along the bottom says a running process keeps the decoder it loaded until it restarts.
Sources: Ubuntu and Debian security trackers; Node.js; Microsoft; Electron; Git for Windows; ELLIO; broccoli2shell.com.

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

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.

Know where you stand.

Book a discovery call→