NEWS

Critical Brotli vulnerability becomes HTTP2Shell, hits Node.js, Python, Bun, Envoy and Istio

The flaw dubbed Node2Shell, which seemed to affect only Node.js, turned out to be a much larger supply-chain problem: any runtime or library that bundles Google's Brotli decoder can be vulnerable.

O que aconteceu

On Wednesday, October 21, 2026, the Node.js team disclosed CVE-2026-111497, nicknamed "Node2Shell": a critical flaw in the Brotli decoder bundled with the runtime that can cause memory leaks or remote code execution when the process decompresses attacker-controlled Brotli data, according to a report by Aikido. The following day, the Python team published an update acknowledging that the same vulnerable packages, Brotli and brotlicffi on PyPI, carry the affected decoder.

On Friday, October 23, it became clear that the blast radius is bigger than "just another Node.js bug": any language, library or service that bundles Google's Brotli decoder is potentially exposed. That's why the nickname changed from Node2Shell to HTTP2Shell, a name coined by Aikido to highlight that the problem runs across the entire HTTP ecosystem, not a specific runtime.

The origin traces back to February 2026. According to OpenAI, the company found the bug at that time and reported it as an unauthenticated remote code execution issue that works both ways: a malicious client can compromise an outdated server, and a malicious server can compromise an outdated client that accepts compressed responses. Brotli's source code received the fix back then, but nobody identified it as a security flaw, and no released version incorporated the fix until now. The practical result: every project that bundles its own copy of Brotli needs its own update, even though the upstream has been fixed for months.

Como a falha funciona

CVE-2026-111497 is an out-of-bounds write that affects Brotli versions 1.0.3 through 1.2.0, on any platform. The CVSS v4.0 score is 9.2 (critical), and the CVE combines CWE-252 (unchecked return value) with CWE-787 (out-of-bounds write).

The bug lives in the DecodeBlockTypeAndLength() function, responsible for handling block switches, the points where a Brotli stream changes which Huffman tree it uses. In the vulnerable versions, this function returns a simple boolean, and BROTLI_FALSE can mean either "this stream is malformed" or "I need more input data". The caller can't distinguish between the two cases, and on the fast path the decoder discards that return value entirely.

A malicious stream can declare a single block type and exhaust that block's size, forcing a switch that can't be completed. The decoder then either reports that it needs more data for an already-complete stream, or keeps decoding with the previous block state, a path that leads to the out-of-bounds write.

The upstream fix, in commit 0ed4268 of the google/brotli repository, makes the function return an error code and introduces BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17). Starting with version 1.2.1, decoding stops with a format error on both the safe and the fast path.

Quem está exposto além do Node.js

The point that matters for anyone maintaining HTTP applications: you don't need to import Brotli directly in your code to be exposed. Popular HTTP clients advertise Accept-Encoding: br and automatically decode Brotli responses whenever the library is installed, even as a transitive dependency.

In Python, this includes urllib3 (and consequently requests), httpx with the brotli extra, aiohttp when the Brotli package is present, and anything built on top of those clients, such as Scrapy. In Node.js, the vector runs through functions like the native fetch() and middleware such as body-parser.

Two paths carry attacker data to the vulnerable decoder:

  • Outbound requests: your application fetches a Brotli response from an attacker-controlled server, for example via webhook delivery, URL preview, or a "fetch this URL" feature.
  • Inbound requests: your framework or middleware decodes a request body sent with Content-Encoding: br, the Python equivalent of the Express 5 case in Node.js.

The list of affected components goes beyond application languages: Bun, the Envoy proxy, the Istio service mesh, and native libraries such as libcurl also bundle the vulnerable Brotli decoder. On the unaffected side, Apache's standard mod_brotli doesn't decode Brotli, and Nginx also doesn't decode by default.

Como corrigir, projeto por projeto

The table below gathers the fixed versions confirmed as of the October 23 review:

ProjectFixed versionsNote
Node.js22.23.4, 24.22.1, 26.12.1Use a supported release line
Bun1.4.4Already includes Brotli 1.2.1
Envoy1.40.0, 1.39.3, 1.38.6, 1.37.81.36.12 also has the fix, but the 1.36 series is unsupported
Istio1.29.9, 1.30.6, 1.31.2The 1.29 series is end-of-life
Brotli (upstream)1.0.10, 1.1.1, 1.2.1Choose the fixed release for your series

For those using Python directly, the fix is a package upgrade: pip install -U "Brotli>=1.2.1" or pip install -U "brotlicffi>=1.2.1.0", followed by updating the lockfile in Poetry, uv, or pip-tools. After that, you need to rebuild container images and any wheel that bundles these packages, since an image built on a vulnerable version remains vulnerable until it's rebuilt.

A point of attention for anyone relying on automated scanners: as of October 23, advisory databases hadn't mapped the CVE to the PyPI packages, so pip-audit, Dependabot, and Safety don't flag the vulnerable versions. For now, the check has to be manual: pip show Brotli brotlicffi in each environment, and pipdeptree -r -p Brotli to find what pulls in the package as a transitive dependency.

Se você não pode atualizar agora

While the upgrade isn't out yet, Aikido recommends mitigating the exposure on two fronts. For those who only use Brotli as optional support in Python, uninstalling Brotli and brotlicffi removes the vulnerable decoding paths: httpx, requests, and urllib3 keep working with gzip and deflate.

For inbound traffic, reject requests whose Content-Encoding includes br at the reverse proxy, load balancer, or application middleware, before any vulnerable decoder processes the body. For outbound traffic, remove br from the Accept-Encoding header and disable automatic decoding of Brotli responses, checking the Content-Encoding before processing the body, not after.

One warning is worth making: changing only the Accept-Encoding isn't enough. A malicious server can send a Brotli response regardless of what the client advertised, and urllib3 picks the decoder based on the response's Content-Encoding header, not on what was requested.

O que muda pra quem desenvolve no Brasil

For teams running Node.js, Python, or Bun in production, this week's concrete task is to map where Brotli enters, not just in your service's package.json or requirements.txt, but across the entire dependency tree, including already-published container images. Services that make outbound calls to third-party servers (webhooks, scraping, link previews) or that accept compressed bodies from external clients deserve priority in the update queue.

The case is also a reminder of how vulnerabilities in low-level libraries spread silently: OpenAI reports having fixed the root cause in Brotli itself back in February without realizing it was a security flaw, and months later nobody had shipped the release with the fix. While advisory databases don't map the CVE to the right packages, automated dependency scanners aren't enough: manual checking remains necessary, at least for the coming days.

Translated from the Brazilian Portuguese original · Read the original