HTTP2Shell CVE-2026-111497 Hits CISA KEV: Brotli RCE Spreads from Node.js to Python, .NET and Envoy

Introduction

The Brotli decoder bug that shipped as a Node.js fix last week is now a CISA Known Exploited Vulnerability. CVE-2026-111497, renamed HTTP2Shell as its reach grew, is an out-of-bounds write in Google's Brotli C decoder (1.0.3 through 1.2.0, CVSS v4.0 9.2). It is reachable from any HTTP stack that auto-decodes Content-Encoding: br: Node.js, Python's requests/httpx/aiohttp, .NET HttpClient, Bun, Envoy and Istio. CISA added it on October 27 with a federal due date of November 10. Patch runtimes and libraries, rebuild every image, and hunt for the copies your package scanner cannot see.

What Happened

This started as a single line item in the Node.js October 21 security release (covered in our earlier write-up). The root cause turned out to sit upstream in Google's Brotli library. It was fixed in Brotli's source on February 27, 2026, but no tagged release carried the fix until October 21. Every downstream project that vendors Brotli then had to ship its own update.

Date (2026) Event
Oct 21 Node.js 22.23.4 / 24.22.1 / 26.12.1; Google tags Brotli 1.0.10, 1.1.1, 1.2.1
Oct 22 PyPI Brotli 1.2.1 and brotlicffi 1.2.1.0; GHSA-9v8h-qqgm-4m35 published
Oct 23 Bun, Envoy, Istio patches; the "HTTP2Shell" name takes over from "Node2Shell"
Oct 24 brotli4j 1.24.0; Amazon Linux 2023 advisory
Oct 25 Microsoft out-of-band .NET 8.0.33 / 9.0.22 / 10.0.14
Oct 26 Debian and Alpine fixes; Fedora and RHEL only partial
Oct 27 Ubuntu fixes; CISA adds CVE-2026-111497 to KEV, due Nov 10

CISA lists the entry as "Google Brotli Unchecked Return Value Vulnerability". That matches the fix, which makes the decoder return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH instead of carrying on past a failed check. CISA has not published who is exploiting it or against what, and lists ransomware use as unknown.

That leaves a gap in the public record. As late as October 24–26, trackers were reporting no confirmed in-the-wild exploitation, and the public PoC only reached code execution on a deliberately weakened Node.js build. Imperva's red team says it ran code on an unmodified Node.js 24.22.0 arm64 build, but has not released the research. Treat the KEV listing as the authoritative signal and assume a working chain exists that nobody has published.

The names (Node2Shell, Python2Shell, Broccoli2Shell, HTTP2Shell) all refer to the same bug. "HTTP2Shell" has nothing to do with HTTP/2. It reflects that HTTP content decoding is the most common way to reach the decoder.

Why It Matters

Most RCEs need a listening port. This one also works in the other direction. Any service that fetches a URL and lets its HTTP client transparently decompress the response can be hit by whoever controls that response. That covers webhook senders, link previewers, scrapers, SSRF-adjacent "import from URL" features, LLM agents with browsing tools, and CI jobs pulling from mirrors. You do not have to import Brotli anywhere in your code. In Python, urllib3 (and so requests), httpx[brotli] and aiohttp decode br automatically once the Brotli or brotlicffi package is installed, and those packages often arrive as transitive dependencies.

The second problem is visibility. Brotli is vendored all over the place: Pillow and pyarrow wheels, curl-cffi, pycurl, Electron apps, Git for Windows, DuckDB, ClickHouse, firmware toolchains. Manifest-based scanners do not see those copies. Updating the OS libbrotli package fixes nothing for software that carries its own decoder. Debian's backports keep old version strings, so scanners also throw false positives. One public tracker counted about 80 affected products on October 26, with 19 still unfixed.

Who Is Affected

  • Upstream Brotli C library 1.0.3–1.2.0. Fixed in 1.0.10, 1.1.1, 1.2.1.
  • Node.js: 22.x before 22.23.4, 24.x before 24.22.1, 26.x before 26.12.1. 18, 20, 23 and 25 are EOL with no upstream fix. Judge by Node version: process.versions.brotli still reports 1.2.0 on fixed builds because the patch was cherry-picked.
  • Python: Brotli ≤ 1.2.0 (fixed 1.2.1) and brotlicffi 1.2.0.x (fixed 1.2.1.0). Everything that decodes through them is exposed: requests, urllib3, httpx, aiohttp, Scrapy.
  • .NET: 8, 9 and 10 before 8.0.33, 9.0.22, 10.0.14 (11 fixed in RC3). Only apps that opted in are exposed: ASP.NET Core request decompression, HttpClient automatic decompression with Brotli, or the System.IO.Compression Brotli APIs. Brotli decoding is off by default. .NET 7 gets no fix. .NET Framework is not affected.
  • Bun 1.0.21–1.4.3, fixed in 1.4.4. Recompile standalone executables.
  • Envoy fixed in 1.40.0, 1.39.3, 1.38.6, 1.37.8, and Istio in 1.29.9, 1.30.6, 1.31.2. Both are only exposed when the Brotli decompressor filter is configured.
  • Java brotli4j, fixed in 1.24.0.
  • Distros: Debian 12/13 libbrotli1 (1.0.9-2+deb12u1, 1.1.0-2+deb13u1), Alpine brotli-libs (1.2.1-r0 / 1.1.1-r0), Amazon Linux 2023 (1.1.0-1.amzn2023.0.2) and Ubuntu are fixed. Fedora 43–45 and standard RHEL 8/9/10 repos were still unfixed at the time of writing, and SUSE had no fix.
  • Not affected: Apache mod_brotli (compress only), and pure Java/JS/C# ports such as pdf.js, iText and HtmlUnit.

How to Protect Yourself

1. Patch the runtimes, then rebuild everything that bakes them in

Restarting picks up a new shared library. It does nothing for containers, self-contained .NET apps, Bun or Node single-executable binaries, or Electron builds. Those need a rebuild and redeploy.

# Node.js
node --version   # want >= 22.23.4 / 24.22.1 / 26.12.1

# .NET: list installed runtimes, want 8.0.33 / 9.0.22 / 10.0.14 or later
dotnet --list-runtimes

# Bun
bun --version    # want >= 1.4.4

# Debian / Ubuntu
sudo apt update && sudo apt install --only-upgrade libbrotli1 nodejs

# Alpine
apk upgrade brotli-libs

2. Find Brotli in your Python environments

pip show Brotli brotlicffi
pipdeptree -r -p Brotli
grep -iE 'brotli' requirements*.txt poetry.lock uv.lock Pipfile.lock 2>/dev/null

pip install -U "Brotli>=1.2.1" "brotlicffi>=1.2.1.0"

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

Upgrading Brotli does not fix wheels that vendor their own decoder: Pillow, pyarrow, curl-cffi and pycurl. Track those packages' own releases.

3. Check proxies and the service mesh

envoy --version
istioctl version

kubectl get envoyfilters -A -o yaml | grep -nE 'compression\.brotli\.decompressor|brotli\.decompressor\.v3\.Brotli'
istioctl proxy-config listeners <pod> -n <namespace> -o json | grep -nE 'brotli\.decompressor'

# curl / libcurl builds with brotli support
curl -V | grep -i brotli

4. Hunt for vendored copies scanners miss

Search images and install trees for bundled Brotli decoders. Look for shared objects and for the decoder symbol in static binaries:

# shared libraries and vendored copies
find / -xdev \( -iname 'libbrotli*' -o -iname '*brotlidec*' -o -iname '_brotli*.so' \) 2>/dev/null

# statically linked binaries that embed the decoder
find /usr /opt /srv -xdev -type f -perm -u+x -size +1M 2>/dev/null \
  | xargs -r grep -l 'BrotliDecoderDecompressStream' 2>/dev/null

Prioritize anything on the unfixed list: DuckDB, ClickHouse, Apache Arrow bundles, Git for Windows, 7-Zip forks, libjxl, and woff2 Makefile builds.

5. Cut the attack paths at the edge until you're patched

  • Reject inbound requests whose Content-Encoding contains br at the reverse proxy, WAF or API gateway. JSON APIs almost never receive legitimate Brotli request bodies.
  • On services that fetch attacker-influenced URLs, remove br from Accept-Encoding and turn off automatic Brotli decoding. A hostile server can send Brotli anyway, so treat this as harm reduction, not a fix.
  • For .NET, if you enabled AddRequestDecompression() or Brotli in HttpClientHandler.AutomaticDecompression, remove Brotli until 8.0.33 / 9.0.22 / 10.0.14 is deployed.
# nginx in front of an API: refuse Brotli-encoded request bodies
if ($http_content_encoding ~* "br") { return 415; }

6. Hunt for probing and crashes

These detection hints come from public trackers. CISA has published no IOCs.

# inbound Brotli request bodies (adjust log format/field)
zgrep -hiE 'content-encoding[":= ]+[^"]*\bbr\b' /var/log/nginx/access.log* | head

# patched builds log the new decoder error on malformed streams: likely probes
journalctl --since '2026-10-21' | grep -E 'BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH'

# unpatched builds crash
journalctl --since '2026-10-21' | grep -Ei '(node|bun|python|envoy|dotnet).*(SIGSEGV|SIGABRT|segfault|core dumped)'
kubectl get pods -A -o json | jq -r '.items[] | select(any(.status.containerStatuses[]?; .restartCount > 3)) | .metadata.namespace + "/" + .metadata.name'

Also look at egress proxy logs for outbound fetches to new or user-supplied hosts that answered with Content-Encoding: br. Check for worker crashes right after Parquet, JPEG XL, WOFF2 or PDF uploads too. Those formats use Brotli internally.

7. Federal agencies: November 10

BOD 26-04 gives FCEB agencies until November 10, 2026 to apply vendor mitigations, or to stop using affected cloud products where no mitigation exists. Everyone else should treat that as the outer limit.

Source