HTTP2Shell (CVE-2026-111497): The Brotli RCE Hitting Node.js, Python and More
Last checked: October 24, 2026, 04:00 UTC. HTTP2Shell is still moving, and we will update this post as vendors publish fixes.
- ✓HTTP2Shell (CVE-2026-111497) is an out-of-bounds write in Google's Brotli decoder, versions 1.0.3 through 1.2.0. It was first called Node2Shell, then Python2Shell, and is also called Broccoli2Shell.
- ✓Any process that decompresses attacker-controlled Brotli data can crash, leak memory, or in the worst case run attacker code. The CVE record scores it 9.2 (Critical) on CVSS 4.0.
- ✓Exposed paths include Express 5 and body-parser 2.x request parsing, Node.js fetch(), Python requests, urllib3, aiohttp and httpx when Brotli is installed, and curl with compressed decoding.
- ✓Fixed in Node.js 22.23.4, 24.22.1 and 26.12.1, Bun 1.4.4, Python Brotli 1.2.1, brotlicffi 1.2.1.0, and Brotli 1.0.10, 1.1.1 and 1.2.1. Node.js 20 and older get no fix.
- ✓Researchers ran code on an official Node.js build in a lab. No attacks in the wild are confirmed yet, and it is not in CISA KEV.
What is HTTP2Shell?
HTTP2Shell is the nickname for CVE-2026-111497, an out-of-bounds write in Google's Brotli decoder (versions 1.0.3 through 1.2.0) that lets crafted compressed data corrupt memory in any process that decompresses it. Because HTTP clients and servers decode Content-Encoding: br automatically, the bug reaches Node.js, Python, Bun and many other stacks through ordinary web traffic. The worst case is remote code execution with the permissions of the affected process.
The CVE record, filed by Google as the numbering authority, scores it 9.2 (Critical) on CVSS 4.0, and GitHub's advisory scores it 9.8 on CVSS 3.1. The Node.js project rates its own exposure as High. If the two numbers confuse you, our guide to CVSS scoring explains why vendors and CNAs often land in different places for the same bug.
Why did Node2Shell become HTTP2Shell?
The name changed three times in four days, and that matters because each name tells you who the industry thought was exposed at the time.
- Node2Shell (October 21). Node.js shipped the fix first, at 10:38 UTC on October 21, so the first wave of coverage treated it as a Node.js runtime bug. Node.js credits OpenAI Codex Security (Khai Tran and Sylvie Mayer) with the report, and Matteo Collina and Antoine du Hamel with the fix.
- Python2Shell (October 23). Researchers at OpenSourceMalware and Aikido showed that the PyPI packages
Brotliandbrotlicfficompile the same C decoder, and that popular Python HTTP clients load them automatically. - HTTP2Shell (late October 23). Aikido renamed the threat once it was clear that the bug lives in the shared library, not in one runtime, so any software that decodes Brotli over HTTP is in scope. Bun's own post on October 24 uses the HTTP2Shell name for its advisory. Some researchers also call it Broccoli2Shell.
All of these names point to one CVE. There is no second bug and no second CVE ID. The useful takeaway is the shift in scope: this started as a runtime patch and became a dependency and supply chain problem.
How does the Brotli decoder bug work?
A Brotli stream is split into blocks, and the decoder switches between block types as it reads. That switching is handled by a function called DecodeBlockTypeAndLength() in c/dec/decode.c.
In vulnerable versions, that function returned a plain true or false. A false result could mean either "this stream is malformed" or "I need more input," and the callers could not tell the two apart. On the decoder's fast path, the return value was ignored entirely. The CVE record lists two weaknesses for this reason: CWE-252 (unchecked return value) and CWE-787 (out-of-bounds write).
An attacker can build a stream that declares a single block type and then forces a block switch that cannot succeed. The vulnerable decoder treats that impossible switch as a temporary need for more data. According to the Node.js advisory, the streaming decoder then keeps appending attacker bytes to an eight-byte internal buffer and writes past its end, corrupting nearby decoder state and process memory.
The upstream fix is google/brotli commit 0ed4268. It changes the function to return a real error code and adds a new error, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (value -17), so decoding stops on both the safe and fast paths. That commit was first written in February 2026, but nobody connected it to remote code execution until October, which is why downstream projects never pulled it in.

Which software and versions are affected?
The root cause is in the Google Brotli C library, so the short answer is: anything that ships, bundles, or links Brotli 1.0.3 through 1.2.0 and decodes data it does not control. Here is where fixes stand as of October 24, 2026.
| Component | Affected | Fixed in |
|---|---|---|
| Google Brotli (C library, libbrotlidec) | 1.0.3 to 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
| Node.js 26.x | Below 26.12.1 | 26.12.1 |
| Node.js 24.x (LTS) | Below 24.22.1 | 24.22.1 |
| Node.js 22.x (LTS) | Below 22.23.4 | 22.23.4 |
| Node.js 25.x, 23.x, 20.x and older | All versions | No fix (end of life). Move to 22, 24 or 26. |
| Bun | 1.4.3 and earlier | 1.4.4 |
Python Brotli (PyPI) | 1.2.0 and earlier | 1.2.1 |
Python brotlicffi (PyPI) | 1.2.0.x and earlier | 1.2.1.0 |
| Linux distribution libbrotli | Varies by distro | Check your distribution's tracker. Debian had fixed bookworm but still listed trixie and unstable as vulnerable on October 23. |
Projects that bundle their own copy
A distribution update does not fix software that vendors a private copy of the decoder. A public tracker run by independent researchers lists several projects whose bundled Brotli was still unpatched on October 24, including the .NET runtime (BrotliDecoder, BrotliStream and HttpClient automatic decompression), the PHP brotli extension, the Ruby brotli gem, DuckDB, ClickHouse, Apache Arrow's bundled build, libjxl, NanaZip, 7-Zip-zstd and p7zip, MuPDF, EDK II and UEFITool, and YDB. Fixes for several of these are in progress. The same tracker reports that Chromium, Firefox, ngx_brotli, Brotli4j and Ghostscript have already shipped updates.
What is not affected
- Pure reimplementations. The Java, JavaScript and C# ports of Brotli cannot reach the bug. Rust's
brotli-decompressorcatches the condition and returns an error. The Go port (andybalholm/brotli) had the same logic error but could only crash, and it was fixed on October 23. - Default Apache and Nginx. Aikido notes that Apache and Nginx do not decode Brotli request bodies by default. They compress responses, which uses the encoder, not the decoder.
- A bare Node.js HTTP server. Receiving a Brotli body does not decode it. Something in your code or middleware has to call the decoder.
Who is actually exposed to HTTP2Shell?
A vulnerable version is necessary but not sufficient. You are exposed when attacker-controlled Brotli data reaches the decoder. There are three ways that happens.
1. Inbound: request bodies your server inflates
Express 5 (5.0.0 to 5.3.0) depends on body-parser 2.x, and body-parser 2.0.0 through 2.3.0 decodes br, gzip, deflate and zstd request bodies by default because its inflate option defaults to true. That means a single line, app.use(express.json()), is enough to put the decoder in front of anonymous internet traffic. Express 4 uses body-parser 1.x, which handles only gzip and deflate and returns a 415 error for Brotli, though other middleware in the same app might still decode it. Python frameworks and middleware that inflate br bodies are exposed the same way.
2. Outbound: responses your code fetches
This is the path most teams will miss, and no WAF will see it. Node.js's built-in fetch() decodes Brotli responses automatically. In Python, urllib3 (and therefore requests), aiohttp and httpx with the brotli extra advertise Accept-Encoding: br and decode Brotli whenever a Brotli package is installed, even if your code never imports it. Scrapy pulls Brotli in as a dependency. On Debian 12 and Ubuntu 24.04, libcurl links the system libbrotli (1.0.9 and 1.1.0), so curl --compressed and any app that enables libcurl automatic decoding are in the same bucket.
Any feature that calls a URL an outsider chooses is a delivery channel: webhook deliveries, link previews, "import from URL," crawlers, image proxies, OAuth and OIDC discovery, and AI agents that browse. If you have hunted SSRF in your own apps, you already know where these are.
3. Direct: data your code decompresses
Code that calls zlib.brotliDecompress(), createBrotliDecompress() or brotli.decompress() on untrusted input is exposed, as are tools that parse Brotli-compressed Parquet files, JPEG XL images, PDFs, archives, caches and queue messages.
Is HTTP2Shell being exploited in the wild?
Not that anyone has confirmed yet, but the gap between "proof of concept" and "in the wild" is closing quickly.
| Claim | Status as of October 24 | Source |
|---|---|---|
| The decoder can be made to write out of bounds | Confirmed | Node.js advisory, CVE record, Brotli 1.2.1 changelog |
| Code execution on an official, unmodified Node.js build | Demonstrated in a lab, not on every attempt | Imperva Red Team, Node.js 24.22.0 Linux arm64 image, via fetch() and Express 5 request bodies |
| Public proof-of-concept code | Available on GitHub since October 21 and 22 | Reportedly crashes stock builds and reaches code execution only on deliberately weakened builds |
| Memory corruption in the Python packages | Bug confirmed with AddressSanitizer, no exploit attempted | OpenSourceMalware analysis, cited by Aikido |
| Listed in CISA KEV | No | CISA's data on the CVE record listed exploitation as "none" on October 23 |
| Attacks reported in the wild | None reported | No vendor or CERT has reported active exploitation |
Treat the missing KEV entry as a timing gap, not a risk signal. The flaw is a memory-safety bug in code that handles unauthenticated network input, public PoCs exist, and the Czech national CERT (GovCERT.CZ) has already issued a warning. Also be careful with "free scanners" and exploit repos that use the HTTP2Shell or Node2Shell names. Run any of them only in an isolated lab.
How do you check if you are vulnerable to HTTP2Shell?
Do not send exploit traffic to production. Check versions and code paths instead, and do it everywhere code runs: production, staging, CI runners, build images, serverless functions and developer laptops.
Node.js and Bun
# Runtime version: affected if below 22.23.4, 24.22.1 or 26.12.1, or on an EOL line
node --version
# The Brotli version bundled into this exact Node binary
node -p "process.versions.brotli"
# Inside a container image (a package inventory can miss the bundled runtime)
docker run --rm --entrypoint node <image> --version
# Find inbound and direct decode paths
npm ls express body-parser
grep -rnE "express\.json|bodyParser|createBrotliDecompress|brotliDecompress" src/
# Bun
bun --version # affected if 1.4.3 or earlier
Python
pip show Brotli brotlicffi
# What pulls Brotli in transitively
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 libraries and running processes
dpkg -l | grep -i brotli # Debian, Ubuntu
rpm -qa | grep -i brotli # RHEL, Fedora
apk info -v | grep -i brotli # Alpine
# Processes that currently have the decoder loaded
sudo grep -l brotlidec /proc/*/maps 2>/dev/null
One warning for Python teams: when this broke, pip-audit, Dependabot and Safety did not reliably flag the affected PyPI versions, because the CVE record named the upstream Google repository rather than the packages. GitHub's advisory (GHSA-9v8h-qqgm-4m35) now maps both packages, but check by hand anyway. The same lesson showed up in the LiteLLM PyPI incident: the transitive dependency you never chose is the one your tooling sees last.
How do you fix HTTP2Shell?
- Upgrade the runtime or library. Node.js to 22.23.4, 24.22.1 or 26.12.1. Bun to 1.4.4. Python
Brotlito 1.2.1 andbrotlicffito 1.2.1.0. System libbrotli through your distribution once it ships. - Restart every process. A running process keeps the old decoder in memory until it restarts.
- Rebuild and redeploy images. A Dockerfile pinned to
node:24.22.0stays vulnerable no matter how often you rebuild it. Move the pin, rebuild on a patched base, and confirm the version inside the running container. - Update lockfiles. Raise the constraint and regenerate
poetry.lock,uv.lockorrequirements.txt, or the next build quietly reinstalls the vulnerable version. - Retire end-of-life runtimes. Node.js 20, 23 and 25 will not get a fix. Plan the move to a supported line now.
- Ask your vendors. Appliances, agents, SDKs and desktop tools that vendor Brotli need their own updates. Ask whether they ship Brotli 1.0.3 to 1.2.0, and when.
What can you do if you cannot patch today?
Node.js has published no official workaround, so everything below is a bridge to the upgrade, not a replacement for it.
- Reject
Content-Encoding: brrequest bodies at your reverse proxy, load balancer, WAF or middleware. Vercel and Imperva deployed WAF rules on disclosure day. This covers the inbound path only. - Turn off body inflation in Express 5 with
express.json({ inflate: false }). Test your clients first, because this also rejects gzip bodies with a 415. - In Python, uninstall
Brotliandbrotlicffiwhere Brotli support is optional. urllib3, requests and aiohttp fall back to gzip and deflate. Remove extras such ashttpx[brotli]too, or the next install brings the package back. This is the only stopgap that covers both inbound and outbound paths. - Do not rely on stripping
brfromAccept-Encoding. A malicious server can send a Brotli response anyway, and clients decode based on the response header. - Shrink the blast radius. Run services that fetch untrusted URLs as unprivileged users, keep secrets out of their environment, and restrict where they can connect.
Why HTTP2Shell is an exposure validation problem, not a version-matching problem
Run a version scanner across a modern estate today and it will flag nearly every Node.js binary, every Python environment with a requests install, and half your base images. That list is accurate and almost useless for deciding what to fix first.
The risk ranking depends on reachability. An internet-facing Express 5 API with express.json() is a pre-authentication target. A webhook dispatcher or URL-preview service that fetches attacker-chosen URLs is a target that your WAF cannot see. A batch job that only decompresses files your own pipeline produced is low priority. A CI runner on Node.js 20 is a lower-urgency item with no patch available. All four show up as the same CVE with the same 9.2.
That is the same pattern we saw with WP2Shell and PHP2Shell earlier this month, and with React2Shell before that. A Continuous Threat Exposure Management (CTEM) program handles it in four steps:
- Scope: find every runtime and library copy, including containers, serverless, vendored copies and forgotten hosts. Attack surface management gives you the internet-facing inventory to start from.
- Prioritize by path: rank assets by whether attacker-controlled Brotli can reach them (inbound body parsing, outbound fetches of untrusted URLs, direct decoding of external files), not by CVSS alone. Risk-based vulnerability management is built for exactly this kind of triage.
- Validate: prove which endpoints actually accept and decode
brbodies, and which features fetch user-supplied URLs, using safe probes rather than exploit payloads. Our post on vulnerability validation covers why this step removes most of the noise, and Strobes Exposure Validation runs it continuously. - Mobilize and retest: patch the reachable paths first, then confirm the fix landed in the running process, not just in the lockfile. Red Loop and Strobes Agentic Pentesting retest the same paths after the change.
HTTP2Shell timeline
- February 2026: The Brotli fix (commit
0ed4268) lands upstream without a security label. - October 21, 10:38 UTC: Node.js releases 22.23.4, 24.22.1 and 26.12.1, rating the Brotli bug High. The first "Node2Shell" posts appear that afternoon.
- October 21, about 19:27 UTC: Google publishes Brotli 1.0.10, 1.1.1 and 1.2.1. Vercel announces WAF rules for inbound traffic.
- October 22: Bun 1.4.4 ships the fix. The CVE record and GitHub advisory GHSA-9v8h-qqgm-4m35 publish. Brotli 1.2.1 reaches PyPI. Imperva publishes its code execution research. Public PoC repositories multiply on GitHub.
- October 23: brotlicffi 1.2.1.0 reaches PyPI. Python impact research publishes, GovCERT.CZ issues a warning, and Aikido renames the threat HTTP2Shell.
- October 24: Bun adopts the HTTP2Shell name for its advisory. Bundled copies in .NET, PHP, Ruby, DuckDB, ClickHouse and others are still awaiting fixes.
Frequently asked questions
What is HTTP2Shell?
HTTP2Shell is the nickname for CVE-2026-111497, an out-of-bounds write in Google's Brotli decoder (versions 1.0.3 through 1.2.0). Any software that decompresses attacker-controlled Brotli data, including Node.js, Python, Bun and libraries that decode HTTP responses, can crash, leak memory, or run attacker code.
Are HTTP2Shell, Node2Shell and Python2Shell different vulnerabilities?
No. They are names for the same bug, CVE-2026-111497. Node2Shell came first because Node.js shipped the fix first, Python2Shell followed when researchers showed the PyPI packages were affected, and HTTP2Shell reflects that the flaw reaches any stack that decodes Brotli over HTTP. Broccoli2Shell is another alias.
Which Node.js versions fix CVE-2026-111497?
Node.js 22.23.4, 24.22.1 and 26.12.1, or any newer patch release on those lines. Node.js 25, 23, 20 and older are end of life and will not receive a fix, so move to a supported line.
Is my Python app affected if I never import Brotli?
Possibly. If the Brotli or brotlicffi package is installed, urllib3 (and requests), aiohttp and httpx with the brotli extra decode Brotli responses automatically. Upgrade Brotli to 1.2.1 and brotlicffi to 1.2.1.0, or uninstall them where Brotli support is optional.
Does a WAF protect against HTTP2Shell?
Only partly. WAF rules from vendors such as Vercel and Imperva can block crafted Brotli request bodies, but they do not see responses your own code fetches from other servers. Upgrading the runtime or library is the only complete fix.
Is HTTP2Shell being exploited in the wild?
As of October 24, 2026, no attacks in the wild have been confirmed and the CVE is not in the CISA Known Exploited Vulnerabilities catalog. Imperva demonstrated code execution against an official Node.js 24.22.0 build in a lab, and public proof-of-concept code exists, so patch now.
Key takeaways
- HTTP2Shell, Node2Shell, Python2Shell and Broccoli2Shell are all one bug: CVE-2026-111497 in the Google Brotli C decoder.
- Patch Node.js to 22.23.4, 24.22.1 or 26.12.1, Bun to 1.4.4, Python Brotli to 1.2.1 and brotlicffi to 1.2.1.0, then restart processes and rebuild images.
- Outbound HTTP calls are the blind spot. Webhooks, URL previews, crawlers and agents that fetch untrusted URLs are exposed, and no WAF covers that path.
- Vendored copies in .NET, PHP, Ruby, data tools and firmware need their own updates. Ask your vendors.
- Rank fixes by reachability, then validate and retest. A version match alone will bury the assets that matter.
Want to see which of your internet-facing services actually decode untrusted Brotli? Book a Strobes demo and we will walk through your exposure.
Sources
- Node.js: Wednesday, October 21, 2026 Security Releases
- CVE.org: CVE-2026-111497
- NVD: CVE-2026-111497
- GitHub Advisory GHSA-9v8h-qqgm-4m35
- Google Brotli v1.2.1 release and fix commit 0ed4268
- Imperva: Node2Shell, from Brotli data to remote code execution
- Aikido: Node2Shell, Node.js Brotli vulnerability
- Aikido: Node2Shell evolves to HTTP2Shell
- OpenSourceMalware: Node2Shell affects Python
- Broccoli2Shell: affected products and bundled copies tracker
- Brandefense: Node2Shell fixed versions and timeline
- Vercel: Node.js security advisory, Brotli decoder RCE
- Bun v1.4.4 release
- Debian security tracker: CVE-2026-111497
