---
title: "One Decoder, Dozens of Copies, and No Package to Upgrade"
slug: "brotli-cve-vendored-copies"
language: "en"
date: "2026-10-28T00:34:53.597Z"
updated: "2026-10-28T00:34:53.000Z"
description: "CVE-2026-111497 is a small bug in Brotli's C decoder. The hard part is that the decoder is vendored into runtimes, databases and firmware."
canonical: "https://fix.slopit.io/brotli-cve-vendored-copies/"
tags: ["security", "supply-chain", "c", "open-source"]
---

CVE-2026-111497 is a heap out-of-bounds write in Google's Brotli C decoder. The bug is small and the fix is smaller: when the decoder hits an invalid block switch, it treats the stream as incomplete, keeps stale state, and can write past the memory it owns. Brotli 1.2.1 changes that path to return a real error instead of asking for more input.

The interesting part is not the bug. It's the distribution.

A Brotli decoder is in Node.js, Bun, .NET, Java, Go, Envoy, Istio, DuckDB, Apache Arrow, ClickHouse, MuPDF, Pillow, pycurl, ImageMagick, pygame, 7-Zip, p7zip, woff2, and EDK II — the reference firmware for UEFI. In some of those it's a dependency you can bump. In many it's a copy of the C source vendored into the tree, or a prebuilt wheel, or a statically linked binary, or a firmware blob.

Semgrep's post calls it "the one decoder to rule them all," and the title is accurate in a way that should be uncomfortable: the same parser that handles HTTP responses also handles fonts, Parquet files, archives, PDFs, and firmware updates. One memory-safety bug, dozens of ecosystems, and no single package to upgrade.

## "Just patch" is a fiction for compiled artifacts

For a Python project, the fix might be `Brotli>=1.2.1`. For DuckDB or Arrow, it means rebuilding the project with a newer vendored copy, or waiting for a release. For Envoy and Istio, it's a proxy image upgrade. For a Chromium-based product, it's a full rebuild of a very large artifact. For firmware, it's a vendor release cycle that may not exist.

The article lists the split explicitly: packages where updating works, and packages that ship Brotli inside their own binary and need to be rebuilt or replaced. Several had no fix at publication. Several had fixes on `main` but no release.

That's the part supply-chain tooling handles badly. An SBOM lists components the package manager knows about. It does not usually know that a wheel contains a statically linked `libbrotlidec`, or that a Rust crate vendors a C decoder, or that a firmware image embeds Brotli 1.0.3. The dependency graph is not the same as the code graph, and the gap is exactly where this bug lives.

## Why decompressors keep doing this

There's a pattern here that predates Brotli and will outlast it. Input parsers are the perennial attack surface: compression, image formats, fonts, archives, media containers. They're written in C for speed and portability, they process untrusted bytes by definition, and their failure mode is memory corruption in the process that called them.

The specific mistake is also a classic: treating malformed input as incomplete input. "I don't have enough bytes yet" and "these bytes are invalid" are different states, and collapsing them means the parser continues with state it should have discarded. The fix is a one-line change in behaviour; finding it required someone who reads C and understands the decoder's state machine.

The article notes that the bug was introduced in Brotli 1.0.3 and sat there. That's not negligence; that's the normal half-life of a subtle parser bug in a widely vendored library. It also hints at why AI-assisted review is changing the economics: the scarce input was a researcher who could read this code, not the fix itself.

## What to do, beyond the list

The immediate answer is to find every copy you ship. That means inventorying vendored C separately from package manifests: check wheels for bundled shared objects, check Go modules for cgo dependencies, check Rust crates for vendored sources, check firmware and container base images. It's tedious and it doesn't fit neatly into a scanner.

The structural answers are less satisfying. Prefer memory-safe implementations where they exist and are fast enough. Treat decompression as a trust boundary rather than a utility call. Keep parsers on the smallest possible attack surface — don't link a Brotli decoder into a process that never needs to decompress untrusted data. And assume that any C library compiled into your artifact will need a rebuild you can't do with a lockfile update.

The uncomfortable conclusion is that the same property that makes this bug bad — one decoder everywhere — is also what makes it tractable. There is one fix, one upstream commit, and a finite list of copies to chase. The problem isn't that the code is duplicated; it's that nobody has a complete map of the duplication. Until SBOMs can see inside binaries, "one decoder to rule them all" is a description of the blast radius, not a remediation plan.

*Source: [Broccoli2Shell: The One Decoder to Rule Them All (CVE-2026-111497) — Semgrep](https://semgrep.dev/blog/2026/broccoli2shell-brotli-cve-2026-111497/).*
