Is your service exposed to HTTP2Shell? It's a remote code execution bug in Google's Brotli decoder, under active attack. It affects Node.js, Bun, Electron, Python, .NET, Java, curl and OS libbrotli packages.
- Check by hand: read the guide for the command and fixed version for each stack. Every version it gives is sourced in the version references.
- Check automatically: run the read-only script in Quickstart.
- Read-only: it reads files and changes nothing on your system.
- No network: container probes run with
--network=noneand never pull images. They also run read-only, with no new privileges, all capabilities dropped, 256 processes and 512 MB of memory. Untagged images are probed by ID, and any still shown as<none>are reported as SKIP. - Runs only version commands, such as
node --versionfor tools on your PATH. It never runs anything it finds under the scan directories. - Needs bash 3.2 or later on macOS or Linux. No root needed.
git clone https://github.com/fieldse/brotli-http2shell-checker.git
cd brotli-http2shell-checker
less scripts/vuln-check-CVE2026-111497-brotli.sh # review before running
./scripts/vuln-check-CVE2026-111497-brotli.sh -r ~/src -q # scan a source tree, report problems only
./scripts/vuln-check-CVE2026-111497-brotli.sh -x ~/private # default roots, skipping one directory
./scripts/vuln-check-CVE2026-111497-brotli.sh --help # all optionsEach output line reads LEVEL [NN] message, where NN is the check that produced it.
FAIL: a known-vulnerable version. Upgrade it.WARN: needs a manual look, such as an unknown version, a distro build, or a Brotli-decoding framework on an unpatched runtime.PASS,INFO,SKIP: fixed, information only, or not checked.
Exit codes:
0: noFAIL1: anyFAIL, or anyWARNwith--strict. Use this to gate CI.2: usage or environment error
With no -r, the script scans:
$HOME- the Homebrew prefix (
/opt/homebrewor/usr/local) /Applications, on macOS
Homebrew formulae and app bundles carry their own Node.js, Electron and Python packages, so a $HOME-only scan misses them.
-r DIR: scan this directory instead of the defaults. Repeatable.-x DIR: never enter this directory. Repeatable.-q: print onlyFAILandWARNlines, plus the summary.-s,--strict: exit 1 onWARNas well asFAIL.-c: also check local Docker or Podman images.- Inside an image it checks only the OS packages, the
nodeandbunon its PATH, and the system Python's packages. - It skips virtualenvs,
node_modules, version-manager installs, .NET runtimes, jars, bundledlibbrotlideccopies and vendored source.
- Inside an image it checks only the OS packages, the
-i IMAGE: check only this image. Implies-c.-e ENGINE:dockerorpodmanfor-c. Default: Docker if it responds, else Podman.-d: also search every binary inside.appbundles. Slow: minutes on a full/Applications.--help: every option, with long forms.
- A symlinked directory also excludes its target.
- Tools installed inside it, such as
node,bun,brew,dotnetandcurl, show asSKIPand are not run. - Python
sys.pathentries inside it are skipped. - Matching ignores case on macOS.
- Excluding
/is a usage error.
The check numbers are stable.
| No. | Check |
|---|---|
| 00 | Header |
| 01 | OS packages (dpkg, apk, rpm, pacman, Homebrew) |
| 02 | node and bun on PATH |
| 03 | Node.js installs from nvm, volta, asdf, fnm |
| 04 | .NET runtimes |
| 05 | Python dist-info and egg-info metadata |
| 06 | Python compiled extensions and Perl IO::Compress::Brotli |
| 07 | Python pins in requirements and constraints files, pyproject.toml, Pipfile.lock, poetry.lock, uv.lock, pdm.lock |
| 08 | Host Python packages |
| 09 | npm middleware that decodes Brotli |
| 10 | Java brotli4j |
| 11 | Ruby brotli gem |
| 12 | Go modules |
| 13 | Rust crates |
| 14 | Vendored decoder sources (decode.c) |
| 15 | Bundled libbrotlidec copies |
| 16 | curl |
| 17 | Envoy, Istio, nginx |
| 18 | Container engine preflight and images (-c, -i) |
| 19 | Other node binaries bundled by apps and tools, judged without running them |
| 20 | Electron apps (see Electron) |
| 21 | Executables and libraries inside .app bundles (-d) |
--help explains how each check decides between FAIL and WARN.
Every Electron release from 42.0.0 on is fixed. Releases below 42 get FAIL, and Electron 46 prereleases get WARN until 46.0.0 ships. Electron uses Chromium's copy of Brotli, and Chromium 148, first shipped in Electron 42, carries the fix. Sources are in the version references.
How the Electron cut-off was confirmed
- Chromium's copy decides. Electron builds its Node.js against Chromium's Brotli (
//third_party/brotli:dec), not Node'sdeps/brotli(patches/node/build_add_gn_build_files.patch). Electron's later Node 24.22.1 bumps don't change the decoder. - Fix commit. The fix is Brotli commit 0ed4268a, cherry-picked into 1.0.10, 1.1.1 and 1.2.1.
- Chromium 148. Chromium rolled Brotli to
cfa184ain 224140d410 (main@{#1603247}, 2026-03-23).cfa184acontains 0ed4268a. Chromium 148 branched at#1610480(Chromium Dash milestones). - Electron 42. Electron 42.0.0 ships Chromium 148.0.7778.96 (releases.json), whose
third_party/brotli/README.chromiumrecordscfa184a. Electron 43, 44 and 45 ship Chromium 150, 152 and 156. - Electron 41 and older. Electron 41.10.7, the last 41 release, ships Chromium 146.0.7680.216, whose Brotli revision
9801a2cpredates the fix. The 39, 40 and 41 branches carry no Brotli patch of their own. - Cross-check. Real Electron 42.4.1, 43.x and 44.x frameworks contain the
_ERROR_FORMAT_BLOCK_SWITCHmarker. Electron 30.0.1 and 39.8.7 contain only the unfixed 1.0.x error names.
Check the script yourself against known-vulnerable containers. The four examples cover the OS libbrotli1 package on Debian 12 and Ubuntu 22.04, the Python Brotli package, and the Node.js runtime.
- Vulnerable build:
FAIL, exit 1. - After the upgrade:
PASS, exit 0. - Run from the repository root.
-r /rootscans the container's real home directory, and nothing is planted. - Podman works in place of
dockerin every example.
docker run --rm -it -v "$PWD/scripts/vuln-check-CVE2026-111497-brotli.sh":/check.sh:ro debian:bookworm-slim bash
# inside the container
apt-get update && apt-get install -y libbrotli1=1.0.9-2+b6
bash /check.sh -r /root # FAIL [01] libbrotli1
apt-get install -y --only-upgrade libbrotli1 # 1.0.9-2+deb12u1
bash /check.sh -r /root # PASS [01] libbrotli1docker run --rm -it -v "$PWD/scripts/vuln-check-CVE2026-111497-brotli.sh":/check.sh:ro ubuntu:22.04 bash
# inside the container
apt-get update && apt-get install -y libbrotli1=1.0.9-2build6
bash /check.sh -r /root # FAIL [01] libbrotli1
apt-get install -y --only-upgrade libbrotli1 # 1.0.9-2ubuntu0.1
bash /check.sh -r /root # PASS [01] libbrotli1docker run --rm -it -v "$PWD/scripts/vuln-check-CVE2026-111497-brotli.sh":/check.sh:ro python:3.13-slim bash
# inside the container
pip install Brotli==1.2.0
bash /check.sh -r /root # FAIL [08] brotli
pip install Brotli==1.2.1
bash /check.sh -r /root # PASS [08] brotliNode is fixed by moving to a newer image tag.
docker run --rm -v "$PWD/scripts/vuln-check-CVE2026-111497-brotli.sh":/check.sh:ro node:24.22.0-slim bash /check.sh -r /root # FAIL [02] node
docker run --rm -v "$PWD/scripts/vuln-check-CVE2026-111497-brotli.sh":/check.sh:ro node:24.22.1-slim bash /check.sh -r /root # PASS [02] nodeThe affected and fixed versions come from vendor advisories, distro security trackers and upstream releases. The sources table lists each version with the source behind it.
Found something wrong? Have an improvement? Open an issue or contact me on LinkedIn.
If you find this useful, please let me know.