# Brotli2Shell: Beyond Node(2Shell) and HTTP(2Shell)

> Source: <https://research.jfrog.com/post/brotli2shell-beyond-node2shell-and-http2shell/>
> Published: 2026-10-25 00:00:00+00:00

CVE-2026-111497 demonstrates how a single memory-corruption bug in a widely reused native decoder can affect applications far beyond the runtime where it was first discovered.

CVE-2026-111497 was publicly disclosed in Node.js’s October 21 security release, which credited OpenAI Codex Security researchers Khai Tran and Sylvie Mayer with reporting the vulnerability. On October 22, Imperva demonstrated remote code execution against an official vulnerable Node.js build and named its research Node2Shell. On October 23, as evidence emerged that the same flaw affected other ecosystems, researchers introduced the name HTTP2Shell to reflect its broader impact on HTTP clients and servers.

But neither name fully captures the scope of the vulnerability.

The underlying flaw resides in Google's Brotli decoder, a native component reused by applications across multiple languages and software categories. HTTP clients and servers are only part of the picture. Software that processes compressed data through file parsers, media tools, document processors, or other native integrations can also expose the vulnerable code, even when HTTP is not involved at all.

Our investigation demonstrates that this is more than a dependency inventory problem. We reproduced instruction-pointer control in additional native applications and developed an [open-source scanner](https://github.com/jfrog/vulnerability_scanners/tree/main/brotli2shell_cve_2026_111497) that identifies Brotli decoding paths across source code, scripts, and ELF binaries, providing code-level evidence that version-only scanners cannot reveal.

## [**The vulnerability behind Node2Shell**](#the-vulnerability-behind-node2shell)

**The vulnerability behind Node2Shell**

CVE-2026-111497 is an out-of-bounds write in Google's Brotli decoder. The root cause is an error-handling flaw in the decoder's block-switch processing: an invalid block switch can be handled as though the decoder simply needs more input, allowing processing to continue with an invalid state and resulting in a write beyond the intended buffer.

The affected upstream ranges are Brotli 1.0.3 before 1.0.10, 1.1.0 before 1.1.1, and 1.2.0 before 1.2.1. Downstream projects may backport the fix, so the upstream version number alone is not always sufficient to determine patch status.

The upstream correction is available in Google Brotli's commit [0ed4268](https://github.com/google/brotli/commit/0ed4268a4401abcb4472096340477cf4af7d1a2b). Node.js subsequently incorporated the fix into its October 2026 security releases, including versions 22.23.4, 24.22.1, and 26.12.1.

The critical distinction is that the vulnerability belongs to the decoder, not to Node.js or HTTP itself. Any application that ships or links an affected implementation may inherit the underlying memory-corruption flaw. Whether that translates into practical exposure depends on the implementation, the input path, and the surrounding runtime and security controls.

## [**One decoder, a much larger attack surface**](#one-decoder-a-much-larger-attack-surface)

**One decoder, a much larger attack surface**

The ecosystem-wide impact became clearer as researchers examined projects beyond Node.js. The [Broccoli2Shell tracker](https://broccoli2shell.com/) provides a useful inventory of affected implementations, downstream projects, and patch status.

However, identifying a vulnerable copy is not the same as proving that an attacker can reach it. Two questions must be answered separately:

**Is the vulnerable implementation present?** An application may bundle Brotli source code, link against a system library, or inherit it through another dependency.

**Can untrusted input reach the decoder?** The application must process attacker-controlled data through the affected decoding path for the memory-corruption condition to become relevant.

These questions lead to a broader attack surface than the original Node.js disclosure might suggest. Brotli is used to decode data in multiple contexts, and the same flaw can be reached through entirely different input types and application features.

### [**HTTP clients and servers**](#http-clients-and-servers)

**HTTP clients and servers**

HTTP is one important attack surface: researchers found that the vulnerable Brotli decoder can be reached through different HTTP workflows. [Imperva](https://www.imperva.com/blog/node2shell-from-brotli-data-to-remote-code-execution/) demonstrated remote code execution against a vulnerable Node.js build through two HTTP-related paths: decompression of Brotli-compressed responses consumed by `fetch()`, and decompression of incoming request bodies handled by server-side middleware. 

Our researchers reproduced a remotely triggerable denial-of-service condition in ClickHouse through the decompression of incoming HTTP request bodies with `Content-Encoding: br`. A single HTTP request containing a crafted 23 MB body crashed the tested ClickHouse server. The malformed Brotli stream corrupts decoder state, and the crash occurs during cleanup when `BrotliReadBuffer` is destroyed and the decoder state is freed. The process terminates with a segmentation fault, interrupting the service and any in-flight queries handled by that server.

While a fix has been merged in [ClickHouse PR #127419](https://github.com/ClickHouse/ClickHouse/pull/127419), a patched release had not yet shipped at the time of writing. This means that, **despite the fix being available in the project's source code, users may still be exposed through released builds.**

### [**Native applications and file processing**](#native-applications-and-file-processing)

**Native applications and file processing**

HTTP is only one way to reach the vulnerable decoder. Our investigation also identified applications where attacker-controlled files or buffers reach Brotli through native libraries and data-processing APIs. These paths demonstrate why exposure cannot be assessed by looking only at HTTP clients, servers, or Node.js versions.

#### [**FFmpeg: memory corruption through JPEG XL decoding**](#ffmpeg-memory-corruption-through-jpeg-xl-decoding)

**FFmpeg: memory corruption through JPEG XL decoding**

FFmpeg is an open-source media framework written in C that supports a wide range of audio and video formats. During our investigation, we found that **FFmpeg reaches the vulnerable Brotli decoder transitively** through `libjxl`, which it uses to decode JPEG XL images. Specifically, `libjxl` uses Brotli to decompress metadata stored in `brob` boxes within the JPEG XL container format. 

We developed a proof of concept that triggers the vulnerability and achieves instruction-pointer control, demonstrating that this issue extends beyond HTTP-based attack paths to native media processing.

The exploitation path is therefore entirely separate from HTTP:

`Crafted JPEG XL file → FFmpeg → libjxl → brob box → BrotliDecoderDecompressStream()`

To demonstrate the impact, we developed a Python script that constructs a JPEG XL file manually. The file contains the expected JPEG XL signature (`JXL` ), an `ftyp` box declaring the file type, a `brob` box containing an `Exif` tag and crafted Brotli bytes, and a `jxlc` box containing a valid image codestream copied from a source image.

The outer structure is valid, and the file contains a real image. The malformed data is embedded in the metadata box, where the Brotli stream is designed to trigger the block-type confusion bug by declaring one block type and then forcing an invalid block switch. The crafted byte sequence is:

`200010414141fcffff07002228f00b60f7f41f000000`

When FFmpeg decodes the image, `libjxl` passes the `brob` contents to `BrotliDecoderDecompressStream()`, triggering the vulnerability and corrupting decoder state. The failure becomes apparent later, when the decoder instance is destroyed and attempts to free a pointer derived from the `0x41` bytes in the payload. In our testing, this results in a segmentation fault at `0x414141414141`.

This demonstrates memory corruption and control over the corrupted pointer value through a crafted image file. The resulting crash confirms that an attacker can reach the vulnerable decoder through FFmpeg's file-processing path, without relying on HTTP. Achieving remote code execution would require further exploitation of the memory corruption and would depend on the target build and its security mitigations.

#### [**.NET: a native crash through Brotli decompression**](#net-a-native-crash-through-brotli-decompression)

**.NET: a native crash through Brotli decompression**

The .NET runtime provides another integration path. Its `System.IO.Compression.Brotli` APIs use a native Brotli decoder compiled into the runtime. APIs such as `BrotliStream` can therefore pass attacker-controlled input to the vulnerable native code. The same decoder can also be reached through HTTP-related features, including `HttpClient` automatic response decompression and ASP.NET Core request-decompression middleware.

In our demonstration, a local console application supplied a two-stage Brotli stream. The first stage suspended decoding mid-state; the second triggered an invalid block switch, exercising the vulnerable logic in `DecodeBlockTypeAndLength()`. The decoder continued using inconsistent state until a memory operation walked beyond valid heap data, causing a native segmentation fault (`SIGSEGV`).

Unlike an ordinary managed exception, this crash terminates the affected process. Although our demonstration used a local console application, the same decoding functionality is used by network-facing .NET applications when they decompress attacker-controlled Brotli data.

The issue is tracked in [dotnet/runtime #136422](https://github.com/dotnet/runtime/issues/136422). On October 25, 2026, Microsoft announced that updates are rolling out for supported .NET runtimes and SDKs through the normal distribution channels. The issue remains open to track porting the fixes to the public GitHub branches.

#### [**Apache Arrow: an infinite loop through Parquet and buffer decompression**](#apache-arrow-an-infinite-loop-through-parquet-and-buffer-decompression)

Apache Arrow demonstrates a different failure mode: denial of service through an infinite loop rather than a process crash. Its `pyarrow` package bundles Brotli decoding support used to process Brotli-compressed Parquet data pages. The decoder can also be reached directly through the buffer-decompression API, without any network connection.

We found that a 534-byte input could trigger the issue. The stream builds on the published Node.js regression vector for this bug: a small valid uncompressed meta-block, followed by a compressed meta-block that declares the maximum possible size of 16 MB and contains a malformed block-switch sequence. We padded it with trailing data to sustain the decode loop. Instead of rejecting the malformed input, the vulnerable decoder's retry logic treats the condition as if more input might resolve it and continues looping. The affected process consumes CPU indefinitely, preventing the data-processing task from completing.

The same input is rejected in under a second by Brotli 1.2.1. The issue is tracked in [Apache Arrow #55166](https://github.com/apache/arrow/issues/55166), which remained open at the time of writing.

### [**Why patch status varies**](#why-patch-status-varies)

**Why patch status varies**

The root cause was fixed upstream in Google's Brotli repository in commit [`0ed4268`](https://github.com/google/brotli/commit/0ed4268), included in Brotli 1.2.1. Although the underlying issue affects the Brotli decoder, patch availability varies across projects that use it. Some projects bundle their own copy of Brotli, while others rely on a system library, a language runtime, or a transitive dependency. As a result, fixing the upstream decoder does not automatically make every affected application safe: downstream projects must incorporate the fix, release an updated version, and ensure that their distributions or deployments use it.

The practical implication is that identifying an affected version is only the first step. It is also necessary to understand whether an application actually reaches Brotli decoding, which inputs can reach that path, and whether the relevant release includes the upstream fix.

## [**Detecting Brotli2Shell: What Existing Tools Miss**](#detecting-brotli2shell-what-existing-tools-miss)

**Detecting Brotli2Shell: What Existing Tools Miss**

Several tools have emerged to help identify systems affected by CVE-2026-111497, but they differ in what they examine and what their results establish.

[node2shell-detect](https://github.com/shivrajem/node2shell-detect) primarily inventories installed and running Node.js and Bun runtimes across supported hosts and containers, then compares their versions against known fixed releases. It helps locate outdated runtimes but does not inspect application code for Brotli usage. Its scope also excludes Node.js embedded in other applications and software that bundles its own Brotli copy.

The [Trail of Bits Brotli Checker](https://github.com/trailofbits/brotli-checker) fingerprints potentially vulnerable Brotli source code and binaries and checks for selected decoder calls and application paths. It also provides source-call discovery to investigate connections between decoder and networking APIs. When used through Claude Code or Codex, its AI-assisted workflow can investigate additional paths, but the depth of that investigation depends on the code and evidence available; it does not guarantee detection of every decompression path.

These tools provide useful starting points, but identifying an outdated runtime, a vulnerable decoder copy, or a potential call path does not necessarily establish whether an application processes attacker-controlled data through the vulnerable implementation. This can be particularly difficult when decompression happens indirectly, such as through an HTTP client, request-processing middleware, or a native library.

## [**How Does JFrog Protect You?**](#how-does-jfrog-protect-you)

**How Does JFrog Protect You?**

**JFrog Xray identifies vulnerable components and versions, while our contextual scanner checks for additional application-level paths that may reach the vulnerable decoder.** This combines component identification with targeted analysis of how Brotli decompression may be used.

Compared with node2shell-detect, our approach also checks application code for potential Brotli decompression paths. Compared with Brotli Checker, it adds targeted detection for high-level JavaScript and Python APIs, including `fetch()`, Express and `body-parser`, Requests, HTTPX, aiohttp, and urllib3. It also checks for Brotli CLI usage and relevant ELF decoder references.

We have also released an [open-source](https://github.com/jfrog/vulnerability_scanners/tree/main/brotli2shell_cve_2026_111497) scanner that covers the same patterns, allowing researchers and developers to investigate potential exposure to CVE-2026-111497 in their own projects. Its checks cover the following patterns:

- **JavaScript:** Direct Brotli decompression APIs, streaming decompression,`DecompressionStream` , HTTP response decompression through`fetch()` and related networking APIs, TLS certificate compression, and request-body decompression through Express and`body-parser` .
- **Python:** Direct use of`brotli` and`brotlicffi` , as well as HTTP clients such as Requests, HTTPX, aiohttp, and urllib3 that may automatically decompress Brotli responses when the appropriate backend is installed.
- **Native binaries:** ELF decoder references such as`BrotliDecoderDecompress` and`BrotliDecoderDecompressStream` .
- **Command-line usage:** Brotli CLI invocations that use decompression options such as`-d` or`--decompress` .

These checks matter because an application may never call the native decoder directly. An HTTP client may decompress a response automatically, middleware may decode an incoming request body before application code processes it, or a native binary may invoke Brotli on behalf of another component.

When Xray identifies a vulnerable component and contextual analysis finds a relevant usage pattern, this additional evidence helps teams prioritize artifacts for investigation and remediation instead of treating every affected version as equally exposed.

A match does not automatically prove remote exploitability. The actual decoder implementation, runtime or library version, input source, and application configuration must still be considered. The goal is to provide evidence of potential exposure that helps security teams determine where further investigation and remediation are needed.

## [**Conclusion**](#conclusion)

**Conclusion**

Node2Shell and HTTP2Shell brought attention to CVE-2026-111497, but their names don't capture the full scope of the vulnerability. Our investigation shows that the vulnerable Brotli decoder can be reached through much more than HTTP clients and servers. We demonstrated additional paths through FFmpeg's JPEG XL processing and the .NET runtime, illustrating how the same underlying flaw can affect applications across different ecosystems and software categories.

Finding vulnerable package versions is an essential first step, but it does not tell the whole story. Brotli may be introduced through transitive dependencies, bundled into native binaries, or invoked indirectly through high-level APIs. Vulnerable functionality can therefore remain hidden even when an application does not directly depend on Brotli or explicitly call its decoding functions. A more complete assessment requires identifying affected components and versions, then examining source code and binaries to find relevant decoder functions and determine how they may be reached by untrusted input.

CVE-2026-111497 illustrates why vulnerability detection must go beyond package inventories. Understanding the actual exposure means tracing vulnerable code across dependency chains, language runtimes, native libraries, and application entry points. Without this broader view, security teams risk overlooking vulnerable code simply because it resides several layers below the application or is reached indirectly through another component.
