Home/ Blog/ Security news/ Article
Blog · Security news

Brotli CVE-2026-111497: Critical RCE in Bundled Copies, Audit Now

Brotli CVE-2026-111497 sits in copies bundled by Node.js, .NET, Python and PHP, so a distro update is not enough. Fixed versions, stopgaps and a host check.

Row of sealed glass cylinders on a workbench, one cracked open by a spring

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 Brotli or brotlicffi package installed is enough for requests, urllib3, httpx and aiohttp to 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_ENCODING is set, and Guzzle, a widely used PHP HTTP client, sets it by default. PHP's rebuilt Windows packages keep the same version number, so php -v cannot tell you whether you have the fix; only the -1 build 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 livesVulnerableFixed inReached when
Google Brotli (C library)1.0.3 to 1.2.01.0.10, 1.1.1, 1.2.1Code decodes untrusted Brotli data
Node.js (bundled copy)22.x, 24.x, 26.x before the fix; 20.x and older are end of life22.23.4, 24.22.1, 26.12.1fetch() of a br response, or br request bodies via body parsers
Bun1.4.3 and earlier1.4.4fetch() of a br response, or node:zlib
.NET8.0, 9.0, 10.0 before the fix8.0.33, 9.0.22, 10.0.14Brotli decompression enabled in ASP.NET Core or HttpClient
Python Brotli / brotlicffi1.2.0 and earlier1.2.1 / 1.2.1.0Package installed; requests, urllib3, httpx or aiohttp then decode br
PHP for Windows 8.2 to 8.5Earlier builds of the same release8.5.12-1, 8.4.27-1, 8.3.36-1, 8.2.35-1cURL with CURLOPT_ENCODING set, as Guzzle does by default
Affected and fixed versions as published by each project. Source: Node.js, Google, Microsoft, PHP and PyPI advisories via The Hacker News and BleepingComputer, October 2026.

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

Eight months between the fix and the patchFeb 27: Fix lands in source. Oct 21: Brotli, Node.js ship. Oct 23: PyPI fixes out. Oct 25: .NET out-of-band. Oct 26: PHP Windows rebuild.Eight months between the fix and the patchFeb 27Fix lands insourceOct 21Brotli, Node.jsshipOct 23PyPI fixes outOct 25.NET out-of-bandOct 26PHP Windowsrebuild
Source: The Hacker News, BleepingComputer, PHP and Microsoft notices.

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.

brotli-inventory.sh (read-only, run as root on each host)
#!/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 Brotli and brotlicffi. Clients fall back to gzip and deflate.

  • PHP with Guzzle: set decode_content to false, or stop setting CURLOPT_ENCODING in raw cURL code. Note this also turns off gzip decoding.

  • .NET: Microsoft advises removing the Brotli provider or DecompressionMethods.Brotli until the update is in.

  • Inbound: reject requests carrying Content-Encoding: br at 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.

Frequently asked questions

What is CVE-2026-111497?

CVE-2026-111497 is an out-of-bounds write in Google's Brotli decompression library. A crafted compressed stream can make the decoder write past an internal buffer, causing a crash, a memory leak or, according to the Node.js project, remote code execution. Google rates it 9.2 critical on CVSS 4.0.

Does updating the system brotli package fix CVE-2026-111497?

No, not for most exposed software. Node.js, Bun, .NET, the Python Brotli wheels and PHP's Windows builds bundle their own copy of the decoder. The distribution package only protects programs that link against the system library dynamically, so each runtime needs its own update and a restart.

Which Node.js versions fix the Brotli flaw?

Node.js 22.23.4, 24.22.1 and 26.12.1 fix CVE-2026-111497. Node.js 20.x and older are end of life and get no fix. No workaround has been published for Node.js, so upgrading and restarting every running Node process is the only remedy.

Is CVE-2026-111497 being exploited in the wild?

No in-the-wild exploitation had been reported as of October 26, 2026, and the CVE is not in CISA's KEV catalog. Imperva demonstrated code execution on one Node.js build but has not released it. Dozens of GitHub repositories claim proofs of concept; Red Hat says none is a reliable RCE.

How can I stop Python from decoding Brotli before I patch?

Uninstall the Brotli and brotlicffi packages. Once they are gone, requests, urllib3, httpx and aiohttp fall back to gzip and deflate and no longer decode Brotli responses. Removing br from the Accept-Encoding header is not enough, because a malicious server can send Brotli anyway.

Is PHP affected by the Brotli vulnerability?

PHP's official Windows builds for 8.2 through 8.5 bundled an affected Brotli and were rebuilt as 8.5.12-1, 8.4.27-1, 8.3.36-1 and 8.2.35-1. The flaw is reached through cURL when automatic decompression is on, which Guzzle enables by default. The version number does not change, so check the build suffix.

See what Suriq finds on your own servers.

Agentless checks for uptime and your cloud, a lightweight agent for detection on each server, and one console that ranks and explains what it finds.