The Node2Shell bug has turned out to be a Brotli problem, not a Node problem. According to Aikido's write-up, CVE-2026-111497 is an out-of-bounds write in the Google Brotli decoder, versions 1.0.3 through 1.2.0, on all platforms. It carries a CVSS v4.0 score of 9.2 (Critical). By Friday, October 23, it was clear the same decoder sits underneath Python packages, curl, Bun, Envoy, Istio and more.

The part that bites: pip-audit, Dependabot and Safety will not flag this yet. As of October 23, advisory databases don't map the CVE to the Brotli or brotlicffi PyPI packages, and the release notes for the fixed versions don't mention it. You have to check by hand.

(This is a different CVE from the one in the PHP2Shell post.)

What the bug actually is

The flaw is in DecodeBlockTypeAndLength(), which handles block switches in a Brotli stream. In vulnerable versions it returns a plain boolean, so BROTLI_FALSE can mean either "malformed stream" or "I need more input." Callers can't tell which, and on the fast path the return value is discarded entirely.

A crafted stream can declare a single block type and then exhaust that block's length. That forces a switch that can't succeed, and the decoder either asks for more input on a complete stream or keeps decoding with stale block state. The second path is the out-of-bounds write.

The upstream fix is google/brotli commit 0ed4268. It makes the function return an error code and adds BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17). Aikido says Brotli 1.2.1 differs from 1.2.0 only in the version bump and those decoder changes.

In Aikido's AddressSanitizer test, 1.2.0 consumed the whole ~23 MB crafted stream and kept asking for more input, even in one-byte chunks. 1.2.1 rejected it with error -17 every time.

Fixed versions

Project Fixed versions
Brotli (upstream) 1.2.1, or 1.1.1 / 1.0.10 for older branches
brotlicffi (PyPI) 1.2.1.0
Node.js 22.23.4, 24.22.1, 26.12.1
Bun 1.4.4 (includes Brotli 1.2.1)
Envoy 1.40.0, 1.39.3, 1.38.6, 1.37.8 (1.36.12 also fixed but unsupported)
Istio 1.29.9, 1.30.6, 1.31.2 (1.29 is end of life)

The source says patches also exist for C++ and "more are coming." Apache's standard mod_brotli does not decode Brotli, and Apache and Nginx don't decode it by default. For curl/libcurl, update the Brotli dependency; statically linked copies need an application rebuild. OS packages may backport the fix without changing the upstream version number.

Why Python apps are exposed without importing Brotli

urllib3 (and so requests), httpx with the brotli extra, and aiohttp all use Brotli or brotlicffi automatically when installed. Scrapy pulls it in as a dependency. An installed package is enough. The source cites 82.8 million monthly downloads for Brotli.

Attacker data reaches the decoder two ways:

  • Outbound: your app fetches a Brotli response from a server the attacker controls (webhooks, URL previews, crawlers, "fetch this URL" features).
  • Inbound: middleware decodes a request body sent with Content-Encoding: br.

OpenAI reported it as unauthenticated RCE that works in both directions.

The manual check

The commands from the write-up:

pip show Brotli brotlicffi
docker run --rm <image> pip show Brotli brotlicffi
pipdeptree -r -p Brotli
grep -i brotli poetry.lock uv.lock requirements*.txt

Then upgrade:

pip install -U "Brotli>=1.2.1"
pip install -U "brotlicffi>=1.2.1.0"

Regenerate your Poetry, uv or pip-tools lockfile, and rebuild container images. An image built on a vulnerable version stays vulnerable until you rebuild it. Check laptops and CI runners too. I'd start with services that call outside servers or accept compressed request bodies.

If you can't upgrade today

  • Python, Brotli optional: uninstall both Brotli and brotlicffi, drop extras like httpx[brotli], regenerate locks, restart. Clients fall back to gzip and deflate, but a server that sends Brotli anyway may produce an unreadable response or an error.
  • Inbound: reject requests whose Content-Encoding includes br (case-insensitive, including within lists) at the proxy, load balancer or middleware.
  • Outbound: remove br from Accept-Encoding and reject br responses before decoding starts.

The catch: changing Accept-Encoding alone doesn't help. A malicious server can send Brotli regardless, and urllib3 picks its decoder from the response's Content-Encoding header. Uninstalling the Python packages also leaves any Brotli decoders bundled into other runtimes or products untouched.

Exploitation status and timeline

The root cause was fixed in Brotli source in February but wasn't recognized as a security issue, and no release shipped with it until now. That is why every project bundling Brotli has to ship its own update. OpenAI says it found the bug in February.

On exploitation: Aikido notes researchers exploited the same decoder code against an unmodified Node.js 24.22.0 build, and The Hacker News reported no in-the-wild exploitation for Node.js as of October 22. I'd still treat it as urgent. It's a memory-safety flaw in code that parses untrusted network input, and the fix is a version bump.

Sources: