CVE-2026-111497

Broccoli2Shell

A bug in the Brotli decoder lets crafted compressed data write past the end of a buffer. Node.js, Python and anything else built on Google's C decoder needs a patch.

Recall notice

Brotli decoder 1.0.3 through 1.2.0. An out-of-bounds write that can lead to a crash, a memory leak or remote code execution. Rated 9.2 Critical (CVSS 4.0) by GitHub, High by Node.js.

Affected versions and their replacements
ProductAffectedFixed in
Google Brotli (C library)1.0.3 – 1.2.01.0.10, 1.1.1, 1.2.1
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, so move to 22, 24 or 26.
Python Brotli1.0.3 – 1.2.01.2.1
Python brotlicffi1.0.3 – 1.2.0.x1.2.1.0

Installing the update isn't enough. A running process keeps the old decoder until it restarts, and a container keeps it until you rebuild the image on a patched base and redeploy.

Who's exposed

You're at risk if your code, or any library it uses, decompresses Brotli data that someone else controls. That happens in more places than you'd think, because HTTP clients decompress Brotli responses on their own.

Express and body-parser don't contain the bug. They're just paths to it, so upgrading them won't help. Node.js bundles its own copy of Brotli, which means the system libbrotli doesn't matter there. Only the Node.js version does.

The bug

A Brotli stream is split into blocks, and the decoder switches between block types as it reads them. Before the fix, DecodeBlockTypeAndLength() in c/dec/decode.c returned a plain boolean, so callers couldn't tell a malformed stream from one that just needed more input. On the fast path, the decoder ignored the return value altogether.

A crafted stream can request a block switch that can't happen. The decoder took that to mean "need more input" and kept adding attacker bytes to an eight-byte internal buffer, writing past its end. The overwrite corrupts nearby decoder state. That's how a malformed stream can turn into a memory leak or code execution.

The upstream fix is google/brotli commit 0ed4268, along with 50a9b70, b418fa8 and da98063. It returns a hard format error, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (−17), to the caller. The bug is tracked as CWE-252 (unchecked return value) and CWE-787 (out-of-bounds write).

Python

The PyPI packages Brotli and brotlicffi compile the same C decoder. Your code doesn't need to import them to be exposed. If either one is installed, these clients use it on their own:

Don't wait for your scanner. When this was disclosed, pip-audit, Dependabot and Safety didn't reliably flag the affected PyPI versions. The CVE record named the upstream Google repository, not the PyPI packages. GitHub's advisory now lists both packages (PYSEC-2026-4322 and PYSEC-2026-4323), but check by hand anyway.

If your bindings come from your Linux distribution (for example python3-brotli) and not from PyPI, the fix arrives through your distribution's security updates.

Other software

The bug is in Google's reference C decoder, not in Node.js or Python. Anything that ships or links Brotli 1.0.3 through 1.2.0 may be affected, even before an advisory names it.

Decoders rewritten in other languages aren't a concern. The Java, JavaScript and C# ports, which pdf.js, iText and HtmlUnit use, can't reach the bug. The Go port (andybalholm/brotli) had the same logic error, but it would only have crashed, and it was fixed on 23 October. Rust's brotli-decompressor catches the condition and returns an error. Code execution is only possible in the C decoder.

Projects that bundle their own copy

Many projects ship a private copy of the C decoder instead of using your system's library. A distribution update doesn't fix those: each project has to update its copy. We searched GitHub for vendored copies and read their headers on 24 October 2026. A copy is patched if it includes the new BLOCK_SWITCH error code.

Not fixed yet, as of 24 October
ProjectBundled copyUntrusted input it decodes
.NET runtime1.1.0BrotliDecoder, BrotliStream, and HttpClient automatic decompression
PHP brotli extension1.2.0Apps calling brotli_uncompress(). A fix is in progress in PR #105.
Ruby brotli gem1.2.0Apps calling Brotli.inflate. Tracked in issue #64.
DuckDBunpatchedBrotli-compressed Parquet files. A fix is in progress in PR #27791.
ClickHouseown forkBrotli-compressed HTTP bodies, files and Parquet. A fix is in progress in PR #127419.
Apache Arrow (bundled build)1.0.9Brotli-compressed Parquet files. Tracked in issue #55166.
libjxl1.2.0JPEG XL images
GraalJS (graal-nodejs), v8-jsi1.2.0The same paths as Node.js, because they're Node.js forks
NanaZip, 7-Zip-zstd, p7zip1.0.9 – 1.2.0Archives and .br files a user opens. Tracked in NanaZip #1034 and 7-Zip-zstd #579.
MuPDFown forkBrotli-compressed streams in documents
EDK II, UEFITool1.2.0Compressed firmware sections and firmware images. Tracked in EDK II #13378 and UEFITool #492.
YDB1.1.0Brotli-compressed data

Already fixed: Node.js, Bun, Chromium, Firefox, brotlicffi, ngx_brotli, Brotli4j, SumatraPDF, Ghostscript, CatBoost and YTsaurus, as well as Gentoo, Solus and VMware Photon packages.

This list isn't complete. GitHub's code search skips submodules and some very large repositories, and private or vendored-at-build-time copies don't show up at all. A project listed here may not reach the decoder in every configuration. If you maintain one of these projects, updating to 1.0.10, 1.1.1 or 1.2.1 (or cherry-picking commit 0ed4268) fixes it.

Check your versions

Node.js

node --version
# affected if below 22.23.4, 24.22.1 or 26.12.1, or on an end-of-life line

# the Brotli version bundled into this Node binary
node -p "process.versions.brotli"

# inside a container image
docker run --rm --entrypoint node <image> --version

Python

pip show Brotli brotlicffi

# what pulls it in
pipdeptree -r -p Brotli

# lockfiles
grep -i brotli poetry.lock uv.lock requirements*.txt Pipfile.lock 2>/dev/null

# inside a container image
docker run --rm --entrypoint pip <image> show Brotli brotlicffi

System library

dpkg -l | grep -i brotli          # Debian, Ubuntu
rpm -qa | grep -i brotli          # RHEL, Fedora
apk info -v | grep -i brotli      # Alpine

# running processes that have the decoder loaded
sudo grep -l brotlidec /proc/*/maps 2>/dev/null

Check everywhere code runs: production, staging, CI runners, build images, serverless runtimes and developer laptops. Patch first any service that fetches URLs you don't control or accepts Content-Encoding: br bodies.

Fix it

Upgrade

# Node.js: through your version manager, base image or package source
nvm install 24.22.1               # or 22.23.4 / 26.12.1
FROM node:24.22.1-slim            # Dockerfile

# Python
pip install -U "Brotli>=1.2.1" "brotlicffi>=1.2.1.0"
# then raise the constraint and regenerate poetry.lock / uv.lock / requirements.txt

Then restart every process, rebuild every image and redeploy. Update the pins in your lockfiles, or the next build will reinstall the vulnerable version.

If you can't upgrade yet

Node.js has published no workaround. For Node.js, upgrading the runtime is the only complete fix.

Exploitation status

Timeline

  1. The fix first lands in the Brotli source, per OpenSourceMalware's analysis.
  2. Node.js releases 22.23.4, 24.22.1 and 26.12.1. Google releases Brotli 1.0.10, 1.1.1 and 1.2.1.
  3. NVD publishes CVE-2026-111497 and GitHub publishes GHSA-9v8h-qqgm-4m35. Brotli 1.2.1 reaches PyPI, Imperva publishes its research, and a public PoC appears.
  4. brotlicffi 1.2.1.0 reaches PyPI. Researchers publish the Python impact analysis, and GitHub adds the PyPI packages to its advisory.

Why broccoli

Lately every serious bug comes with a something2shell domain, and people race to register them. Some of those sites have ended up taking credit for work done by other people. This bug was first called "Node2Shell", but the flaw is in Brotli itself and reaches well beyond Node.js. Juliano Rizzo had a better idea:

“The Brotli bug (think zlib 20 years ago) is a bigger deal than PHP2Shell […] Node2Shell is a terrible name. I'm calling it Broccoli.”

Juliano Rizzo, @julianor

To be clear: we didn't find this bug. We just registered a domain named after a vegetable. Credit goes to the people who did the work. OpenAI Codex Security (Khai Tran and Sylvie Mayer) reported it to Node.js. Matteo Collina and Antoine du Hamel fixed it in Node.js. The Imperva Red Team (Ron Masas) did the exploitation research. OpenSourceMalware and Aikido analyzed the Python impact.

Sources