Bye Bye Brotli?

Klaas van Schelven
Klaas van Schelven; October 25 - 6 min read | tags: Security

Is it time to say goodbye to Brotli yet? My vote is on yes.

This is where I’d love to same something smart about the vulnpocalypse, and how this is all super-relevant because of the new age of AI. I won’t, and instead will just say: the Brotli package is in such a state of disrepair that even mere humans would have been able to poke holes in it left and right.

But first: some anime, so you know this is actually serious:

An anime character posing in front of colorful Japanese lettering
Source: the accidentally(?) leaked video of the PHP2Shell exploit.

This time

So what got us here? It might have escaped your attention because the world was on fire over many security incidents (e.g. PHP2Shell, eMudhra, Railway and PingCAP’s breach) but we just had a thing called Node2Shell. It’s now called HTTP2Shell and I’d argue it should be called Brotli2Shell because that’s where the vulnerability actually is.

What is it? It’s a memory-corruption vulnerability in the Brotli decoder that can be triggered by a maliciously crafted Brotli-encoded payload. Since brotli decoding is something that often happens with untrusted input, and in the case of web servers: sometimes even simply on any request, this is a very serious vulnerability.

The timeline is somewhat sad though: Google fixed this in February 27, 2026, but apparently didn’t consider letting attacker-controlled input wander past the end of an eight-byte buffer into adjacent process memory a security issue. Since the regular release schedule for Brotli is “whenever the maintainer has time” it took until October 21, 2026 for a new release to be made. Also weird in the timeline: Node made their security advisory public before the Brotli release and puzzling what the consequences were for the rest of the world was left as an exercise to the broader public.

The last time

This wasn’t my first run-in with Brotli’s security “practices”. So I get to feel a bit more stupid than the rest of you. The previous time I ran into this was in November 2025.

Bugsink got its regular dependabot warning stating that it was running on an insecure version of (Python) Brotli, and that it needed to upgrade to a new version to be secure.

The problem being fixed: because brotli compresses very well it’s possible to create “bombs”: small brotli files that expand (in the failing scenario typically: in-memory) to something very large and then cause a DOS via memory exhaustion. Bugsink accepts requests with a Content-Encoding: br brotli encoding, and automatically decodes those. Which meant that Bugsink was vulnerable to a trivial memory exhaustion DOS attack.

However, upon reading the actual upgrade-notes I noticed that all the new version did was add a new parameter to a method, but that how this parameter was to be used was left as an excercise to the reader: there was no documentation for it, and the only other package that had figured out that something was to be done other than just upgrading, Scrapy, hadn’t actually figured out how to do that upgrade yet.

(Note: Upon researching this again in 2026 I noticed that this was flagged on the original PR too).

The lack of documentation bit actual implementers too. urllib3 implemented the new limit on 5 December 2025; 153 days later it had to fix that implementation because it had introduced another high-severity decompression-bomb vulnerability. In other words: totally expected when you leave people to figure it out for themselves.

Last time’s timeline

The timeline for last year’s drama was also pretty bad: the fix was first committed in public view on Sep 17, 2024, merged on January 8, 2025 and the release notes, committed 9 days later, said:

SECURITY
python: added Decompressor::can_accept_more_data method and optional
output_buffer_limit argument Decompressor::process; that allows mitigation
of unexpectedly large output; reported by Charles Chan

It was then left unreleased until October 27, 2025 as version 1.2.0, 283 days after the release notes; the Python packages had to wait another 9 days until release.

In brief, in days:

The next time

I have very little confidence that this will be the last time we’ll have a severe security problem in this package. That’s obviously informed by the above, but also by the current state of the GitHub repository:

Here’s 2 screenshots that should by themselves make the whole case:

Open pull requests in the Google Brotli repository
List of open pull requests on October 25, 2026.
Open issues in the Google Brotli repository
List of open issues on October 25, 2026.

To make the slightly more explicit: that’s 3 out of bounds reads sitting in public view as PRs (here, here and here), and a whole bunch of complaints about the release process (missing release notes, when will the next release be released?, and a release tracker that is still open). Also: the maintainer says that resources are limited, and releases happen when he can find a slice of time.

Because it’s older (in fact: over a year old) the trivial CPU exhaustion for the .NET implemenation didn’t make that screenshot. Fun fact: because Google doesn’t publish this code as an official NuGet package this code is probably not widely deployed but I know it’s somewhat widely in-use by virtue of copy-pasta.

So now what?

So where are we now? My take on the above is: One of the world’s largest tech giants has been pushing a somewhat-specified new way to do compression with a reference implementation in C. While its support is basically limited to what one guy is able to do in his spare time. In other words: they’re not serious people, and we should act accordingly.

For me (Bugsink) the way forward is not entirely clear, but there’s at least some things we can do. Not entirely clear because Bugsink is a Sentry-SDK compatible Error Tracker, and that means I’m aiming at accepting whatever Sentry SDKs send me. And since at least the Python sends Brotli-encoded messages, that means I’ll have to decode them.

Still, I’m adding a feature to just turn this off so that at least self-hosters that don’t actually use this sending-side can avoid future RCEs, and I’ve also asked upstream Sentry whether we can maybe just get rid of this.

There’s also the crazy idea of just vibe-coding a Python wrapper around a presumably more sane Rust implementation of Brotli. Crazy idea, and I’m sure not ready to commit real code to it, but at least it’s there as an escape hatch now.

For the rest of the world I’d say: maybe it’s time to seriously consider whether you actually need to support brotli in the first place.

If nobody does, at least I get to say “I told you so”.