RC RANDOM CHAOS

By October 23, one Brotli flaw reached every language

CVE-2026-111497 is an out-of-bounds write in Google's Brotli decoder that lets unauthenticated attackers run code via crafted compressed streams.

· 4 min read
By October 23, one Brotli flaw reached every language

The flaw behind “node2shell,” now being called “HTTP2Shell,” is a single out-of-bounds write in Google’s Brotli decoder. It is tracked as CVE-2026-111497, scored 9.2 on CVSS v4.0, and it affects Brotli 1.0.3 through 1.2.0 on every platform. A remote, unauthenticated attacker who can hand the decoder a crafted Brotli stream can corrupt memory, leak sensitive data, crash the process, or run code. Brotli is Google’s compression library, used almost everywhere for HTTP content encoding, and it ships as a C library that Node.js, Python, Bun, Envoy, curl and many others each bundle. That is why the same bug surfaced in one ecosystem after another: Node.js disclosed it first, Python shipped a patch for the identical flaw the next day, and by Friday October 23 it was clear the problem reached any language or service that decodes Brotli.

How the bug works

The flaw sits in DecodeBlockTypeAndLength(), which handles block switches, the points where a Brotli stream changes which Huffman tree it uses. In vulnerable versions the function returns a plain boolean, and BROTLI_FALSE means either “the stream is malformed” or “I need more input.” Callers cannot tell the two apart, and 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 reports that it needs more input for a stream that is already complete, or keeps decoding with the block state it already had. That second path is the out-of-bounds write. The CVE record pairs CWE-252, unchecked return value, with CWE-787, out-of-bounds write, which is exactly this shape.

The upstream fix, google/brotli commit 0ed4268, changes the function to return an error code and adds a new error, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17), so decoding stops on a format error on both the safe and fast paths. OSM diffed the 1.2.0 and 1.2.1 source distributions and found 1.2.1 differs only in the version bump and that one commit. Their AddressSanitizer test is a clean demonstration: fed a crafted ~23 MB stream, 1.2.0 consumed all of it and kept asking for more input, whether it arrived in one piece or in one-byte chunks, while 1.2.1 rejected it with error -17 every time.

The timing is the awkward part. OpenAI reported this in February as unauthenticated remote code execution that works in both directions: a malicious client can take over an unpatched server, and a malicious server can take over an unpatched client that accepts compressed responses. The root cause was fixed in the Brotli source then, but it was not recognized as a security issue, so no release shipped the fix until now. Every project that bundles Brotli has to ship its own update rather than inherit one.

You can be exposed without importing Brotli

For the Python side this matters more than usual. Two PyPI packages, Brotli (82.8 million downloads a month) and brotlicffi, compile the same vulnerable decoder. If either is installed, urllib3 and therefore requests, httpx with the brotli extra, aiohttp, and anything built on them, including Scrapy, advertise Accept-Encoding: br and decode Brotli responses automatically. Your own code never has to import either package. Attacker data reaches the decoder through outbound requests, where your app fetches a Brotli response from a server an attacker controls via a webhook, URL preview or crawler, and through inbound requests, where middleware decodes a body sent with Content-Encoding: br.

As of October 23 the advisory databases do not map CVE-2026-111497 to either PyPI package, so pip-audit, Dependabot and Safety will not flag vulnerable versions. OSM is submitting PYSEC entries and a PyPI mapping to fix that, but until it lands you have to check by hand: pip show Brotli brotlicffi in every environment and image, pipdeptree -r -p Brotli to see what pulls it in, and a grep through your lockfiles.

What to upgrade

Update the runtime or the bundled Brotli library:

  • Native Brotli / libbrotli: 1.2.1, or patched 1.0.10 or 1.1.1 on older lines.
  • Python: Brotli 1.2.1, brotlicffi 1.2.1.0. If the bindings came from a distro package, the fix comes through the distro.
  • Node.js: 22.23.4, 24.22.1, or 26.12.1.
  • Bun: 1.4.4, and rebuild compiled executables that bundle it.
  • Envoy: 1.40.0, 1.39.3, 1.38.6, or 1.37.8.
  • Istio: 1.29.9, 1.30.6, or 1.31.2.
  • curl / libcurl: update its Brotli dependency; statically linked copies need an application rebuild.

Rebuilding is the step people miss. An image or wheel built on a vulnerable version stays vulnerable until you rebuild it. Upgrade the services that call outside servers or accept compressed request bodies first.

If you cannot patch yet, uninstalling both Python packages removes the decode path and the clients fall back to gzip and deflate. At the edge, reject requests whose Content-Encoding includes br, matched case-insensitively and inside encoding lists. Dropping br from Accept-Encoding on outbound calls is not enough on its own: a malicious server can send a Brotli response regardless of what you advertise, and urllib3 picks its decoder from the response’s Content-Encoding header, so you have to disable Brotli response decoding or reject such responses before the body is processed.

Patches now exist for libbrotli, Python, C++, Node.js, Bun and Envoy, with more coming. Apache and Nginx do not decode Brotli by default. Researchers exploited the identical decoder against an unmodified Node.js 24.22.0 build, per Imperva and others, though The Hacker News reported no exploitation in the wild for Node.js as of October 22.

Share

Keep Reading

Latest on the Wire

Full wire →

New signal daily · RSS

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.