Running apt upgrade on the system Brotli package will not close CVE-2026-111497 on most servers that are exposed to it. The vulnerable decoder is a small piece of C that Node.js, Bun, .NET, the Python Brotli wheels, Envoy and PHP's Windows builds each compile into their own binaries, so every copy has to be replaced separately, and every process still holding the old one has to be restarted. That is the real work this week, and it is an inventory problem before it is a patching problem.
The flaw is an out-of-bounds write in Google's Brotli decompressor. A crafted compressed stream makes the decoder keep appending input past the end of an eight-byte internal buffer, which can crash the process, leak memory or, per the Node.js project, lead to remote code execution (RCE). Severity depends on who you ask: Google's CVSS 4.0 score is 9.2, while Red Hat and Microsoft both land on 8.1. Fixed releases started shipping on October 21, and Microsoft pushed out-of-band .NET updates on October 25.
Who is exposed to CVE-2026-111497?
You are exposed if any program on the host decompresses Brotli data that someone outside your control can supply. In practice that means an HTTP client that fetches remote content and accepts br responses, or a server or proxy that decodes br request bodies. Software that merely has Brotli installed but never decodes untrusted input is not reachable.
That second sentence is where most teams will misjudge their risk, because "decodes untrusted input" happens in places nobody thinks of as attack surface. Two examples from the advisories stand out for anyone running a typical web stack:
-
Python. Simply having the
Brotliorbrotlicffipackage installed is enough forrequests,urllib3,httpxandaiohttpto start decoding Brotli responses automatically. A webhook sender, a link-preview worker or a scraper that calls an attacker-chosen URL becomes the target. -
PHP on Windows. The cURL extension decodes Brotli when
CURLOPT_ENCODINGis set, and Guzzle, a widely used PHP HTTP client, sets it by default. PHP's rebuilt Windows packages keep the same version number, sophp -vcannot tell you whether you have the fix; only the-1build suffix or the published SHA-256 hash does.
The direction matters. Imperva's researchers achieved code execution against an unmodified Node.js 24.22.0 (ARM64 Linux) by serving it a malicious response, and OpenAI's Codex Security team, which reported the bug, points out that a malicious server can take over a vulnerable client. Most CVE triage asks "what do I expose to the internet?" This one also asks "what on my servers reaches out to it?"
| Where the decoder lives | Vulnerable | Fixed in | Reached when |
|---|---|---|---|
| Google Brotli (C library) | 1.0.3 to 1.2.0 | 1.0.10, 1.1.1, 1.2.1 | Code decodes untrusted Brotli data |
| Node.js (bundled copy) | 22.x, 24.x, 26.x before the fix; 20.x and older are end of life | 22.23.4, 24.22.1, 26.12.1 | fetch() of a br response, or br request bodies via body parsers |
| Bun | 1.4.3 and earlier | 1.4.4 | fetch() of a br response, or node:zlib |
| .NET | 8.0, 9.0, 10.0 before the fix | 8.0.33, 9.0.22, 10.0.14 | Brotli decompression enabled in ASP.NET Core or HttpClient |
| Python Brotli / brotlicffi | 1.2.0 and earlier | 1.2.1 / 1.2.1.0 | Package installed; requests, urllib3, httpx or aiohttp then decode br |
| PHP for Windows 8.2 to 8.5 | Earlier builds of the same release | 8.5.12-1, 8.4.27-1, 8.3.36-1, 8.2.35-1 | cURL with CURLOPT_ENCODING set, as Guzzle does by default |
Why the distro update fixes only part of it
Brotli is vendored, not linked. Node.js statically bundles its own copy, Python's Brotli wheels ship their own compiled extension, and .NET carries its own native library. The Linux distribution's libbrotlidec package protects the programs that link against it dynamically and nothing else. BleepingComputer's coverage makes the same point about Node.js specifically; it applies to every row in the table above.
There is a second blind spot in tooling. The CVE entry points at Google's upstream project, not at the PyPI packages people install, and GitHub's advisory database, the feed Dependabot relies on, carried no package entry for it. A dependency scanner that came back clean on a Python project with brotli==1.2.0 pinned is not evidence of safety here. This is the same pattern we saw with OpenSSL's HollowByte bug: when the identifier does not map cleanly onto the package names scanners track, the gap falls to whoever does the manual check.
Third, long-running processes keep the decoder they started with. A Node.js API or a .NET worker that has been up since before the update is still vulnerable after the new binary lands on disk, and so is any container whose image was built before the fix.
An eight-month window that nobody saw open
According to the published timeline, the fix was committed to Brotli's source on February 27 without being labelled as a security issue, and no tagged release carried it until October 21. That gap is worth sitting with. For eight months the patch existed in public, in a repository anyone could diff, while every downstream project kept shipping the vulnerable release because there was no new version to pull. Projects that track tagged releases rather than upstream commits, which is nearly all of them, had no signal at all.
The exploitation picture is mixed, and the sources do not agree. CISA's October 23 assessment recorded no exploitation and the CVE is not in its Known Exploited Vulnerabilities catalog. Red Hat's position is that nobody has published a dependable code-execution exploit, and Imperva has not released its exploit, which took several attempts on a single build. BleepingComputer, however, reports that public proof-of-concept exploits exist. Both can be true: a GitHub search on October 27 returned 62 repositories naming the CVE, many labelled as PoCs, most created after October 22. We have not verified any of them, and a crash reproducer is far easier to publish than a working RCE chain. Treat it as "triggerable in public, weaponization unproven" and patch on that basis.
Find every copy of the decoder on a host
This is the check we would run on each Linux server before closing the ticket. It reports the Brotli version inside the Node.js binary, the Python packages that switch on automatic decoding, the .NET runtimes, any Envoy or Bun install, the Brotli files on disk, and the processes still mapping a system library that has since been replaced.
#!/bin/sh command -v node >/dev/null && echo "node $(node -v) brotli $(node -p process.versions.brotli)" command -v bun >/dev/null && echo "bun $(bun --version)" command -v envoy >/dev/null && envoy --version command -v dotnet >/dev/null && dotnet --list-runtimes for py in python3 /opt/*/bin/python3 /srv/*/venv/bin/python3; do [ -x "$py" ] && "$py" -m pip list 2>/dev/null | grep -iE '^(brotli|brotlicffi) ' | sed "s|^|$py: |" done find / -xdev \( -name 'libbrotlidec*' -o -name '_brotli*.so' -o -name 'brotlicffi*' \) 2>/dev/null grep -l 'libbrotlidec.*(deleted)' /proc/[0-9]*/maps 2>/dev/null ps -eo pid,lstart,args | grep -wE '[n]ode|[d]otnet|[e]nvoy|[b]un'
Read the output against the version table. A Node.js line reporting Brotli 1.1.0 or 1.2.0 is unpatched whatever the distribution says. Any PID returned by the /proc check is running a deleted, pre-update copy of the system library and needs a restart. The ps start times tell you which runtimes predate your patch window. Adjust the virtualenv glob to wherever your applications keep theirs.
Disable automatic Brotli decoding before you patch
If an upgrade has to wait for a change window, the advisories offer stopgaps that close the client-side path:
-
Python: uninstall
Brotliandbrotlicffi. Clients fall back to gzip and deflate. -
PHP with Guzzle: set
decode_contenttofalse, or stop settingCURLOPT_ENCODINGin raw cURL code. Note this also turns off gzip decoding. -
.NET: Microsoft advises removing the Brotli provider or
DecompressionMethods.Brotliuntil the update is in. -
Inbound: reject requests carrying
Content-Encoding: brat the proxy, before anything decodes them.
Two traps. Removing br from your Accept-Encoding header does not help, because a hostile server can send Brotli anyway. And there is no published workaround for Node.js, so for Node the upgrade to 22.23.4, 24.22.1 or 26.12.1 is the only fix. Debian has shipped fixed brotli packages for bookworm and trixie, and Ubuntu has issued USN-8956-1 for its brotli package; those cover the dynamically linked consumers only.
The broader lesson is about where a "library CVE" lives. If your asset inventory stops at the package manager, a bug like this is invisible to it. Vulnerability detection that reads what is installed on each host, including language packages and runtimes, is what turns a advisory spanning half a dozen runtimes into a short list of servers. We covered the other half of this week's PHP fallout, the PHP2Shell core flaws, in our PHP2Shell analysis, and the release-delay debate that bundled copies complicate in our look at PyPI release cooldowns.