TL;DR
Upgrade now if you decompress Brotli from anywhere you don't control. That includes HTTP responses: fetch() in Node.js and requests/urllib3 in Python decode Content-Encoding: br responses automatically.
| Component | Vulnerable | Fixed in |
|---|---|---|
| Google Brotli (C library) | 1.0.3 โ 1.2.0 | 1.0.10 ยท 1.1.1 ยท 1.2.1 |
| Node.js 26.x | < 26.12.1 | 26.12.1 |
| Node.js 24.x | < 24.22.1 | 24.22.1 |
| Node.js 22.x | < 22.23.4 | 22.23.4 |
| Node.js 25.x, 23.x, 20.x and older | End of life | No fix. Move to 22, 24 or 26. |
PyPI Brotli | 1.0.3 โ 1.2.0 | 1.2.1 |
PyPI brotlicffi | 1.0.3 โ 1.2.0.x | 1.2.1.0 |
Upgrading on disk is not enough. A running process keeps the old decoder until it restarts. A container keeps it until you rebuild the image on a patched base and redeploy it.
The bug
A Brotli stream is split into blocks, and the decoder changes block types as it reads them. Before the fix, DecodeBlockTypeAndLength() in c/dec/decode.c returned a plain boolean. That meant callers could not tell a malformed stream from one that just needed more input. On the fast path, the return value was ignored altogether.
A crafted stream can request a block switch that cannot happen. The decoder took that as "need more input", so the streaming decoder kept adding attacker bytes to an eight-byte internal buffer and wrote past its end. The overwrite corrupts nearby decoder state, which is how a decompression error can become memory disclosure or code execution.
The upstream fix (google/brotli commit 0ed4268, plus 50a9b70, b418fa8 and da98063) makes the decoder return a hard format error, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (โ17), and pass it back to the caller.
Node.js
Node.js bundles its own copy of the Brotli decoder, so your operating system's libbrotli doesn't matter here. Only the Node.js version does. Node.js runs the decoder when:
Outbound: fetch()
The built-in fetch() (undici) decodes responses that arrive with Content-Encoding: br. Any server your app talks to can deliver the payload. That includes webhooks, link previews, scrapers and anything built around "fetch this URL".
Inbound: request bodies
Express 5.0.0โ5.3.0 and body-parser 2.0.0โ2.3.0 inflate Brotli request bodies in their parsers (for example raw() and json()). A bare node:http server does not decode bodies on its own.
Direct: node:zlib
zlib.brotliDecompress*(), createBrotliDecompress() and any library that calls them on uploads, archives, caches or message queues.
Express and body-parser don't contain the bug. They only give attackers a way to reach it. Upgrading them won't help. Upgrading Node.js will.
The same release (2026-10-21) fixed nine other CVEs, including three more rated High in TLS and DNS. See the Node.js advisory.
Python
The PyPI packages Brotli and brotlicffi compile the same C decoder. Your code doesn't have to import them. If either one is installed, common HTTP clients use it automatically:
- urllib3, and therefore requests, sends
Accept-Encoding: brand decodes Brotli responses whenever either package is installed. - aiohttp uses Brotli if it's installed.
- httpx uses it through the
httpx[brotli]extra. - Scrapy and other crawlers. Some versions pull Brotli in as a dependency.
- Web frameworks or middleware that decode
Content-Encoding: brrequest bodies.
Scanners may report nothing for a while. When this was published, pip-audit, Dependabot and Safety didn't reliably flag the vulnerable PyPI versions, because the CVE record names the upstream Google repo rather than the PyPI packages. GHSA now lists both packages (PYSEC-2026-4322, PYSEC-2026-4323). Check manually anyway.
If your Brotli bindings come from your Linux distribution (for example python3-brotli) rather than from PyPI, the fix will come through your distribution's security updates.
Everything else
The bug is in the reference C decoder, not in Node.js or Python. Anything that ships or links that decoder from Brotli 1.0.3 through 1.2.0 is a candidate, even if no advisory names it yet:
- The system library (
libbrotlidec, packaged aslibbrotli1,brotliand similar). Programs that link it dynamically are fixed when your distribution ships a patched package and you restart them. Statically linked or vendored copies need their own rebuild. - Language bindings that wrap the C code, such as Java JNI wrappers, PHP and Ruby extensions, and Go cgo bindings. Check each one for a release that bundles 1.0.10, 1.1.1 or 1.2.1.
- Proxies, CDNs, HTTP clients and archive tools that decode
brcontent using the C library.
Not yet confirmed: independent ports and translations of the decoder, such as pure Go, Rust, Java, Kotlin and JavaScript implementations. No public analysis says whether they carry the same logic error. Watch the vendor advisories, and treat the C-backed builds as affected until a fix is shipped.
Am I affected?
Node.js
node --version
# vulnerable if below 22.23.4 / 24.22.1 / 26.12.1, or on any EOL line
# inside a container image
docker run --rm --entrypoint node <image> --version
# the Brotli version actually bundled into this Node binary
node -p "process.versions.brotli"
Python
pip show Brotli brotlicffi
python -c "import brotli; print(brotli.__version__)"
# 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
# Debian / Ubuntu
dpkg -l | grep -i brotli
# RHEL / Fedora
rpm -qa | grep -i brotli
# Alpine
apk info -v | grep -i brotli
# which running processes have it mapped
sudo grep -l brotlidec /proc/*/maps 2>/dev/null
Check every place that runs code: production, staging, CI runners, build images, serverless runtimes and developer laptops. Fix the services that fetch URLs you don't control, or that accept Content-Encoding: br bodies, first.
Fix & mitigate
1. Upgrade (the actual fix)
# Node.js โ via your version manager, base image or package source
nvm install 24.22.1 # or 22.23.4 / 26.12.1
# Dockerfile
FROM node:24.22.1-slim
# Python
pip install -U "Brotli>=1.2.1" "brotlicffi>=1.2.1.0"
# then update the constraint and regenerate poetry.lock / uv.lock / requirements.txt
Then restart every process, rebuild every image and redeploy. Bump pinned versions in lockfiles, so the next build doesn't install the old version again.
2. If you can't upgrade yet
- Python: uninstall
Brotliandbrotlicffi. urllib3, requests and aiohttp fall back to gzip and deflate. Also remove extras such ashttpx[brotli], or the next install will bring the package back. - Inbound: reject requests that carry
Content-Encoding: brat your proxy, load balancer or WAF, before they reach the app. Several providers, including Vercel, deployed WAF rules for this inbound path on the day of disclosure. - Outbound: removing
brfromAccept-Encodinghelps less than it seems, because a malicious server can send Brotli anyway. A WAF in front of your app does nothing for responses your app fetches. Only the runtime or library upgrade protects that path.
Node.js has published no workaround. For Node.js, upgrading the runtime is the only complete fix.
Exploitation status
- Imperva Red Team ("Node2Shell") reported running attacker-supplied code on the official, unmodified Node.js 24.22.0 Linux arm64 image. The payload arrived both as a fetched response and as an Express request body. It took several attempts, and Imperva hasn't released the exploit code.
- A public proof-of-concept on GitHub (2026-10-22) reportedly reaches code execution only on a deliberately weakened build. On standard builds it causes a crash and a limited memory read.
- For the Python packages, researchers confirmed the bug with AddressSanitizer: 1.2.0 accepted a crafted ~23 MB stream, and 1.2.1 rejected it with error โ17. They didn't try to build an exploit.
- As of 2026-10-22, no one had reported attacks using this flaw. Public PoCs make that likely to change, so patch now.
Timeline
- The fix is first committed to the Brotli source, according to OpenSourceMalware's analysis.
- 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.
- NVD publishes CVE-2026-111497. GHSA-9v8h-qqgm-4m35 goes live, Brotli 1.2.1 reaches PyPI, Imperva publishes its "Node2Shell" research, and a public PoC appears.
- brotlicffi 1.2.1.0 reaches PyPI. Researchers publish the Python impact analysis, and GHSA is updated to list the PyPI packages.
Why "Broccoli"?
Imperva's research called it "Node2Shell", but the bug is in Brotli itself and reaches well beyond Node.js. Security researcher Juliano Rizzo proposed a better name:
IMHO, the Brotli bug (think zlib 20 years ago) is a bigger deal than PHP2Shell [โฆ] Node2Shell is a terrible name. I'm calling it Broccoli.
Credits
Reported to Node.js by OpenAI Codex Security (Khai Tran and Sylvie Mayer). Fixed in Node.js by Matteo Collina and Antoine du Hamel. Independent exploitation research by the Imperva Red Team (Ron Masas). Python impact analysis by OpenSourceMalware and Aikido.
References
- Node.js โ October 2026 security releases
- GitHub Advisory GHSA-9v8h-qqgm-4m35
- NVD โ CVE-2026-111497
- CVE.org record
- google/brotli releases (1.0.10 / 1.1.1 / 1.2.1)
- Imperva โ Node2Shell: From Brotli Data to Remote Code Execution
- OpenSourceMalware โ Python packages vulnerable to Node2Shell
- Aikido โ Python packages are also vulnerable
- Vercel โ Node.js security advisory