printf '%s\n' "$PATH" | tr ':' '\n' | while IFS= read -r d; do case $d in "~"/*) d=$HOME/${d#"~"/} ;; /*) ;; *) continue ;; esac; [ -f "$d/node" ] && [ -x "$d/node" ] && printf '%s %s\n' "$("$d/node" -v </dev/null 2>&1)" "$d/node"; done
Wardlatch · Brotli · Checks by hand · Free
CVE-2026-111497The Brotli decoder flaw: are you affected?
Brotli is a compression library built into Node.js, Bun, Python packages, web browsers and the Envoy and Istio proxies. A flaw in its decoder lets crafted compressed data corrupt memory, and Google, which makes Brotli, says that could let an attacker who needs no login run code. Find the copies you run and check their versions below. Wardlatch doesn’t check this flaw yet, so these checks are by hand; each one only reads.
- Node.jsthe 22, 24 and 26 lines22.23.4 · 24.22.1 · 26.12.1
- Pythonthe Brotli and brotlicffi packagesBrotli 1.2.1 · brotlicffi 1.2.1.0
- Bun1.0.21 to 1.4.3 are affected1.4.4
- Envoywith Brotli decompression set up1.40.0 · 1.39.3 · 1.38.6 · 1.37.8 · 1.36.12
- Istiowith Brotli decompression turned on1.31.2 · 1.30.6 · 1.29.9
- The Brotli library1.0.3 to 1.2.0 are affected, but not 1.0.10 or 1.1.11.0.10 · 1.1.1 · 1.2.1
What it is, and who is exposed.
CVE-2026-111497 is a buffer overflow in Brotli’s decoder, the part that unpacks compressed data. Google’s record says that in Brotli 1.0.3 to 1.2.0 (apart from 1.0.10 and 1.1.1, which are fixes), on every platform, a crafted compressed stream lets an attacker who needs no login corrupt the decoder’s memory, read memory they shouldn’t, crash the program, or possibly run code; Google rates it 9.2 out of 10, critical, and Node.js rates it high. The fix had been in Brotli’s public source code, though in no release, since 27 February 2026, under a title that didn’t mark it as a security fix; Google released it as 1.0.10, 1.1.1 and 1.2.1 on 21 October 2026. Node.js, Bun, Envoy, Istio and the Python packages have shipped fixed releases; Linux distributions are still rolling theirs out (§06).
What matters is whether something you run unpacks Brotli data that someone else can send it, and that often happens without anyone asking for it. Bun says its fetch() asks servers for Brotli and unpacks a Brotli answer by itself. Node.js’s fetch() does the same: we tested Node.js 26.12.1, and over HTTPS it asks for Brotli, and it unpacks a Brotli answer either way (Node.js’s own advisory names data an app decompresses with node:zlib). In Python, requests asks for Brotli and urllib3 unpacks it whenever a Brotli package is installed. So an app that downloads from servers you don’t control can reach the decoder without ever calling a decompress function. Envoy and Istio are affected where Brotli decompression is set up. When in doubt, update: it’s quicker than proving a copy is never reached.
OpenAI’s security researchers reported it: Node.js and Envoy credit them. Many programs carry their own copy of Brotli, built in; some, such as Homebrew’s Node.js and Debian’s, use the system’s library instead. Either way, a fix reaches you only when every copy is updated: each Node.js version you have installed, each Python environment, the system library, each container image and each app. Then restart services and long-running programs: a running program keeps the old decoder loaded until it restarts.
Wardlatch 0.1.9 doesn’t check for this flaw yet. The steps below find each copy by hand. They print versions and change nothing; the fix lines are marked as fixes.
Check every Node.js on your path.
Paste this into a terminal on macOS or Linux running bash or zsh, the usual shells. It looks in each folder on your PATH for a program called node and prints its version beside where it is. No output means no node on your path; if node -v still prints a version, it most likely comes from a version manager that loads on first use (see the note below the rows). It reads ~/ entries as your home folder and skips other entries that aren’t full paths, since those point into whatever folder you’re in. The same node can appear twice where one folder links to another, such as /bin and /usr/bin.
Most builds of Node.js carry their own Brotli, and the rows below go by their version. Some use the system’s library instead, and then that library decides it: Homebrew’s do, and Debian’s tracker says Debian’s own do. To tell, run otool -L (Mac) or ldd (Linux) on the path a line shows, for example otool -L /opt/homebrew/bin/node | grep brotli or ldd /usr/bin/node | grep brotli. A libbrotlidec line means the system library: check that in §06 instead, and ignore the version numbers on the line, which are the ones node was built against, not the one it uses now. No output means its own copy, and the rows below apply. On a Mac, otool comes with Xcode’s command-line tools; if they’re missing, run ls -l on the path instead: a node in, or pointing into, /opt/homebrew or a Cellar folder is Homebrew’s (check §06); one in /opt/local is MacPorts’, whose Node.js 26 also uses the system library (check §06); any other carries its own copy.
- Fixed
- v22.23.4 or later on the 22 line, v24.22.1 or later on 24, v26.12.1 or later on 26.
- Update
- An earlier version on the 22, 24 or 26 line: install the fixed release for your line from nodejs.org, your version manager or your package manager.
- No fix
- v20 and older, and the odd-numbered lines (v21, v23, v25): they are end-of-life and get no fix from the Node.js project. Move to 22, 24 or 26.
A version manager keeps one copy per installed version, and the line above only sees the one on your path. List the others with nvm ls, fnm list, volta list node, mise ls node or asdf list nodejs, and update or remove the old ones. Rebuild Docker images on a fixed Node.js, and update apps that carry their own Node.js, such as Electron apps, when their makers ship a fix.
Find the Brotli and brotlicffi packages.
Each Python environment keeps its own copy of the Brotli or brotlicffi package, if it has one. GitHub’s advisory: Brotli 1.0.3 up to but not including 1.2.1, and brotlicffi 1.0.3 up to but not including 1.2.1.0, are affected. This lists each copy of those two packages in the folders it searches, and where it is; the version is in the name. It can take a few minutes. On a Mac it skips ~/Library, apart from the folders where Python, Poetry, PDM, Hatch and pipx keep environments, and macOS may ask whether Terminal may open some folders, such as Documents: you can say no, but environments inside those folders won’t be found.
find -H "${HOME%/}" /usr /opt /srv /var/www /Library/Frameworks /Library/Python "${HOME%/}/Library/Python" "${HOME%/}/Library/Caches/pypoetry" "${HOME%/}/Library/Application Support/hatch" "${HOME%/}/Library/Application Support/pipx" "${HOME%/}/Library/Application Support/pdm" "${HOME%/}/Library/Application Support/pypoetry" \( -path "${HOME%/}/Library" -o -name node_modules -o -name .git -o -path '*/.cache/uv' -o -path '*/.local/share/containers' -o -path '*/.local/share/flatpak' -o -path '*/.local/share/docker' -o -path '*/pkgs/brotli-python-*' -o -path '*/pkgs/brotlicffi-*' \) -prune -o \( -iname 'brotli-*.dist-info' -o -iname 'brotlicffi-*.dist-info' -o -iname 'brotli-*.egg-info' -o -iname 'brotlicffi-*.egg-info' -o -iname 'brotli-*.egg' -o -iname 'brotlicffi-*.egg' \) -print 2>/dev/null
- Fixed
…/brotli-1.2.1.dist-infoor later,…/brotlicffi-1.2.1.0.dist-infoor later (the name may start with a capital B).- Update
- Anything earlier, such as
…/Brotli-1.1.0.dist-info, in a virtual environment, conda environment, pyenv, user folder, python.org or Homebrew Python: update it (below). - Distribution
- A line under
/usr/lib/python3/dist-packages,/usr/lib/python3.X/site-packagesor/usr/lib64/…is usually your Linux distribution’s package. Ask which package owns it: type the command, a space and the line in quotes, for exampledpkg -S '/usr/lib/python3/dist-packages/Brotli-1.0.9.egg-info'on Debian or Ubuntu;rpm -qf(Fedora, Red Hat) andpacman -Qo(Arch) work the same way. On Alpine, runapk info --who-ownson theMETADATAfile inside that folder (apk before 3.0, in Alpine 3.22 and older, can’t look up a folder). If a package owns it, its folder may keep the original version number even after a security fix, so check that package’s version against your distribution’s security page (§06 links Ubuntu’s, Debian’s and Red Hat’s), and update it with the system’s package manager, not pip. If no package owns it, it was installed with pip as root: update it as below. - An app’s own
- A line inside
/opt/homebrew/Cellar/…or alibexecfolder belongs to that Homebrew formula or app. It’s fixed when Homebrew or the app’s maker ships a version with Brotli 1.2.1: runbrew upgradeand its name, or update the app, and check again; don’t change it with pip. - Nothing
- No lines: no Brotli or brotlicffi package in the folders searched. It searches only your home folder (on a Mac without
~/Library, apart from the folders named above),/usr,/opt,/srv,/var/wwwand, on a Mac,/Library/Frameworksand/Library/Python. Everything else is not searched, including/Applications, other disks (/Volumes,/mnt,/media),/var/lib, Linuxbrew (/home/linuxbrew), Snap, Flatpak and Nix, folders reached through a link,node_modulesfolders, Git folders (.git), Docker and Podman images, other people’s home folders, uv’s and conda’s caches, and folders you can’t open or said no to.
To update a line, find its Python. For a virtual environment, pyenv, python.org or Homebrew Python (for a conda environment, see conda below), take the part of the line before /lib/python3.X/ and add /bin/python3.X, with the version from the line for 3.X. Keep the quotes: some of these paths have spaces in them.
'/path/to/env/bin/python3.X' -m pip install --upgrade 'Brotli>=1.2.1'
- Change the path between the quotes to that Python. For brotlicffi, use
'brotlicffi>=1.2.1.0'; on an Intel Mac, pip builds brotlicffi 1.2.1.0 from source, which needs Xcode’s command-line tools. - A user folder.
~/Library/Python/3.9/…belongs to Apple’s Python:/usr/bin/python3 -m pip install --user --upgrade 'Brotli>=1.2.1'. For another~/Library/Python/3.X/…, or~/.local/lib/python3.X/…on Linux:python3.X -m pip install --user --upgrade 'Brotli>=1.2.1'. - Apple’s Python (
/Library/Python/3.9/…, installed there with sudo):sudo /usr/bin/python3 -m pip install --upgrade 'Brotli>=1.2.1'. - Installed with sudo pip on Linux (
/usr/local/lib/python3.X/…,/usr/local/lib64/…, or a/usr/libline no package owns):sudo python3.X -m pip install --upgrade 'Brotli>=1.2.1'. - “Externally managed.” If pip refuses with that (Homebrew’s Python, Debian 12, Ubuntu 23.04 and later), add
--break-system-packagesto the line. Homebrew has no formula for this package, so a Brotli in Homebrew’s Python was installed with pip; you can also remove it and use a virtual environment. - uv environments have no pip:
uv pip install --python '/path/to/env/bin/python3' --upgrade 'brotli>=1.2.1'.uvxanduv run --withrun from uv’s cache, which the search skips:uv cache cleanmakes them fetch fixed versions next time. - conda:
conda list -n ENV brotlilists the Brotli packages in an environment, including the library itself (libbrotlidec), which the search above doesn’t see. Update withconda install -n ENV 'brotli-python>=1.2.1'(or'brotlicffi>=1.2.1.0'), andconda install -n ENV 'libbrotlidec>=1.2.1'if the library is older. On 24 October conda-forge had all three for every platform it builds them for. Anaconda’s own channel had brotli-python 1.2.1 only for Linux (x86-64 and ARM), Apple-silicon Macs and 64-bit Windows, and no fixed brotlicffi; for brotlicffi, and on other platforms, add-c conda-forgeto those lines. - Lock files put the old version back on the next install: in a uv project,
uv lock --upgrade-package brotlithenuv sync; with Poetry,poetry update brotli; in a requirements file, raise the pin toBrotli>=1.2.1.
Some Python packages carry their own copy of the Brotli library inside them, which the search above doesn’t see. On our own Mac, the macOS wheels of Pillow 12.3.0 and 13.0.0 (the latest on 24 October) carried libbrotlidec.1.2.0, and opencv-python 5.0.0.93 (also the latest) carried libbrotlidec.1.1.0; copies like these are fixed when those packages ship a release with a fixed Brotli. That comes from looking inside our installed wheels, not from an advisory.
Update Bun to 1.4.4.
Bun’s advisory: Bun 1.0.21 to 1.4.3 bundle an affected Brotli, and Bun 1.4.4 bundles the fixed Brotli 1.2.1.
bun --version
It needs to print 1.4.4 or later. Update the way you installed it: bun upgrade for Bun’s own installer, brew upgrade bun for Homebrew, and a 1.4.4 or later image tag in Docker and CI. A program built with bun build --compile carries the Bun it was built with: Bun says it stays affected until you rebuild it with 1.4.4 or later.
Proxies that unpack Brotli.
Envoy’s advisory covers deployments that have the Brotli decompressor configured; Istio’s covers users who turn Brotli decompression on, for example with an EnvoyFilter. If you use either, check the version you run. Envoy usually runs from a container image, whose tag is its version.
envoy --version
istioctl version
| Project | Affected | Fixed in |
|---|---|---|
| Envoy | The advisory names 1.39.2, 1.38.5, 1.37.7 and 1.36.11; going by Google’s range, earlier releases on those lines are affected too | 1.40.0, 1.39.3, 1.38.6, 1.37.8, 1.36.12 |
| Istio | 1.31.0 to 1.31.1, 1.30.0 to 1.30.5, 1.29.0 to 1.29.8 | 1.31.2, 1.30.6, 1.29.9 (ISTIO-SECURITY-2026-007) |
istioctl version also asks your current cluster for its versions, which only reads; add --remote=false to see the local tool alone. Istio says 1.29.9 is the last 1.29 release, as 1.29 reached end of life on 15 October: plan a move to 1.30 or later. On an older line of either project, move to a fixed line.
The Brotli on your system.
Some programs use the system’s copy of Brotli instead of their own, Homebrew’s Node.js among them. On a Mac with Homebrew, the fix is Brotli 1.2.1.
brew list --versions brotli
It should list 1.2.1. If it lists only older versions, run brew upgrade brotli; Homebrew may upgrade programs that depend on it at the same time. Homebrew keeps old versions on disk until you remove them, so it can list two: after the upgrade, 1.2.1 is the one in use, and brew cleanup brotli removes the rest. If it prints nothing, Homebrew’s Brotli isn’t installed; “command not found” means there is no Homebrew.
On Linux, distributions often fix a library without changing its version number, so follow your distribution’s own page for this CVE: Ubuntu, Debian, Red Hat, and install its updates when they arrive. To see what you have: apt list --installed '*brotli*' (Debian, Ubuntu), rpm -qa '*brotli*' (Fedora, Red Hat) or apk list --installed 'brotli*' (Alpine); with MacPorts on a Mac, port installed brotli (the one marked “(active)” should be 1.2.1; if it isn’t, upgrade brotli with MacPorts). On 24 October, Debian had fixed Brotli in 1.0.9-2+deb12u1 (Debian 12) and 1.1.0-2+deb13u1 (Debian 13), and Ubuntu’s page still said “needs evaluation”. Web browsers unpack Brotli on most websites: keep them updated. Other apps that bundle Brotli are fixed when their makers ship an update; Google lists 1.0.10, 1.1.1 and 1.2.1 as the fixed versions to look for.
Narrow the way in, then update.
The exposure is unpacking Brotli data that someone else controls. Some of that can be switched off; much of it can’t, so updating is the fix.
- Envoy and Istio: remove the Brotli decompressor, and any Istio EnvoyFilter that adds it.
- Bun: pass
decompress: falseto eachfetch()that talks to a server you don’t control or uses plainhttp://; Bun then neither asks for Brotli nor unpacks the answer. Don’t pass untrusted data tonode:zlib’s Brotli functions or toDecompressionStreamwith thebrotliformat. Bun’s advisory says there is no process-wide workaround. - Python: requests asks for Brotli, and urllib3 unpacks it, only while a Brotli package can be imported, so removing Brotli and brotlicffi from an environment that doesn’t need them stops that. This comes from urllib3’s own code, not an advisory; check first that nothing in the environment imports Brotli itself.
- Node.js: its
fetch()unpacks Brotli answers by itself, and Node.js’s advisory offers no workaround: update.
Sources.
- Google’s CVE record, CVE-2026-111497: Brotli 1.0.3 to 1.2.0 affected, apart from 1.0.10 and 1.1.1; fixed in 1.0.10, 1.1.1 and 1.2.1; CVSS 4.0 score 9.2. Published 22 Oct 2026.
- Google’s Brotli releases 1.2.1, 1.1.1 and 1.0.10, 21 Oct 2026. The fix itself landed in Brotli’s source code as commit 0ed4268a, “Detect and report invalid stream earlier.”, on 27 Feb 2026; GitHub’s advisory lists it among the fix commits.
- Node.js, October 2026 security releases: 22.x, 24.x and 26.x affected; the releases 22.23.4, 24.22.1 and 26.12.1; end-of-life versions always affected.
- GitHub’s advisory for the Python packages, GHSA-9v8h-qqgm-4m35: Brotli from 1.0.3 to below 1.2.1, brotlicffi from 1.0.3 to below 1.2.1.0.
- Bun, GHSA-663w-cv8g-jj2p: Bun 1.0.21 to 1.4.3 affected, fixed in 1.4.4;
fetchunpacks Brotli by itself;decompress: falseper request; no process-wide workaround. Published 23 Oct 2026. - Envoy, GHSA-3wvj-j6qm-62mr: fixed in 1.40.0, 1.39.3, 1.38.6, 1.37.8 and 1.36.12. Published 23 Oct 2026.
- Istio, ISTIO-SECURITY-2026-007: Istio 1.29.0 to 1.29.8, 1.30.0 to 1.30.5 and 1.31.0 to 1.31.1; upgrade to 1.31.2, 1.30.6 or 1.29.9, the final 1.29 release.
- The distributions’ pages for the CVE, read 24 Oct 2026: Debian (its nodejs uses the system Brotli library; Brotli fixed for Debian 12 and 13), Ubuntu (needs evaluation) and Red Hat.
- MacPorts’ nodejs26 Portfile, built with
--shared-brotliagainst its brotli port, read 24 Oct 2026. - Package indexes, read 24 Oct 2026: PyPI’s Brotli 1.2.1 and brotlicffi 1.2.1.0 files (no Intel-Mac wheel for brotlicffi), and conda-forge and Anaconda’s brotli-python builds. urllib3’s own code, 2.7.0 and 2.8.0, the latest (
urllib3/util/request.py,urllib3/response.py), for when it asks for and unpacks Brotli.
Briefing: common questions
What is CVE-2026-111497?
A buffer overflow in the decoder of Brotli, Google’s compression library. Google’s record says that in Brotli 1.0.3 to 1.2.0 (apart from 1.0.10 and 1.1.1, which are fixes) a crafted compressed stream lets an attacker who needs no login corrupt memory, read memory, crash the program, or possibly run code, and rates it 9.2 out of 10. Brotli 1.0.10, 1.1.1 and 1.2.1, released on 21 October 2026, fix it.
Is Node.js affected, and which version fixes it?
Yes: Node.js says the 22, 24 and 26 lines are affected, through applications that decompress Brotli with node:zlib, and its fetch() unpacks Brotli answers by itself too (we tested it). Node.js 22.23.4, 24.22.1 and 26.12.1 fix it. Node.js 20 and older get no fix from the Node.js project: move to 22, 24 or 26. Homebrew’s and Debian’s Node.js use the system’s Brotli library instead, so updating that library fixes them. The check lists every node on your path with its version.
Does this affect Python?
Through the Brotli and brotlicffi packages: GitHub’s advisory lists Brotli from 1.0.3 to below 1.2.1 and brotlicffi from 1.0.3 to below 1.2.1.0 as affected. requests asks servers for Brotli, and urllib3 unpacks it, whenever one of them is installed. Each environment has its own copy, so update each one; the check finds them in the usual places. Some packages also carry a Brotli library inside them: we found one in the Mac wheels of Pillow and opencv-python, which are fixed when those packages ship an update.
Are Bun, Envoy and Istio affected?
Bun 1.0.21 to 1.4.3 are, and Bun 1.4.4 fixes it (Bun’s advisory). Envoy and Istio are where Brotli decompression is set up: Envoy 1.40.0, 1.39.3, 1.38.6, 1.37.8 and 1.36.12 fix it (Envoy’s advisory), and Istio 1.31.2, 1.30.6 and 1.29.9 (ISTIO-SECURITY-2026-007).
Does Wardlatch check for this?
Not yet. Wardlatch 0.1.9 doesn’t check for CVE-2026-111497. This page’s checks are by hand: they print versions and change nothing.
What if I can’t update today?
Narrow the way in: remove the Brotli decompressor from Envoy, and any Istio EnvoyFilter that adds it; in Bun, pass decompress: false to each fetch() that talks to a server you don’t control or uses plain http://; in Python, remove the Brotli and brotlicffi packages from environments that don’t need them, so requests and urllib3 stop asking for and unpacking Brotli. Bun says there is no process-wide workaround, and Node.js’s advisory offers none, so updating is the fix.