- Book: RAG Pocket Guide
- The series: Pocket Guides for Developers — 8 short books on agents, RAG, prompts, observability, events, databases and system design — the whole series
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
You never imported brotli. You may still be running it. If the package sits anywhere in your Python environment, urllib3 adds br to the Accept-Encoding header it builds, and requests sends that header on every call by default. Every server you fetch from is then invited to answer in Brotli, and your process decodes the reply.
That matters this week. The Brotli decoder bug behind Node.js's "Node2Shell" fix, CVE-2026-111497, lives in Google's C library, which Node only bundles. On October 22 GitHub published an advisory titled "Out-of-bounds write in the Brotli decoder ("Node2Shell") also affects the PyPI Brotli package". On October 23, Aikido called it "HTTP2Shell" and spelled out what that means for Python HTTP clients.
What's new since the Node.js fix
The bug was reported by OpenAI's Codex Security team, per the credits in the Node.js security release, which shipped fixes in 22.23.4, 24.22.1 and 26.12.1. Node's advisory describes it as malformed compressed input that writes "past the end of an internal decoder buffer", which "can lead to a crash, disclosure of process memory, or remote code execution." Imperva showed code execution against an official Node.js build, as The Hacker News reported.
The new part is the Python side. The GitHub advisory lists two PyPI packages:
| Package | Affected | Fixed |
|---|---|---|
brotli |
>= 1.0.3, < 1.2.1 | 1.2.1 |
brotlicffi |
>= 1.0.3, < 1.2.1.0 | 1.2.1.0 |
The advisory text also names 1.0.10 and 1.1.1 as fixed releases on the older branches, if you can't move to 1.2.x yet. It rates the bug Critical, CVSS v4 9.2. Brotli 1.2.1 and brotlicffi 1.2.1.0 both list October 22 on PyPI. The brotlicffi changelog says the release "remediates CVE-2026-111497, an out-of-bounds write."
Be precise about what is proven. Code execution has been shown on Node.js. Aikido's write-up says code execution was not pursued in the Python extension, and points out that the decoder code is the same one exploited against Node. Treat it as the same bug in the same C code, reachable from Python, with no public Python exploit yet.

The C decoder is shared, so the bug travels with it into every runtime that bundles Brotli.
Why you can be exposed without importing it
The risk sits in a dependency you may not know you have. urllib3's source tries import brotlicffi, then import brotli, and if either works it appends ,br to its ACCEPT_ENCODING. requests builds its DEFAULT_ACCEPT_ENCODING from that same urllib3 helper. urllib3's docs say Brotli is "supported by urllib3 if the Brotli package or brotlicffi package is installed". aiohttp's docs say the same: "just install Brotli or brotlicffi."
Aikido lists urllib3 and requests, httpx with its brotli extra, aiohttp, Scrapy, and anything built on those clients. It also notes that your code doesn't have to import Brotli itself to be affected.
You can see what your client asks for in two lines:
import requests
print(requests.utils.default_headers()["Accept-Encoding"])
If br is in that string, every server your code calls is invited to send Brotli. A server you control is low risk. A URL a user pasted, a webhook target, or a page your crawler found is not.
The outbound path AI apps lean on
A RAG ingestion job fetches documents from URLs it was given. An agent's web-fetch tool downloads whatever page the model decided to read. A scraper follows links. In all three, the other end of the connection is chosen by someone else, and the response body goes straight through your HTTP client's decoder.
That is the outbound exposure Strobes lists for Python: urllib3/requests, aiohttp and httpx, whenever a Brotli package is installed. Inbound matters too if a Python service decodes Brotli request bodies, but outbound fetching of untrusted URLs is the path many AI pipelines run all day.

Agents, crawlers and RAG loaders fetch pages someone else picked. That is the outbound path to patch first.
Check and fix
Start with what is installed, in each environment and each image. This uses only the standard library:
from importlib.metadata import version, PackageNotFoundError
for pkg in ("Brotli", "brotlicffi"):
try:
print(pkg, version(pkg))
except PackageNotFoundError:
print(pkg, "not installed")
Aikido's checks cover the rest: who pulls it in, what your lockfiles pin, and what is inside a built image:
pip show Brotli brotlicffi
pipdeptree -r -p Brotli
grep -i brotli poetry.lock uv.lock requirements*.txt
docker run --rm <image> pip show Brotli brotlicffi
The fix is the upgrade:
pip install -U "Brotli>=1.2.1"
pip install -U "brotlicffi>=1.2.1.0"
brotlicffi 1.2.1.0 also starts rejecting trailing bytes after a complete Brotli stream, per its changelog, so run your fetch tests after the bump.
Then update the lockfile, rebuild containers and wheels, and redeploy. A running process keeps the old decoder loaded until it restarts, which Strobes calls out.
If you can't upgrade today, Aikido's workarounds are to uninstall Brotli and brotlicffi (and drop extras like httpx[brotli]), or reject br in Content-Encoding before any decoder runs. It warns that removing br from Accept-Encoding is not enough on its own, and that uninstalling can cause errors with servers that send Brotli regardless. The reason is in urllib3's response docs: with decode_content on, it decodes "based on the 'content-encoding' header", which is the header the server picks, not the one you asked for.
Beyond Python and Node
The same C code is bundled in a lot of places. As of October 24, Strobes, citing a public tracker, lists Bun 1.4.4, Chromium and Firefox as fixed. It lists bundled copies in .NET, PHP, Ruby, DuckDB and ClickHouse as still waiting on fixes, and the Rust, Java, JavaScript and C# ports as not affected. Aikido adds Envoy 1.40.0, 1.39.3, 1.38.6 and 1.37.8, and Istio 1.29.9, 1.30.6 and 1.31.2, as fixed releases.
The fix commit landed upstream in February 2026 without a security label, per Strobes. Aikido says OpenAI found the flaw in February. The patched code sat in Brotli's source for months before anyone shipped it as a security release. A dependency scanner can only flag what an advisory names, so through that whole stretch no scanner knew the fix mattered.
If this was useful
If you run a RAG pipeline, the loader that fetches documents is attack surface. The RAG Pocket Guide covers ingestion, chunking and reranking for production systems, the parts that sit right after that fetch. It's one of the 8 short books in the Pocket Guides for Developers series, which also covers agents, prompts, observability, events, databases and system design.
I also build Hermes IDE, so weigh this with that in mind: it gives each agent task its own branch and worktree, which is a tidy place to let Claude Code or Codex bump Brotli and run your tests before you land it. It's source-available, runs on macOS, Windows and Linux, and is on GitHub.

Top comments (0)