SUNDAY, OCTOBER 25, 2026|No. 20087
Cybersecurity · Technology · Software

Critical Vulnerability Discovered in Brotli Decoder Affects Node.js and Python

A critical out-of-bounds write vulnerability in the Brotli compression library, dubbed Broccoli2Shell, poses remote code execution risks to various Node.js and Python environments.

The Broccoli2Shell vulnerability highlights the risks inherent in widely used compression libraries.
The Broccoli2Shell vulnerability highlights the risks inherent in widely used compression libraries. · Photo by Zulfugar Karimov on Unsplash
1 sources
Pipeline ingest
3 reads
Positive / Neutral / Negative
0 countries
Related coverage

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.

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.

  • Your app fetches a URL Node's built-in fetch() decodes Content-Encoding: br responses automatically. So do Python's requests/urllib3 and aiohttp whenever a Brotli package is installed. Any server you talk to can send the payload, whether through webhooks, link previews, crawlers or "import from URL" features.

  • Your app accepts uploads or request bodies Express 5.0.0–5.3.0 and body-parser 2.0.0–2.3.0 inflate Brotli request bodies in parsers such as raw() and json(). Python frameworks and middleware that decode br bodies are exposed in the same way. A bare node:http server doesn't decode bodies by itself.

  • Your app calls the decoder directly This means zlib.brotliDecompress*(), createBrotliDecompress(), brotli.decompress(), and any library that uses them on archives, caches or queue messages.

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:

  • urllib3, and therefore requests, advertises Accept-Encoding: br and decodes Brotli responses.
  • aiohttp uses Brotli when it's installed.
  • httpx uses it through the httpx[brotli] extra.
  • Scrapy and other crawlers. Some versions install Brotli as a dependency.

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.

  • The system library. It's called libbrotlidec, packaged as libbrotli1, brotli and similar. Programs that link it dynamically are fixed once your distribution ships a patched package and you restart them. Statically linked or vendored copies need their own rebuild.
  • Bindings that wrap the C code. This covers Java JNI wrappers, PHP and Ruby extensions, and Go cgo bindings. Look for a release that bundles 1.0.10, 1.1.1 or 1.2.1.
  • Proxies, CDNs, HTTP clients and archive tools that decode br content with the C library.

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, and last rechecked them on 25 October 2026. A copy is patched if it includes the new BLOCK_SWITCH error code.

ProjectBundled copyUntrusted input it decodes
.NET runtime1.1.0BrotliDecoder, BrotliStream, HttpClient automatic decompression, and request bodies in ASP.NET Core apps that use the request decompression middleware
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.
v8-jsi1.2.0The same paths as Node.js, because it bundles Node.js
7-Zip-zstd1.2.0Archives and .br files a user opens. Tracked in issue #579.
p7zip1.0.9Archives and .br files a user opens
EDK II1.2.0Compressed firmware sections. Tracked in issue #13378.
woff2 (Makefile build)1.0.3WOFF2 fonts passed to woff2_decompress. The CMake build links the system library instead. Tracked in issue #207.

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 --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 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

  • Python: uninstall Brotli and brotlicffi. urllib3, requests and aiohttp will fall back to gzip and deflate. Remove extras such as httpx[brotli] too, or the next install brings the package back.
  • Inbound: reject Content-Encoding: br request bodies at your proxy, load balancer or WAF. Several providers, including Vercel, deployed WAF rules for this on disclosure day.
  • Outbound: removing br from Accept-Encoding helps less than you'd hope, because a malicious server can send Brotli anyway. A WAF doesn't see responses your app fetches. Only the runtime or library upgrade covers that path.

Exploitation status

  • Imperva Red Team ran attacker-supplied code on the official, unmodified Node.js 24.22.0 Linux arm64 image. They delivered the payload both as a fetched response and as an Express request body. It took several attempts, and they haven't released the exploit.
  • A public proof of concept appeared on GitHub on 2026-10-22. It reportedly reaches code execution only on a deliberately weakened build. On standard builds it causes a crash and a limited memory read.
  • For Python, researchers confirmed the bug with AddressSanitizer. Version 1.2.0 accepted a crafted stream of about 23 MB, and 1.2.1 rejected it with error −17. They didn't attempt an exploit.
  • As of 2026-10-22, no attacks in the wild had been reported. With public PoCs out, expect that to change.

PAN's pipeline reviewed approximately 1 open sources for this article. No human editor reviewed this article before publication.

Related Reads

Show on timeline →

Earlier on PAN

More in Technology →