p.enthalabs

Broccoli2Shell (CVE-2026-111497): Where Brotli Hides

**One Brotli decoder bug. A lot of copies.**

_TL;DR - CVE-2026-111497 is an out-of-bounds write in the Brotli decompressor in google/brotli, fixed upstream in 1.2.1. It is not a separate Node, PHP, or npm bug. It is one C decoder that has been copied into runtimes, databases, document readers, image libraries, language bindings, and prebuilt native packages. And while originally reported as Node2Shell, this CVE impacts far more than just Node. In this post we go into detail on all the copies that you may have missed if you stopped reading after “Node.”_

What is the bug?![Image 1: Link icon](https://semgrep.dev/blog/2026/broccoli2shell-brotli-cve-2026-111497/#what-is-the-bug? "What is the bug?")

CVE-2026-111497 is a Heap out-of-bounds write in Google's Brotli C decoder. Brotli is a compression algorithm that’s commonly used in HTTP applications and the vulnerable component is called when an attacker supplies a malformed Brotli stream. The decoder can treat an invalid block switch as incomplete input, continue with stale state, and write past the memory it owns.

That can mean a crash, memory disclosure, or memory corruption that may be useful for code execution, depending on the wrapper and the process around it. The bug is in the shared decoder, not in Node.js, Python, PHP, or npm itself. But how the decoder is handled and implemented changes how the vulnerability is actually patched and remediated.

The upstream 0ed4268a fix is in Brotli 1.2.1. The important change is that invalid block-switch state now returns a real decoder error instead of being treated as “need more input.” It’s a fairly simple fix, but prior to AI, it would need a skilled researcher familiar with C code and the Brotli compression decoder, which is probably why no one picked it up in Brotli 1.0.3 when it was introduced.

Why this is bigger than Node![Image 2: Link icon](https://semgrep.dev/blog/2026/broccoli2shell-brotli-cve-2026-111497/#why-this-is-bigger-than-node "Why this is bigger than Node")

Node2Shell covered the Node.js attack paths. But Node is only one example. The underlying issue is the vulnerable decoder in Google’s C Brotli library.

That decoder has been copied into runtimes, browsers, databases, proxies, document tools, image libraries, and language bindings. It may be hidden inside a native library, wheel, DLL, addon, static binary, or transitive dependency. Updating one package does not update every copy in the application.

The patch is small. The difficult part is finding each bundled copy and confirming whether untrusted Brotli data can reach it.

Depending on the project, fixing it may mean:

- Publishing new Python wheels or native packages

- Releasing an updated runtime

- Rebuilding Chromium, DuckDB, Envoy, .NET, or another larger artifact;

- Updating the system `libbrotli`

- Adapting the fix to a language-specific port

Sometimes it is a dependency update. Sometimes it requires a vendor release or a complete rebuild, so the `just patch` narrative can be complicated and not simple.

What to do![Image 3: Link icon](https://semgrep.dev/blog/2026/broccoli2shell-brotli-cve-2026-111497/#what-to-do "What to do")

There will not be one neat package list for CVE-2026-111497. Broccoli2Shell is a useful resource, but each vendor will also identify packages that are not covered by the main public lists.

Using Semgrep? A post on how it fits into your CVE-2026-111497 response is on the way.

The full scope will be difficult to track. Some projects are already patching silently, without publishing an advisory, and new affected packages will continue to appear across programming-language ecosystems and bundled native components.

Below, we list packages we have confirmed so far, along with recommendations for cases where an immediate upgrade is not available.

Patch the package or runtime that contains Brotli![Image 4: Link icon](https://semgrep.dev/blog/2026/broccoli2shell-brotli-cve-2026-111497/#patch-the-package-or-runtime-that-contains-brotli "Patch the package or runtime that contains Brotli")

In this list we’ve compiled packages or runtimes that specifically ship Brotli inside of them. Patches are typically available for that component if you just update to the version listed below, and you can easily do these updates as you would any other patch.

| **Package** | **Vulnerable versions** | **Fixed in** | **Where Brotli lives** | | --- | --- | --- | --- | | Google Brotli | 1.0.3–1.2.0, excluding patched backports | 1.2.1 | upstream C decoder | | `libbrotli` / distro `brotli` packages (source) | same upstream range unless backported | vendor-specific; upstream 1.2.1 | system shared library or CLI | | Python Brotli | 1.0.3–1.2.0 | 1.2.1 | CPython binding built from the C decoder | | Python brotlicffi | 1.0.3–1.2.0.x | 1.2.1.0 | native `libbrotli`build | | Node.js | unpatched 22.x, 24.x, and 26.x releases | 22.23.4, 24.22.1, 26.12.1 | bundled `deps/brotli` | | Bun | 1.0.21–1.4.3 | 1.4.4 | bundled Brotli copy | | Go andybalholm/brotli | before v1.2.7 | v1.2.7 | Go decoder port | | Java brotli4j | before 1.24.0 | 1.24.0 | bundled native library |

| .NET System.IO.Compression.Brotli | 8.0 before 8.0.33; 9.0 before 9.0.22; 10.0 before 10.0.14; .NET 11 before RC3 | 8.0.33, 9.0.22, 10.0.14, .NET 11 RC3 | runtime bundle | | Envoy Proxy | 1.36.11, 1.37.7, 1.38.5, 1.39.2 | 1.36.12, 1.37.8, 1.38.6, 1.39.3, 1.40.0 | bundled through Envoy | | Istio | 1.29.0–1.29.8, 1.30.0–1.30.5, 1.31.0–1.31.1 | 1.29.9, 1.30.6, 1.31.2 | Envoy / bundled Brotli |

Bundled Brotli![Image 5: Link icon](https://semgrep.dev/blog/2026/broccoli2shell-brotli-cve-2026-111497/#bundled-brotli "Bundled Brotli")

Some packages ship Brotli inside their own binary instead of using the system library. Many of these remain unfixed because either the package itself must be updated or rebuilt from source with the Brotli fixes included. Updating a top-level `Brotli` dependency will not patch those copies.

| **Package** | **Vulnerable versions** | **Fixed in** | **Where Brotli lives** | | --- | --- | --- | --- | | DuckDB | unpatched bundled copy | no fix yet | vendored Brotli; Parquet files | | Apache Arrow | bundled Brotli 1.0.9 | no fix yet | bundled Brotli; Parquet files | | v8-jsi | bundled Brotli 1.2.0 | no fix yet | bundled Node copy | | 7-Zip-zstd | bundled Brotli 1.2.0 | no fix yet | bundled decoder; archives and `.br` files | | p7zip | bundled Brotli 1.0.9 | no fix yet | bundled decoder; archives and `.br` files | | EDK II | bundled Brotli 1.2.0 | no fix yet | bundled decoder; compressed firmware | | woff2 Makefile build | bundled Brotli 1.0.3 | no fix yet | bundled decoder; WOFF2 files | | Ruby brotli gem | before 0.9.1 | 0.9.1 | bundles Brotli 1.2.1 | | PHP brotli extension | before 0.21.1 | 0.21.1 | bundles Brotli 1.2.1 | | UEFITool | before A76 | A76 | bundles Brotli 1.2.1 | | MuPDF | 1.27.x | 1.28.0 | bundled Brotli fork | | ClickHouse | latest release predates the fix | public branch fixed; release pending | bundled Brotli | | NanaZip | latest release predates the fix | `main` fixed; release pending | bundled Brotli | | GraalJS | latest release predates the fix | `master` fixed; release pending | bundled Node/Brotli copy | | YDB | released versions not yet fixed | `main` fixed; release pending | bundled Brotli | | libjxl | `v0.12.0` and earlier | `main` fixed; release pending | bundled Brotli | | Brotli4j | `<= 1.23.0` | 1.24.0 | Bundled native Brotli C decoder in the `native-*`Maven JARs, loaded through JNI. | | Ruby brotli | `< 0.9.1` | 0.9.1 | Vendored Brotli source under `vendor/brotli`, with an optional system-library path. |

| Rust brotli-decompressor | `< 6.2.0` | `6.2.0` (fix) | Rust implementation of the Brotli decompressor, with optional C-compatible FFI. | | Go go-brrr | `<= 1.3.0` | 1.3.1 | Pure-Go Brotli encoder and decoder. |

| cycurl | `<= 0.13.0` | No fixed release (open issue #27) | Precompiled Python wheels bundle`libcurl-impersonate` with Brotli 1.1.0 statically linked. |

| Pillow (PyPI) | `>=9.3.0, <=13.0.0` | No fix yet "main fixed (PR https://github.com/python-pillow/Pillow/pull/10340) ; release pending | wheels ship libbrotlidec (FreeType WOFF2) | | curl_cffi (PyPI) | `< 0.17.0` | 0.17.0 | bundled brotli | | pycurl (PyPI) | `>= 7.45.3, <7.49.0` | 7.49.0 | wheels ship libbrotlidec 1.2.0 | | imagecodecs (PyPI) | `2019.12.31 -2026.10.10(all releases)` | No fix yet | wheels ship libbrotlidec 1.2.0 (Brotli codec, JPEG XL) | | pygame (PyPI) | `2.0.3 – 2.6.1 (Linux / macOS wheels)` | No fix yet | wheels ship libbrotlidec 1.0.9 (FreeType WOFF2) | | pygame-ce (PyPI) | `<= 2.5.8` | No fix yet | wheels ship libbrotlidec 1.2.0 (FreeType WOFF2) | | Magick.NET (NuGet, all Magick.NET-* variants) | `>=7.23.3, <=15.0.0` | No fix yet | native ImageMagick build | | IronCompress (NuGet) | `>=1.2.5, <=1.7.0` | No fix yet | native Brotli; used by Parquet.Net 4.x/5.x | | Brotli.NET (NuGet) | `>=2.0.0, <=2.1.1` | no fix (open issue https://github.com/XieJJ99/brotli.net/issues/28) | prebuilt Brotli 1.0.9 | | methanol-brotli (Maven) | `<= 1.9.0` | no fix yet ( open issue #198) | JNI Brotli | | mupdf-sys (Rust) | `>=0.6.0, <=0.8.0` | No fix yet | vendored MuPDF Brotli | | woff (Rust) | `>=0.2.2, <=0.6.3` | No fix yet | vendored Brotli | | es_compression (Dart) | `<= 2.0.15` | No fix yet | prebuilt Brotli 1.0.9 | | brotli (Elixir / Hex) | `>=0.2.1, <0.3.4` | 0.3.4 | vendored Brotli | | multi_compress (Ruby) | `<= 0.6.0` | No fix yet | vendored Brotli | | node-libcurl (npm) | `>=2.0.0, <=5.1.2` | No fix yet | prebuilt binaries statically link libcurl + Brotli | | ladybug (PyPI) | `>=0.15.3, <=0.22.0` | no fix yet | vendored Brotli 1.1.0 | | real_ladybug (PyPI) | `<= 0.15.3 (all releases)` | no fix yet | vendored Brotli 1.1.0 | | @ladybugdb/core and its platform packages (npm) | `<= 0.22.0 (all releases)` | no fix yet | prebuilt native addon; vendored Brotli 1.1.0 | | lbug (npm) | `<= 0.14.3 (all releases)` | no fix yet | prebuilt native addon; vendored Brotli 1.1.0 | | lbug (crates.io) | `<= 0.22.0 (all releases)` | no fix yet | vendored Brotli 1.1.0 | | LadybugDB.Native.* (NuGet: linux-x64, linux-arm64, osx-x64, osx-arm64, win-x64) | `<= 0.19.1 (all releases)` | no fix yet | vendored Brotli 1.1.0 |

We will continue to add to this list, and for packages which are directly included in your first party code, we will include the relevant Semgrep Supply Chain rules to help pinpoint locations you are using insecure versions of a library.

![Image 6: Semgrep Supply Chain findings for the Brotli unchecked return value advisory, showing four reachable findings in uv.lock files: two direct dependencies on brotli and brotlicffi, and two transitive ones through an httpx extra.](https://semgrep.dev/assets/semgrep-supply-chain-brotli-cve-2026-111497-reachable-findings.png) Semgrep Supply Chain showing four reachable findings for the Brotli advisory: two from direct dependencies and two from transitive ones.