Skip to content

Back to the catalog

Python packages are also vulnerable to the Node2Shell Brotli flaw (CVE-2026-111497)
aikido.devadvisory

Python packages are also vulnerable to the Node2Shell Brotli flaw (CVE-2026-111497)

Python2Shell is the Python-side impact of CVE-2026-111497 (Node2Shell), a Brotli decoder flaw (CVSS 4.0 9.2) in DecodeBlockTypeAndLength() that mixes an unchecked return value with an out-of-bounds write. Brotli 1.2.0 and earlier, and brotlicffi 1.2.0.x, are affected through urllib3, requests, httpx, aiohttp and Scrapy. Fixes are Brotli 1.2.1 and brotlicffi 1.2.1.0; as of 23 October 2026 advisory databases do not map the CVE to either package. The post lists checks and workarounds.

TL;DR Explains how the Node2Shell Brotli decoder bug reaches Python apps via transitive Brotli and brotlicffi installs, and how to detect, patch or work around it.
Document

What it is

Python2Shell is the name the post gives to CVE-2026-111497 (Node2Shell) as it affects the PyPI packages Brotli and brotlicffi. Both compile the same Brotli decoder that Node.js bundles. The CVE record pairs CWE-252 (unchecked return value) with CWE-787 (out-of-bounds write) and scores it CVSS 4.0 9.2. Brotli has 82.8 million downloads per month.

How it works

DecodeBlockTypeAndLength() handles block switches, where a stream changes Huffman tree. In vulnerable versions it returns a plain boolean, so BROTLI_FALSE means either "malformed stream" or "need more input", and callers cannot tell which. On the fast path the decoder discards the return value entirely.

A crafted stream declares a single block type and then exhausts that block's length, forcing a switch that cannot succeed. The decoder then either asks for more input on a complete stream, or keeps decoding with its old block state, which leads to the out-of-bounds write.

The upstream fix, google/brotli commit 0ed4268, returns an error code and adds BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17). OSM found 1.2.1 differs from 1.2.0 only in the version bump and this commit's decoder changes.

Preconditions / Affected

Affected: Brotli 1.2.0 and earlier, brotlicffi 1.2.0.x. Your code need not import either package. urllib3 (so requests), httpx with the brotli extra, aiohttp and Scrapy decode Brotli automatically when the package is installed. Scrapy installs Brotli as a dependency.

Attacker data reaches the decoder two ways:

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

What to do

Upgrade to Brotli 1.2.1 or brotlicffi 1.2.1.0:

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

With Poetry, uv or pip-tools, update the constraint and regenerate the lockfile. Rebuild container images and wheels that bundle either package. Check laptops, CI runners, servers and containers. If the bindings come from a Linux distribution package, wait for the distribution's fix.

To check exposure:

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

Patch first the services that call servers you do not control or accept Content-Encoding: br bodies.

If you cannot upgrade yet, uninstall Brotli and brotlicffi. The HTTP clients fall back to gzip and deflate. Also remove extras such as httpx[brotli], or the next install restores the package. You can also strip br from Accept-Encoding on outbound requests and reject Content-Encoding: br inbound at the proxy, load balancer or middleware.

Caveats

  • pip-audit, Dependabot and Safety will not flag this as of 23 October 2026. The CVE record, OSV and the GitHub Advisory Database do not list either PyPI package. The release notes for both fixed versions omit the CVE. OSM is submitting PYSEC entries and a package mapping.
  • Header filtering does not protect outbound traffic: a malicious server can send Brotli regardless, and urllib3 decodes based on the response's Content-Encoding header. Only uninstalling covers both directions.
  • OSM did not pursue memory corruption or code execution in the Python extension. Under AddressSanitizer, 1.2.0 consumed a roughly 23 MB crafted stream and kept asking for more input, even in one-byte chunks, while 1.2.1 rejected it with error -17. The decoder code is the same that researchers exploited against Node.js 24.22.0, per Imperva and others.
  • The Hacker News reported no exploitation in the wild for Node.js as of 22 October 2026.
  • brotli.decompress() raises only a generic brotli.error, so the error code is not visible. A Decompressor() fed the stream incrementally never finishes on 1.2.0.

Timeline: fix first committed February 2026. Upstream fixed releases and Node.js patches came 21 October 2026. Brotli 1.2.1 reached PyPI 22 October, and brotlicffi 1.2.1.0 on 23 October.

Organisations named here

  • CWE-252
  • CWE-787
  • Imperva
  • Python2Shell

CVEs named here

  • CVE-2026-111497

Technologies named here

  • Aikido
  • brotlicffi 1.2.0.x, fixed in 1.2.1.0
  • pip-audit
  • Node.js
  • Dependabot
  • httpx with the brotli extra
  • pipdeptree
  • AddressSanitizer
  • OSV and GitHub Advisory Database
  • Scrapy
  • Express 5
  • OpenSourceMalware.com
  • Linux distribution packages of the Python Brotli bindings
  • Poetry, uv, pip-tools
  • Brotli
  • PyPI
  • Python
  • urllib3
  • httpx
  • OpenSourceMalware
  • brotlicffi

Techniques named here

  • Request body compressed with Content-Encoding
  • OSM test

Threat actors named here

  • Node2Shell