Security advisory · CVE-2026-111497 · Node2Shell
HTTP2Shell
Published
Google's C Brotli decoder, versions 1.0.3 through 1.2.0, writes past an 8-byte internal buffer when a crafted stream forces an impossible block switch. The result is a crash, a leak of process memory, or code execution. It was disclosed on 2026-10-21 as "Node2Shell", and is now called "HTTP2Shell" because every HTTP stack that decodes Brotli is affected.
The attacker needs no account. A Brotli request body reaches the decoder in Express 5, body-parser 2, Envoy and Istio; a Brotli response reaches it in Node's fetch() and in Python's HTTP clients whenever the Brotli package is installed. No exploitation in the wild had been reported on 2026-10-22.
- Impact
- Unauthenticated crash, memory leak or code execution
- Affected
- Brotli 1.0.3 – 1.2.0 and everything that bundles it
- Fixed in
- Brotli
1.2.1· Node.js24.22.1· Python Brotli1.2.1 - .NET
- No fixed runtime released yet
- Disclosed
[01]
Two ways in
Inbound
Brotli request bodies
A request sent with Content-Encoding: br is inflated before your code sees it. Express 5.0 to 5.3 and body-parser 2.0 to 2.3 were tested and do this, and so do Envoy and Istio.
Outbound
Brotli responses
A server you do not control answers a webhook, URL preview, crawler or other URL fetch with Brotli. Node's fetch() decodes it, and so do urllib3, requests, aiohttp, httpx and Scrapy whenever the Python Brotli or brotlicffi package is installed. Your code never has to import Brotli.
[A]Timeline
10-21
Disclosed as Node2Shell. Node.js ships
22.23.4,24.22.1and26.12.1. Upstream Brotli's fix is commit0ed4268.10-22
Python Brotli
1.2.1and brotlicffi1.2.1.0ship; GitHub publishes GHSA-9v8h-qqgm-4m35 for them.10-23
Envoy publishes GHSA-3wvj-j6qm-62mr; Aikido reports the wider impact on Envoy, Istio, Bun, curl and .NET. Go's
andybalholm/brotlifixes its crash-only variant.
[B]Not affected
- Rust's
brotli-decompressor, so Deno and Swamp itself, which is built on Deno. - The Java, JavaScript and C# ports.
- Go's
andybalholm/brotlihas the same logic error but can only crash, and was fixed on 2026-10-23.
[C]Scanners missed it
On 2026-10-23, pip-audit, Dependabot and Safety did not flag the affected PyPI versions. The scanner below checks versions itself instead of relying on them.
Fixed versions
| Component | Fixed in |
|---|---|
| Google Brotli (C), 1.0.3 – 1.2.0 affected | 1.0.10, 1.1.1, 1.2.1 |
| Node.js 22 / 24 / 26 | 22.23.4, 24.22.1, 26.12.1 |
| Node.js 20, 23, 25 and older | no fix (end of life) |
| Bun | 1.4.4 |
| Python Brotli | 1.2.1 |
| Python brotlicffi | 1.2.1.0 |
| Envoy | 1.37.8, 1.38.6, 1.39.3, 1.40.0; 1.36.12 (out of support) |
| Istio | 1.30.6, 1.31.2; 1.29.9 (end of life) |
| Ruby brotli gem | 0.9.1 |
| .NET runtime (bundles Brotli 1.1.0) | no fix released yet |
Distributions backport the fix without changing the upstream version, so a distro build below these numbers may already be fixed. The tracker entries the scanner knows, as of 2026-10-24 18:00 UTC:
| Distro | Package | Status |
|---|---|---|
| Debian 12 (bookworm) | libbrotli1, python3-brotli | fixed in 1.0.9-2+deb12u1 |
| Debian 13 (trixie) | libbrotli1, python3-brotli | fixed in 1.1.0-2+deb13u1 |
| Debian forky / sid | libbrotli1, python3-brotli | 1.2.0-4 and older affected, no fix yet |
| Amazon Linux 2023 | libbrotli, brotli | affected, fix pending |
| Amazon Linux 2023 | nodejs24 | affected, fix pending |
| Amazon Linux 2023 | nodejs22 | not affected |
| Amazon Linux 2023 | nodejs, nodejs20 | no fix planned |
Any other distro build below the upstream fix (Ubuntu, RHEL, Fedora, Alpine, SUSE) is reported as check-vendor, never guessed either way.
Stopgaps
Until everything is patched, reject Brotli request bodies before they reach the decoder:
- nginx:
if ($http_content_encoding ~* "(^|,)\s*br\s*(,|$)") { return 415; } - HAProxy:
http-request deny deny_status 415 if { req.hdr(content-encoding) -m reg -i (^|,)\s*br\s*(,|$) } - Envoy: a route that matches the
content-encodingheader with the same regex and adirect_responseof 415, ahead of the Brotli decompressor filter.
For outbound requests, removing br from Accept-Encoding is not enough: a malicious server can send Brotli anyway. Reject responses whose Content-Encoding includes br before the client decodes them, or uninstall the Python Brotli and brotlicffi packages.
[02]
Check and fix with Swamp
Step 1:Am I affected?
A host is likely exposed if any of these is true.
Upgrading is not the end: a process keeps running the old library until it is restarted.
- It runs Node.js below
22.23.4,24.22.1or26.12.1for its line, or any release of Node.js 20, 23 or 25. - Any Python environment has
Brotlibelow1.2.1orbrotlicffibelow1.2.1.0installed, even if no code imports it. - It runs Envoy, Istio, Bun or the Ruby
brotligem below the fixed versions, or a .NET runtime. - Its system Brotli library, which distro Node.js, curl and .NET link, predates the distro's fix.
- It runs container images built before 2026-10-21.
- It runs Node.js below
Step 2:Install Swamp and the scanner
The @swamp/cve/http2shell extension finds every Brotli decoder on a host: the system library, Node.js, Bun, Python Brotli and brotlicffi, .NET, Envoy, Istio, curl, the Ruby gem, Electron apps, lockfile pins, and processes still running old code.
Each gets a verdict and an action.
scanneeds no root and never uses sudo.Install Swamp:
curl -fsSL https://swamp-club.com/install.sh | shInitialize a new repo (run this in a new empty directory):
swamp repo initPull the scanner:
swamp extension pull @swamp/cve/http2shellCreate the scanner, with the SSH login it uses for remote hosts and the directories to search for lockfiles and virtualenvs:
swamp behavior create @swamp/cve/http2shell brotli-fleet \ --global-arg sshUser=ubuntu \ --global-arg sshKey=~/.ssh/id_ed25519 \ --global-arg searchPaths=/srv,/opt/appsAm I affected (this machine):
swamp behavior run brotli-fleet scanMitigate (a dry run unless you pass dryRun=false):
swamp behavior run brotli-fleet mitigateScan a running container, or an image:
swamp behavior create @swamp/cve/http2shell brotli-api \ --global-arg dockerContainer=api swamp behavior create @swamp/cve/http2shell brotli-img \ --global-arg dockerImage=gcr.io/distroless/nodejs24-debian12 swamp behavior run brotli-img scanAn image is started with
--network nonewhile it is scanned. Distroless images have nosh, so the scanner runs their ownnode,bunandpython3directly, and lists what it could not check.›Image scan results
A real scan of
gcr.io/distroless/nodejs24-debian12as pulled on 2026-10-25: it ships Node.js24.14.0.# Brotli Decoder Scan (CVE-2026-111497, Node2Shell / HTTP2Shell) 1 target(s) scanned: 1 with affected decoders, 0 with only decoders to check with the vendor, 0 patched or without Brotli ┌─────────────────────────────────────┬──────────┬──────────┬──────────────┬────────────┬──────┬───────────────┐ │ Target │ Decoders │ Affected │ Check vendor │ Worst │ Risk │ Action │ ├─────────────────────────────────────┼──────────┼──────────┼──────────────┼────────────┼──────┼───────────────┤ │ gcr.io/distroless/nodejs24-debian12 │ 1 │ 1 │ 0 │ vulnerable │ HIGH │ rebuild-image │ └─────────────────────────────────────┴──────────┴──────────┴──────────────┴────────────┴──────┴───────────────┘ ## Affected decoders ┌─────────────────────────────────────┬──────┬──────────────────┬─────────┬────────────┬──────────┬───────────────┐ │ Target │ Kind │ Path │ Version │ Verdict │ Fixed in │ Action │ ├─────────────────────────────────────┼──────┼──────────────────┼─────────┼────────────┼──────────┼───────────────┤ │ gcr.io/distroless/nodejs24-debian12 │ node │ /nodejs/bin/node │ 24.14.0 │ vulnerable │ 24.22.1 │ rebuild-image │ └─────────────────────────────────────┴──────┴──────────────────┴─────────┴────────────┴──────────┴───────────────┘ ## Recommendations * Rebuild this image on a base and runtime with the fix (Node.js 22.23.4, 24.22.1 or 26.12.1; Brotli 1.2.1 / brotlicffi 1.2.1.0; patched distro brotli) and redeploy; do not patch running containers. ## Not checked * gcr.io/distroless/nodejs24-debian12: The image has no sh: only its node, bun and python runtimes were checked. OS packages, .NET, curl, lockfiles and processes were not. ## Stopgaps until everything is patched * nginx: reject Brotli request bodies before they reach the app: if ($http_content_encoding ~* "(^|,)\s*br\s*(,|$)") { return 415; } * HAProxy: http-request deny deny_status 415 if { req.hdr(content-encoding) -m reg -i (^|,)\s*br\s*(,|$) } * Envoy: add a route that matches header content-encoding with safe_regex (?i)(^|,)\s*br\s*(,|$) and a direct_response of 415, ahead of the Brotli decompressor filter. * Outbound: removing br from Accept-Encoding is not enough, because a malicious server can send Brotli anyway; reject responses whose Content-Encoding includes br before the client decodes them, or uninstall the Python Brotli and brotlicffi packages (clients fall back to gzip and deflate). Fixed upstream: Brotli 1.0.10, 1.1.1, 1.2.1; Node.js 22.23.4, 24.22.1, 26.12.1; Python Brotli 1.2.1, brotlicffi 1.2.1.0. Distro verdicts use the tracker snapshot from 2026-10-24T18:00Z. No indicators of compromise have been published.Step 3:Scan your fleet
Pass a comma-separated list of hosts and
scanFleetscans Linux, macOS and Windows hosts in parallel over SSH. A host that needs a different login takes it asuser@host; plain entries usesshUser, and every host usessshKey.Windows hosts are scanned with a PowerShell probe that needs no administrator rights. A host it cannot reach is reported as an error, never as clean.
swamp behavior run brotli-fleet scanFleet \ --input hosts=ubuntu@192.0.2.10,Administrator@192.0.2.11,192.0.2.12›Scan results
A real scan of a container named
shop-apibuilt onnode:24.22.0-bookworm-slim, with an Express 5 app in/srv/apiand a Python worker virtualenv on Brotli1.2.0in/srv/worker, scanned withsearchPaths=/srv:# Brotli Decoder Scan (CVE-2026-111497, Node2Shell / HTTP2Shell) 1 target(s) scanned: 1 with affected decoders, 0 with only decoders to check with the vendor, 0 patched or without Brotli ┌─────────────────┬──────────┬──────────┬──────────────┬────────────┬──────┬───────────────┐ │ Target │ Decoders │ Affected │ Check vendor │ Worst │ Risk │ Action │ ├─────────────────┼──────────┼──────────┼──────────────┼────────────┼──────┼───────────────┤ │ docker-shop-api │ 2 │ 2 │ 0 │ vulnerable │ HIGH │ rebuild-image │ └─────────────────┴──────────┴──────────┴──────────────┴────────────┴──────┴───────────────┘ ## Affected decoders ┌─────────────────┬────────┬───────────────────────────────────────────────┬─────────┬────────────┬──────────┬───────────────┐ │ Target │ Kind │ Path │ Version │ Verdict │ Fixed in │ Action │ ├─────────────────┼────────┼───────────────────────────────────────────────┼─────────┼────────────┼──────────┼───────────────┤ │ docker-shop-api │ node │ /usr/local/bin/node │ 24.22.0 │ vulnerable │ 24.22.1 │ rebuild-image │ ├─────────────────┼────────┼───────────────────────────────────────────────┼─────────┼────────────┼──────────┼───────────────┤ │ docker-shop-api │ python │ /srv/worker/venv/lib/python3.11/site-packages │ 1.2.0 │ vulnerable │ 1.2.1 │ rebuild-image │ └─────────────────┴────────┴───────────────────────────────────────────────┴─────────┴────────────┴──────────┴───────────────┘ ## Exposure * docker-shop-api: /srv/api/package-lock.json: body-parser 2.4.0 inflates Brotli request bodies into Node's decoder * docker-shop-api: /srv/api/package-lock.json: express 5.1.0 inflates Brotli request bodies into Node's decoder * docker-shop-api: requests in /srv/worker/venv/lib/python3.11/site-packages decodes Brotli responses automatically * docker-shop-api: urllib3 in /srv/worker/venv/lib/python3.11/site-packages decodes Brotli responses automatically ## Recommendations * Rebuild this image on a base and runtime with the fix (Node.js 22.23.4, 24.22.1 or 26.12.1; Brotli 1.2.1 / brotlicffi 1.2.1.0; patched distro brotli) and redeploy; do not patch running containers. ## Stopgaps until everything is patched * nginx: reject Brotli request bodies before they reach the app: if ($http_content_encoding ~* "(^|,)\s*br\s*(,|$)") { return 415; } * HAProxy: http-request deny deny_status 415 if { req.hdr(content-encoding) -m reg -i (^|,)\s*br\s*(,|$) } * Envoy: add a route that matches header content-encoding with safe_regex (?i)(^|,)\s*br\s*(,|$) and a direct_response of 415, ahead of the Brotli decompressor filter. * Outbound: removing br from Accept-Encoding is not enough, because a malicious server can send Brotli anyway; reject responses whose Content-Encoding includes br before the client decodes them, or uninstall the Python Brotli and brotlicffi packages (clients fall back to gzip and deflate). Fixed upstream: Brotli 1.0.10, 1.1.1, 1.2.1; Node.js 22.23.4, 24.22.1, 26.12.1; Python Brotli 1.2.1, brotlicffi 1.2.1.0. Distro verdicts use the tracker snapshot from 2026-10-24T18:00Z. No indicators of compromise have been published.Step 4:Mitigate across your fleet
mitigatescans first. On Linux it upgrades every installed Brotli library and binding package, and any affected distro Node.js, through apt, dnf, yum or apk, restarts the systemd units still running the old library, then scans again.Containers are reported for an image rebuild and never changed in place. macOS and Windows hosts get a saved script with the exact commands.
dryRundefaults totrue— the method shows the plan and changes nothing:swamp behavior run brotli-fleet mitigate \ --input hosts=ubuntu@192.0.2.10,192.0.2.12Pass
dryRun=falseto apply:swamp behavior run brotli-fleet mitigate \ --input hosts=ubuntu@192.0.2.10,192.0.2.12 \ --input dryRun=falseAdd
upgradePython=trueto also pip-upgrade Brotli and brotlicffi in the virtualenvs the scanning user owns. Another user's environment is reported with the command for its owner to run, never touched:swamp behavior run brotli-fleet mitigate \ --input hosts=ubuntu@192.0.2.10 \ --input dryRun=false \ --input upgradePython=trueMitigation needs
root, or passwordless sudo for the exact commands the dry run prints. If sudo would ask for a password, the host is reported asneeds-privilegeand nothing is changed. Upstream Node.js, Bun, Envoy and .NET are reported with the command to run.›Mitigation results
A dry run on the same
shop-apicontainer: mitigate never changes a container, and reports the image to rebuild.# Brotli (CVE-2026-111497) Mitigation: DRY RUN ┌─────────────────┬──────────────────┬────────────┬──────────┬───────────────┐ │ Target │ Outcome │ Privilege │ Risk now │ Action │ ├─────────────────┼──────────────────┼────────────┼──────────┼───────────────┤ │ docker-shop-api │ no-automatic-fix │ not-needed │ HIGH │ rebuild-image │ └─────────────────┴──────────────────┴────────────┴──────────┴───────────────┘ ## docker-shop-api * Target is a container: rebuild its image on a fixed base and runtime instead of changing it in place.The script mitigate saves for a Mac, generated by @swamp/cve/http2shell 2026.10.24.1 for a Mac with Homebrew Brotli
1.2.0, Homebrew Node.js 24, an nvm Node.js24.22.0and a project virtualenv on Brotli1.2.0. Homebrew's Node.js links Homebrew's Brotli, so upgrading Brotli fixes both:#!/bin/sh # CVE-2026-111497 (Brotli decoder RCE): fixes for this Mac, from @swamp/cve/http2shell. set -e brew upgrade brotli # /Users/dev/.nvm/versions/node/v24.22.0/bin/node: Node.js 24.22.0; install 24.22.1 with the tool that installed it /Users/dev/src/crawler/.venv/bin/python3.12 -I -m pip install --upgrade 'Brotli>=1.2.1' 'brotlicffi>=1.2.1.0' # Then restart the apps and services that use Node.js, Python or curl.
And for a Windows host with Node.js
24.22.0from the official installer, Python 3.12 on Brotli1.2.0and .NET8.0.21:# CVE-2026-111497 (Brotli decoder RCE): fixes for this Windows host, from @swamp/cve/http2shell. # Run in an elevated PowerShell session. winget upgrade --id OpenJS.NodeJS.LTS --exact --accept-source-agreements --accept-package-agreements & 'C:\Python312\python.exe' -I -m pip install --upgrade 'Brotli>=1.2.1' 'brotlicffi>=1.2.1.0' # C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.21: .NET 8.0.21 bundles Brotli 1.1.0 and has no fix yet; install the fixed runtime when Microsoft releases it. # Then restart the apps and services that use Node.js or Python.
[03]
References
- Node.js October 2026 security releasesFixed Node.js 22, 24 and 26 releases
- GHSA-9v8h-qqgm-4m35Python Brotli and brotlicffi
- GHSA-3wvj-j6qm-62mrEnvoy
- Aikido: the Python packages and wider impact
- Aikido: Node2Shell
- Imperva: from Brotli data to remote code execution
- Broccoli2Shell tracker
- Debian security tracker
- Amazon Linux security center
Find every decoder before someone else does
Pull the scanner and run it against every host, container and image you own.
swamp extension pull @swamp/cve/http2shell