Contents
The Brotli bug disclosed on 21 October is not an integer overflow or a bad length field. According to the Node.js advisory, malformed input made the streaming decoder write past the end of an eight-byte internal buffer because “an invalid block switch was reported as a temporary need for more input rather than a permanent format error.” A permanent error should end the stream. A temporary one tells the caller to supply more bytes and call again, so the decoder kept appending whatever it was given.
That detail matters more than the CVSS score, because it tells you where the bug lives (the contract between the decoder’s internals and its caller) and why the exposure is wider than “apps that import brotli”.
What the upstream diff changes
The GitHub advisory GHSA-9v8h-qqgm-4m35 places the flaw in c/dec/decode.c and classifies it as CWE-252, Unchecked Return Value. It lists four upstream fix commits, and I read two of them, 0ed4268 and da98063. I could only read them through a summarizing fetch, not a raw checkout, so I am paraphrasing the change and not quoting code.
Before the fix, the helpers that handle a block switch for literals, commands and distances had inconsistent return types. DecodeBlockTypeAndLength returned a boolean, and the non-safe wrappers (DecodeLiteralBlockSwitch, DecodeCommandBlockSwitch, DecodeDistanceBlockSwitch) returned void, discarding whatever the inner helper reported. One of the failure paths, a tree with one or fewer block types, was simply false, and nothing downstream could distinguish it from “ran out of input”.
After the fix:
DecodeBlockTypeAndLengthand the wrappers return aBrotliDecoderErrorCode.- The one-or-fewer-block-types case returns a new
BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, which takes the previously reserved error slot-17. - Running out of input returns
BROTLI_DECODER_NEEDS_MORE_INPUT, as before, but now only for that case. - A new macro,
BROTLI_SAFE_WITH_STATUS, stores any non-success status and jumps to the decoder’s save-state-and-return path.ProcessCommandsInternaluses it at the three block-switch call sites. - A debug assertion checks that the remaining meta-block length stays within
BROTLI_BLOCK_SIZE_CAP.
The point of a resumable decoder is that “not enough bytes yet” is a normal state and must be cheap to re-enter. That design makes it easy to let a real error fall into the same lane. If you maintain any streaming parser with a need-more-input status, the review question this bug suggests is: can any malformed-input path return the same value as a short read?
Which copies of the decoder you run
The advisory’s affected range is Brotli 1.0.3 through 1.2.0, with fixes in 1.0.10, 1.1.1 and 1.2.1, all tagged on 21 October per the google/brotli releases page. That range is only the start, because the decoder is vendored or bundled in many places. The Hacker News summary lists separate fixed versions for Node.js (22.23.4, 24.22.1, 26.12.1), Bun 1.4.4, Envoy, Istio, the Python Brotli and brotlicffi packages (1.2.1 and 1.2.1.0), and cdxgen. Node.js 20 and older are end of life and get no fix.
Two different exposure descriptions are in circulation, and they are worth keeping apart. Node.js’s own advisory names node:zlib, for example zlib.brotliDecompress on an untrusted buffer. The Hacker News article adds that built-in fetch() reading a Content-Encoding: br response, and Express 5 or body-parser 2 decoding a request body, are also exposed. I did not verify the fetch() and Express claims against a primary source, so treat them as reported.
The Python case is the one most teams would miss. I installed urllib3 2.8.0 in a clean virtual environment and asked it for its default headers:
without Brotli installed: {'accept-encoding': 'gzip,deflate'}
with Brotli 1.2.0: {'accept-encoding': 'gzip,deflate,br'}
So adding the package, directly or as someone else’s dependency, changes what your HTTP client asks servers for and enables a decode path through the vulnerable C code. Your own code never imports brotli. The urllib3 source I read also treats a missing Brotli package as “no br decoder”, so removing the package turns the path back off. That is consistent with the uninstall workaround the Hacker News summary lists. I did not run the exploit and have no reason to think one is public: the same summary says Imperva’s proof of concept is unreleased and CISA assessed exploitation as “none” as of 23 October.
What the mitigations do and do not cover
The reported workarounds fall into three groups, and they do different things:
| Workaround | Covers | Does not cover |
|---|---|---|
Reject Content-Encoding: br on requests | Server-side decoding of client bodies | Responses your own clients fetch |
| Disable automatic br decoding in clients | Malicious or compromised upstreams | Server-side request decoding |
Uninstall Python Brotli / brotlicffi | urllib3-based clients (requests, httpx, aiohttp as reported) | Other decoders in your stack |
The Hacker News summary also notes that removing br from Accept-Encoding is not enough on its own, since a hostile server does not have to honor what you asked for, and quotes Vercel saying a WAF does not see responses a function fetches from other servers. The direction matters: OpenAI’s description, as relayed there, is that this works both ways, so a malicious server can hit a client and a malicious client can hit a server.
Updating is the only complete fix according to those reports. For a container fleet that means checking each layer separately: the Node.js binary, the Python site-packages, any proxy that ships its own Brotli, and tooling images such as scanners that decompress untrusted input.
A note on the February change
The Hacker News article says the underlying change landed in the Brotli source on 27 February 2026, with no release carrying it and nobody flagging it as a security fix. I could not confirm that date from a primary source. The two fix commits I read have no date in what I retrieved, and the GHSA does not mention it. If it is accurate, it is a useful reminder that “fixed in main” and “fixed in a release you consume” are separate events, and vendored copies pin to the second.
Sources
- Node.js, 21 October 2026 security releases
- GHSA-9v8h-qqgm-4m35
- google/brotli releases and fix commits 0ed4268, da98063
- The Hacker News: Brotli Decoder Flaw Behind Node.js RCE (secondary; source of the Bun, Envoy, Istio, fetch, Express and workaround details)
- My own check:
urllib32.8.0 defaultaccept-encodingwith and withoutBrotli1.2.0 installed, in a clean virtualenv