DEV Community

OnaEiuspkz
OnaEiuspkz

Posted on

CVE-2026-111497: how a Brotli block-switch error became a Node.js memory write

CVE-2026-111497: how a Brotli block-switch error became a Node.js memory write

A single wrong assumption inside a streaming decompressor is enough to turn a malformed archive into a memory-corruption primitive. CVE-2026-111497 is that case for the Brotli decoder shipped with Node.js: input that should be rejected as invalid is instead treated as merely incomplete, and the decoder keeps copying into a fixed-size internal buffer.

The Node.js project published the fix in its October 21, 2026 security releases, alongside nine other CVEs. The Singapore Cyber Security Agency rates the Brotli issue 9.8 on CVSS v3.1, while the Node.js advisory classifies the same defect as high. Both statements describe one defect; the difference is scoring convention, not disagreement about the code.

What the decoder did wrong

Brotli is a general-purpose compression format, and like any streaming decoder it processes input in pieces. A stream is divided into meta-blocks and blocks, and the decoder carries state between calls until a block ends. When the state machine encounters a block switch it cannot interpret, the correct response is to declare a permanent format error and stop.

According to the advisory, the bundled decoder did something else. An invalid block switch was reported as a temporary need for more input. A caller that faithfully follows the streaming contract then does the reasonable thing: it feeds more bytes and asks the decoder to continue. The internal buffer holding that state is eight bytes wide, and it kept receiving input it was never sized to hold. The result is a write past the end of the buffer, in the same process that is decoding untrusted content.

What an attacker actually needs

The precondition is application-level rather than network-level. Something in the deployment has to hand attacker-controlled Brotli data to node:zlib. That covers a lot of ground in real services: an API that accepts a compressed request body, a worker that unpacks a compressed job payload, a proxy or gateway written in Node.js that inflates upstream responses, or a CLI that opens a compressed file produced elsewhere.

Where such a path exists, the consequences follow the usual pattern for an out-of-bounds write in a parser. The process can die, which is a denial of service. Memory adjacent to the overflow can be read back or reflected through later output, which is disclosure of process memory. And with enough control over the overwritten bytes, an attacker can redirect execution, which is remote code execution in the advisory's wording.

Two details keep this from being a universal remote exploit. First, the vulnerable code sits behind the application's choice to decompress. A Node.js service that never inflates Brotli input never reaches the bug, no matter what an attacker sends. Second, the write is small and constrained by the decoder's state machine, so weaponising it demands more than a fuzzer crash.

Fixed versions and the buffer story

The affected release lines are 22.x, 24.x and 26.x. Fixes are 22.23.4, 24.22.1 and 26.12.1, and the project notes that end-of-life lines are always affected when a security release lands, which is a polite way of saying that unsupported branches will not receive this patch at all.

The upstream component matters too. The CVE record attributes the defect to Brotli 1.0.3 through 1.2.0 in c/dec/decode.c, with fixes in 1.0.10, 1.1.1 and 1.2.1. Teams that vendor Brotli separately from Node.js, or build native services against the same library, inherit the same decode path and need the same version floor.

What to do this week

Upgrade to the fixed release on each supported line and treat older branches as unsupported rather than merely unpatched. Where an upgrade has to wait, reduce reachability: cap decompressed and compressed sizes before inflation, reject Brotli bodies from untrusted clients when the endpoint does not require them, and keep the decoding work in a process that does not hold credentials worth stealing.

It is also worth checking the dependency rather than only the runtime version string. A container built on an older base image, or a service pinned to a Node.js build from before the release, will report the same application version while carrying the vulnerable decoder.

The bug was reported by Khai Tran and Sylvie Mayer of OpenAI Codex Security and fixed by mcollina and aduh95. It is a reminder that the boring parts of a runtime, the decoders nobody reads, decide how much damage a malformed packet can do.

References

Top comments (0)