Beyond HTTP: JFrog’s Brotli2Shell Work Shows the Decoder Bug Riding Files, Not Just Requests
We have covered this Brotli decoder flaw three times in five days — the Node.js disclosure and its February fix that never shipped, the rescoping to a library bug reaching Codex, Envoy and Istio, and Imperva’s Node2Shell remote-code-execution proof — plus Microsoft’s 25 October .NET out-of-band release. JFrog Security Research (Aviv Engelberg, Eddy Tsalolikhin and Ofri Ouzan, 25 October 2026) now forces a fourth post, because it breaks the frame the earlier names set. Node2Shell and HTTP2Shell both imply a network path. JFrog’s “Brotli2Shell: Beyond Node(2Shell) and HTTP(2Shell)” demonstrates the same CVE-2026-111497 out-of-bounds write reached through a crafted image file, a media pipeline, a managed runtime, and a data-frame library — no HTTP required.
The root cause, as JFrog states it: an error-handling flaw in the decoder’s block-switch processing treats an invalid block switch as “needs more input,” continues with invalid state, and writes past the intended buffer. Affected upstream: Brotli 1.0.3 through before 1.0.10, 1.1.0 through before 1.1.1, 1.2.0 through before 1.2.1, corrected in upstream commit 0ed4268 (1.2.1). And the sentence that should change how every scanner result from this month is read: downstream projects may backport the fix, so the upstream version number alone is not always sufficient to determine patch status. Version inventory was step one. It was never the whole assessment.
Four new reaches, four different failure modes
FFmpeg: instruction-pointer control from a picture. FFmpeg reaches the vulnerable decoder transitively through libjxl, which uses Brotli to decompress metadata in brob boxes inside JPEG XL containers. JFrog built a Python script that hand-assembles a valid JPEG XL file — real signature, real ftyp, a brob box carrying an Exif tag plus crafted Brotli bytes, a valid jxlc image codestream — with the malformed stream declaring one block type and then forcing an invalid block switch. Decoding passes the brob contents to BrotliDecoderDecompressStream(); the failure surfaces at instance destruction, freeing a pointer derived from the 0x41 bytes in the payload, and the process dies at 0x414141414141. That is control over the corrupted pointer value through a crafted image file. JFrog is explicit that weaponizing it into RCE needs further exploitation work and depends on target build and mitigations — but the network perimeter is now irrelevant to this path. An image attachment is a delivery vehicle.
ClickHouse: one request, remote crash, fix merged but unshipped. A single HTTP request with a crafted 23 MB body and Content-Encoding: br crashed the tested ClickHouse server: the malformed stream corrupts decoder state, and the crash lands during cleanup when the buffer is destroyed and the decoder freed — SIGSEGV, taking in-flight queries with it. A fix was merged in ClickHouse PR #127419, but no patched release had shipped at the time of writing. A merged fix your packages cannot install is not a fix; it is a countdown other people can read too.
.NET: a native kill no managed code can catch. System.IO.Compression.Brotli compiles the native decoder into the runtime, so BrotliStream feeds attacker-controlled input straight to the vulnerable logic — a two-stage stream that suspends decoding mid-state, then triggers the invalid block switch inside DecodeBlockTypeAndLength(), walking heap until a native SIGSEGV ends the process. Unlike a managed exception, the runtime cannot catch this; the process dies. Tracked as dotnet/runtime #136422, with Microsoft’s 25 October out-of-band runtimes (8.0.33, 9.0.22, 10.0.14, 11.0 RC3) as the remediation we already covered.
Apache Arrow: a 534-byte infinite loop. The pyarrow path to Brotli-compressed Parquet pages — or the direct buffer-decompression API, no network at all — turns a 534-byte input built on the published Node.js regression vector into a denial of service by infinite loop: the decoder’s retry logic treats the malformed input as “send more” and spins forever instead of rejecting it. Brotli 1.2.1 rejects the same input in under a second. Tracked as apache/arrow#55166, open at the time of writing.
Present is not reachable, and reachable is not exploitable
The report’s most reusable contribution is its two-question discipline: is the vulnerable implementation present? (bundled source, system library, transitive dependency) and separately, can untrusted input reach the decoder? HTTP clients and servers are one answer to the second question; file parsers, media tools, and native data APIs are others. JFrog also grades the existing tooling honestly: node2shell-detect inventories runtimes but skips embedded Node and bundled copies; the Trail of Bits Brotli Checker fingerprints decoder code and, paired with a coding agent, investigates paths — but depth depends on available evidence and does not guarantee every path is found. Their own open-source scanner covers JavaScript Brotli APIs and streaming, fetch(), TLS certificate compression and Express/body-parser paths; Python’s brotli/brotlicffi plus the HTTP clients that silently decompress Brotli when those packages are installed (requests, urllib3, httpx, aiohttp); ELF decoder symbols; and CLI usage. The Python auto-decompression point deserves emphasis for agent infrastructure: an application can be exposed without ever importing Brotli itself, because its HTTP client does the decoding on its behalf.
The operational picture as of this weekend, corroborated by contemporaneous reporting tracking the disclosure: Debian fixed trixie and bookworm, Amazon Linux 2023 and openSUSE shipped fixes, Ubuntu still listed its packages as needing evaluation, the Ruby brotli gem fixed 0.9.1 on 24 October bundling 1.2.1, PYSEC entries for the two Python packages landed 23 October, and Cloudflare’s two managed-ruleset additions on 25 October only log by default. CISA’s 23 October assessment recorded exploitation as none and automatable as no — a snapshot, not a forecast, and the JFrog work is exactly the kind of publication that moves the second field.
Our verification was read-only. The root-cause description, affected ranges, fix commit, FFmpeg payload mechanics and crash address, ClickHouse request size and PR status, .NET failure mode and issue tracker, Arrow input size and issue status, scanner coverage lists, and tool comparisons above were read first-hand from JFrog Security Research’s 25 October 2026 report, with same-week secondary coverage used as corroboration for distribution status. We did not build or run any proof of concept, craft any JPEG XL file, send any request to any ClickHouse instance, or test any runtime. No payload is reproduced beyond the report’s published excerpt mechanics as described in prose. Brave Search was used for discovery only.
Sources:
- JFrog Security Research (Engelberg, Tsalolikhin, Ouzan) — “Brotli2Shell: Beyond Node(2Shell) and HTTP(2Shell)” (25 October 2026)
- The Hacker News — “Brotli Decoder Flaw Behind Node.js RCE Also Affects Bun, Python and Other Widely Used Software” (24 October 2026, updated)
- al-ice.ai — Node2Shell: Imperva’s Brotli RCE proof
- al-ice.ai — Brotli rescoped: a library bug reaching Codex, Envoy and Istio
- al-ice.ai — .NET’s 25 October out-of-band release
- al-ice.ai — The February fix that never shipped