Log inGet a free assessment

Broccoli2Shell: The Brotli Decoder Bug Hiding Inside Your Runtimes (CVE-2026-111497)

CVE-2026-111497, nicknamed Broccoli2Shell, is a critical out-of-bounds write in Google's reference Brotli decoder. It started as a Node.js story and turned out to live inside Python, .NET, Bun, Envoy, curl, PHP, Ruby and every Linux distribution that ships libbrotli. Here is how the bug works, which versions fix it, how to mitigate where no patch exists, and why the hardest part is finding every copy.

Christoph Hartmann
Christoph Hartmann
·8 min read·
Broccoli2Shell: The Brotli Decoder Bug Hiding Inside Your Runtimes (CVE-2026-111497)

Last week gave us two critical remote code execution bugs with the same nickname pattern, disclosed on the same day. The first was PHP2Shell, an out-of-memory flaw in PHP core (CVE-2026-109618). The second started life as Node2Shell, a Node.js security release on October 21. Within two days it had a new name, Broccoli2Shell, and a much bigger blast radius, because the bug was never really in Node.js. It is in Google's reference C implementation of the Brotli decoder, google/brotli on GitHub, the library almost everything uses to decompress Content-Encoding: br.

CVE-2026-111497 affects Brotli 1.0.3 through 1.2.0, carries a CVSS 4.0 score of 9.2 (Critical), and can be triggered by a remote, unauthenticated attacker who gets a crafted Brotli stream in front of a vulnerable decoder. That can happen from either side of a connection: a malicious client attacking your server, or a malicious server attacking your client.

The two bugs have something important in common. Neither lives in code your developers wrote or chose. They live in the runtime underneath it, which is exactly why they are so hard to inventory.

TL;DR

  • What it is. An out-of-bounds write in the google/brotli C decoder, versions 1.0.3 through 1.2.0. A crafted Brotli stream can crash the process, leak memory, or run attacker code. CVSS 4.0 score of 9.2 (Critical).
  • Who is affected. Anything that decodes Brotli data an attacker controls: Node.js fetch() and Express 5 request bodies, Python HTTP clients with Brotli or brotlicffi installed, .NET, Bun, Envoy, curl, Ruby and PHP extensions, the system libbrotli, and a long tail of projects that vendor the decoder.
  • How bad it is. Imperva achieved code execution on an unmodified Node.js 24.22.0 build. A public proof of concept exists. No exploitation in the wild had been reported as of October 26.
  • What to do. Upgrade to Brotli 1.2.1 (or 1.1.1 and 1.0.10 on older lines), Node.js 22.23.4, 24.22.1 or 26.12.1, and Python Brotli 1.2.1 and brotlicffi 1.2.1.0. Then rebuild images and restart processes. Where you cannot patch yet, reject Content-Encoding: br request bodies at the edge.
  • The hard part. The fix sat in the Brotli source since February 2026 without a release. Finding every bundled, vendored and statically linked copy is now the real work.

How the bug works

A Brotli stream is split into blocks, and the decoder switches block types as it reads them. In vulnerable versions, the function that handles those switches, DecodeBlockTypeAndLength() in c/dec/decode.c, returned a plain boolean. A false could mean "this stream is malformed" or "I need more input", and callers had no way to tell the two apart. On the decoder's fast path, the return value was not checked at all.

A crafted stream exploits that ambiguity by requesting a block switch that cannot succeed. The decoder interprets the failure as "need more input" and keeps appending attacker-controlled bytes to an 8-byte internal buffer, writing past its end. The overwrite corrupts adjacent decoder state, which can lead to a crash, a memory leak, or code execution. The CVE record pairs CWE-252 (unchecked return value) with CWE-787 (out-of-bounds write).

The upstream fix in google/brotli commit 0ed4268 ("Detect and report invalid stream earlier", February 27, 2026) makes the function return an error code and introduces a hard failure, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17), on both the safe and fast paths.

The uncomfortable detail is the timeline. The root cause was fixed in the Brotli source in February 2026, but the change was not recognized as a security fix, so no release shipped. Every project that vendors Brotli kept shipping the vulnerable decoder for eight more months.

How exploitable is it?

This is not a theoretical memory safety finding:

  • Code execution on stock Node.js. The Imperva Red Team ran attacker-supplied code on the official, unmodified Node.js 24.22.0 Linux arm64 image, both through a fetched HTTP response and through an Express request body. It took several attempts, and that exploit has not been released.
  • A public proof of concept exists. One appeared on GitHub on October 22. On standard builds it reportedly causes a crash and a limited memory read; code execution only works on a deliberately weakened build.
  • Python is confirmed vulnerable. Researchers reproduced the bug under AddressSanitizer: Brotli 1.2.0 accepted a crafted stream of roughly 23 MB and kept asking for input, while 1.2.1 rejected it with error -17. No exploit was attempted, but it is the same C decoder that was exploited in Node.js.
  • No exploitation in the wild had been reported as of October 26, according to heise online.

The bug was reported to Node.js by OpenAI Codex Security, and the Node.js fix was landed by Matteo Collina and Antoine du Hamel.

Where Brotli hides

The reason this bug deserves its own name is the number of places it shows up without anyone having asked for it.

Outbound HTTP clients. Node.js fetch() decodes Content-Encoding: br automatically. In Python, urllib3 and requests, aiohttp, httpx (with the brotli extra) and Scrapy all decode Brotli automatically as soon as a Brotli package is installed, and that package often arrives as a transitive dependency nobody imported on purpose. The PyPI Brotli package alone sees roughly 83 million downloads a month. Any service that calls a third-party API, crawls the web, or follows a webhook URL is exposed to whoever controls the other end. Removing br from Accept-Encoding does not protect you, because a malicious server can send Brotli anyway.

Inbound request bodies. Express 5.0.0 through 5.3.0 and body-parser 2.0.0 through 2.3.0 decode br request bodies in their raw() and json() parsers. Upgrading those packages does not fix the bug; they are only the path to it. A bare node:http server does not decode bodies on its own.

Runtimes and proxies that bundle their own copy. Node.js ships its own Brotli, so the system libbrotli version is irrelevant there. The same is true for Bun, for .NET self-contained apps, and for anything statically linked. Envoy and Istio only run the decoder when the decompressor filter is configured, which Istio does not enable by default.

Everything else that links the C library. curl and libcurl built with Brotli support, the Ruby brotli gem, the PHP brotli extension, Java's brotli4j, and the system libbrotli on every major Linux distribution.

Vendored copies nobody is tracking. Databases, archivers, firmware toolchains and font libraries carry their own copy of the decoder and update it on their own schedule. The tables below list the ones known so far. The broccoli2shell.com maintainers point out that the list is incomplete, because vendored copies do not show up in dependency searches.

Pure language ports are not affected by the code execution risk: the Java, JavaScript and C# ports and Rust's brotli-decompressor are fine. Apache's mod_brotli only compresses and never runs the decoder.

Affected and fixed versions

These tables follow the inventory on broccoli2shell.com as of October 26, 2026.

Core library, runtimes and packages

ProductAffectedFixed in
Google Brotli (C library)1.0.3 to 1.2.01.2.1, or 1.1.1 and 1.0.10 on older lines
Node.js 26below 26.12.126.12.1
Node.js 24below 24.22.124.22.1
Node.js 22below 22.23.422.23.4
Node.js 25, 23, 20 and olderall versionsNo fix. These lines are end of life; move to 22, 24 or 26
Python Brotli1.0.3 to 1.2.01.2.1
Python brotlicffi1.0.3 to 1.2.0.x1.2.1.0

Node.js bundles its own Brotli, so only the Node.js version matters, not the system libbrotli. Express 5.0.0 to 5.3.0 and body-parser 2.0.0 to 2.3.0 are exposure paths rather than the bug itself; upgrading them alone does not fix anything.

Fixed since disclosure

ProjectFixed inNotes
.NET runtime8.0.33, 9.0.22, 10.0.14, 11.0.0-rc.3Covers BrotliStream, BrotliDecoder, HttpClient automatic decompression and ASP.NET Core request decompression. Out-of-band release on October 25. Source builds from the public GitHub branches are still affected.
Ruby brotli gem0.9.1Bundles Brotli 1.2.1 and refuses to build against an unpatched system libbrotli. Released October 24.
PHP brotli extension0.21.1Bundles Brotli 1.2.1. Released October 24.
UEFIToolA76Bundles Brotli 1.2.1. Released October 24.
MuPDF1.28.0Every 1.28.x release is patched. The 1.27.x releases still bundle the affected decoder.
Bun1.4.4Affected from 1.0.21. Updates the bundled Brotli from 1.2.0 to 1.2.1 and covers fetch, node:zlib and DecompressionStream. Compiled executables stay vulnerable until rebuilt. No process-wide workaround exists. (GHSA-663w-cv8g-jj2p)
Envoy1.40.0, 1.39.3, 1.38.6, 1.37.8Also 1.36.12, which is out of support. Only deployments with the Brotli decompressor filter configured are affected. (GHSA-3wvj-j6qm-62mr)
Istio1.31.2, 1.30.6, 1.29.9Ships the Envoy fix. Istio does not enable Brotli decompression by default, so only meshes that turn it on, for example with an EnvoyFilter, are exposed. Released October 15.
Java brotli4j1.24.0Bundles Brotli 1.2.1. Released October 24. (GHSA-x524-jrqf-vwq6)

Fixed on the main branch, not yet released

ProjectStatus
ClickHouseBrotli 1.2.1 on master and backported to 26.10. Backports to 26.9, 26.8 and 26.3 LTS are open. The latest release, 26.10.2.103, predates the fix.
NanaZipBrotli 1.2.1 on main. The latest release, 7.0.1873.0, predates the fix.
GraalJS (graal-nodejs)Fixed on master. GraalVM ships security updates quarterly; the latest release, 25.4.4.1.1, predates the fix.
YDBPatched on main, not yet released.
libjxlBrotli 1.2.1 on main. The latest release, v0.12.0, predates the fix.

Unpatched bundled copies

ProjectBundled BrotliUntrusted input it decodes
DuckDBunpatchedBrotli-compressed Parquet files. A fix is in progress.
Apache Arrow (bundled build)1.0.9Brotli-compressed Parquet files. An approved pull request moves to 1.0.10.
v8-jsi1.2.0The same paths as Node.js, which it bundles
7-Zip-zstd1.2.0Archives and .br files a user opens
p7zip1.0.9Archives and .br files a user opens
EDK II1.2.0Compressed firmware sections
woff2 (Makefile build)1.0.3WOFF2 fonts passed to woff2_decompress. The CMake build links the system library instead.

Linux distributions

Distributions are moving at different speeds. Debian, Alpine, Amazon Linux 2023 and Fedora have published fixed brotli packages, for example 1.0.9-2+deb11u1 on Debian 11, 1.1.1-r0 on Alpine 3.22 and 1.2.1-1.fc43 on Fedora 43, while several other distributions have no fixed package yet. Distributions often backport the fix without bumping the upstream version number, so a version that looks old may already be patched. Rely on the vendor advisory, not the version string. MONDOO-CVE-2026-111497 lists the fixed package for each distribution.

What to do now

1. Find every copy

Start with an inventory, because this is where most teams will lose time:

Bash
# Node.js reports the Brotli version it bundles
node -p process.versions.brotli
# Python packages, including transitive installs
pip show Brotli brotlicffi
# System library
dpkg -l | grep -i brotli
rpm -qa | grep -i brotli
apk info -v | grep -i brotli
# curl built with Brotli support
curl -V | grep -i brotli
# Running processes that have the decoder loaded
sudo grep -l brotlidec /proc/*/maps

Do this for container images and lockfiles too, not only for hosts. When the bug was disclosed, common Python scanners did not reliably flag the affected PyPI versions, so verify rather than assume.

2. Patch, then rebuild and restart

Upgrade Node.js to 22.23.4, 24.22.1 or 26.12.1 through your version manager, base image or package source. In Python, require Brotli>=1.2.1 and brotlicffi>=1.2.1.0 and regenerate your lockfiles. Then rebuild every container image, wheel and self-contained binary, and restart every process. Installing a patched library does nothing for a process that already has the old decoder mapped into memory, and nothing at all for a binary that statically linked it.

3. Prioritize by exposure

Fix internet-facing services that accept request bodies first, then anything that fetches content from servers you do not control: crawlers, link previewers, webhook consumers, integrations with third-party APIs, and AI agents that browse. Internal batch jobs that only decompress your own data can follow.

4. Mitigate where you cannot patch yet

  • Inbound: reject requests whose Content-Encoding contains br at the reverse proxy, load balancer, WAF or middleware, before anything decodes the body. Match case-insensitively, including inside encoding lists.
  • Outbound in Python: uninstall Brotli and brotlicffi and remove extras such as httpx[brotli]. Clients fall back to gzip and deflate, although a server that sends Brotli anyway will now produce an error.
  • Outbound with curl: do not use --compressed, and leave CURLOPT_ACCEPT_ENCODING unset in libcurl.
  • Envoy and Istio: search your EnvoyFilters and live proxy configuration for the Brotli decompressor filter and remove it if it is present.
  • Node.js and Bun: you can reject br request bodies before body-parser or Express 5 sees them, but fetch() has no standard opt-out. For fetched responses, upgrading the runtime is the only fix.

Detect with Mondoo

Mondoo has published its own advisory, MONDOO-CVE-2026-111497, which maps the vulnerability to the affected packages across Linux distributions, including brotli, libbrotli, python3-brotli and the distribution nodejs packages, with distribution-specific fixed versions. Because distributions backport, that mapping is what tells you whether a version that looks old is actually patched. Mondoo customers see affected hosts, VMs and container images flagged automatically, alongside the interim mitigations from the advisory.

The same applies to PHP2Shell. MONDOO-CVE-2026-109618 tracks the PHP out-of-memory flaw that can lead to unauthenticated remote code execution across PHP-FPM, mod_php, php-cgi, FrankenPHP and LiteSpeed, with fixes in PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12.

Why this one matters

We have written about a run of named vulnerabilities this year, from Copy Fail and Dirty Frag in the Linux kernel to Bleeding Llama in Ollama and the forged merge records in GitLab. Broccoli2Shell is different from all of them in one way that matters for defenders: the vulnerable code has no single owner.

A kernel bug is fixed by a kernel update. A GitLab bug is fixed by a GitLab upgrade. A Brotli bug is fixed by Node.js, Python, .NET, Bun, Envoy, curl, Ruby, PHP, every Linux distribution and every project that copied decode.c into its source tree, each on its own schedule. The fix existed in February. It took a nickname and a working exploit eight months later for it to start reaching the software that needed it.

That is the real lesson. Patch speed is not limited by how fast an upstream fixes a bug. It is limited by how fast you can find every place that bug lives, including inside runtimes, base images and binaries you never think of as dependencies. The teams that will close Broccoli2Shell quickly are the ones that already know what is running where.

References

How Mondoo helps

Mondoo's Agentic Vulnerability Management continuously inventories what actually runs across your cloud instances, container images, Kubernetes clusters and on-premises servers, and maps it against advisories like MONDOO-CVE-2026-111497 as soon as they are published. When the vulnerable code sits in a runtime or base image rather than in your own dependency tree, that inventory is what turns "are we affected?" from a week of grepping into a list of assets to fix.

Ready to see Mondoo in action? Schedule a demo today.

About the Author

Christoph Hartmann

Christoph Hartmann

Co-Founder & CTO

Christoph Hartmann, co-founder and CTO at Mondoo, wants to make the world more secure. He's long been a leader in security engineering and DevOps, creating widely adopted solutions like Dev-Sec.io and Chef InSpec. For fun, he builds everything from custom operating systems to autonomous robots.

Ready to Get Started?

See how Mondoo can help secure your infrastructure.