On October 21, Node.js fixed a flaw in its Brotli decoder, CVE-2026-111497. I went through the Node images in my projects. Two Next.js frontends are built on a tag that still ships Node 22.21.1. That tag will never get the fix.

TL;DR: a crafted Brotli stream can make Node write outside the memory it reserved. The process can crash, leak memory, or run an attacker's code. You are exposed if your server decompresses Brotli that comes from outside. Node's fetch, axios and Express 5 all do it by default. Move to Node 22.23.4, 24.22.1 or 26.12.1. Node 18, Node 20 and some Docker tags will never be fixed.

Versions and source status: snapshot as of October 31, 2026. Check the latest fixes before deploying.

This article is for Node.js developers and for teams that ship Node in containers. You do not need to know Brotli to follow it.

Who decompresses Brotli without knowing it

The flaw has one condition. Your application must decompress Brotli data supplied by someone else. So the real question is simple. Which code on your server decompresses Brotli?

I read the source code of Node, undici, axios and body-parser. The table sums up what each one does.

The fetch case is the quietest one. Node built it on undici, the HTTP library of the Node.js project. Over HTTPS, fetch asks the server for Brotli on its own, with an accept-encoding header that includes br. Then it decompresses the response without asking you.

Changing that header does not protect you. In undici's code, the decoder depends on the response's Content-Encoding header. A hostile server can send you Brotli even if you did not ask for it.

The risk comes from URLs you do not control. Think of a webhook entered by a customer, a link preview, an RSS feed or an image to download. A compromised third-party API is enough too.

For incoming requests, Express 5 relies on body-parser 2. That version decompresses bodies sent with Content-Encoding: br. So any client can push a Brotli stream at you. Express 4 uses body-parser 1, which does not know Brotli.

An image that only builds a frontend receives no traffic. It still needs the update, but it is not the priority.

What Node.js fixed on October 21

On October 21, Node.js shipped three security releases: 22.23.4, 24.22.1 and 26.12.1. The advisory lists ten flaws. Four are rated "high". Only one can lead to code execution, the one in the Brotli decoder.

A server that compresses with Brotli says so with the Content-Encoding: br header. The client must then decompress the response before reading it. That is the decoder's job.

The decoder reads the stream piece by piece. When it hit an invalid section, it thought it was just missing a few bytes. It waited for more instead of rejecting the stream.

Meanwhile, it kept storing the next bytes in a small eight-byte buffer. It did not stop at the edge. So it wrote into the memory next to it. This is called an out-of-bounds write.

Node lists three possible effects. The process can crash. It can leak part of its memory. Or the attacker can run their own code inside your Node process.

Node rates the flaw "high". The CVE record, written by Google, scores it 9.2 out of 10. The NVD, the US vulnerability database, scores it 9.8.

The bug comes from Google's Brotli library. Google shipped the fixed versions the same day: 1.0.10, 1.1.1 and 1.2.1. But Node ships its own copy of Brotli.

I checked with ldd in two official images. The node binary loads no Brotli library from the system. So updating the packages in your image changes nothing for Node. It is the same trap as Go's eMudhra fix, which I covered yesterday.

Confirmed exploitation since October 27

On October 22, the CVE record still said "Exploitation: none". CISA added the flaw to its KEV catalog on October 27. That catalog records confirmed exploitation. The assessment changed to "active" the next day.

On October 30, Singapore’s Cyber Security Agency also reported a public proof of concept. These sources describe successive states. They do not give an account of a specific attack here. This snapshot ends on October 31, 2026.

My lab: 14 FROM node lines, three versions that will never be fixed

I listed the FROM node: lines in every Dockerfile of my projects. I count 14 of them, in 11 files. For each tag, I read Docker Hub, then ran node --version in today's image.

The tag that surprised me is node:22-alpine3.21. It looks like it follows Node 22. In fact, it ships Node 22.21.1, and it has not moved in a year.

Alpine is a very small Linux distribution. The alpine3.21 suffix pins its version. The team behind the Node images dropped Alpine 3.21 on December 4, 2025. Today it only builds the Alpine 3.23 and 3.24 variants.

Since then, that tag gets no new Node version at all. Nothing warns you. The build passes and the image starts.

One of the two repositories does have a Dependabot that watches Docker images. In May, it proposed moving to node:25-alpine3.21. It kept the suffix, so it stayed inside the frozen family. Node 25 reached end of life three weeks later, on June 1.

node:18-alpine and node:20-alpine are frozen for another reason. Node 18 has been end of life since April 30, 2025, Node 20 since April 30, 2026. Their copy of Brotli is version 1.1.0, inside the affected range. Node's advisory says it plainly. These official releases remain vulnerable because their release lines received no fix.

One last detail, on my laptop. The cached node:24-alpine image dates from August 3. It holds Node 24.19.0, without the fix. A build that reuses that copy makes a vulnerable image, even with an up-to-date tag on Docker Hub.

Then I searched my code for anything that decompresses Brotli. No service calls the Brotli functions of zlib. None uses Express. The two Next.js frontends make no server-side fetch in production. Their API calls leave from the browser.

A single route makes a server-side fetch. It only turns on in local development, toward a storage service I control. So my real exposure is low.

But a grep does not see what my dependencies do. And the next server-side fetch will arrive without anyone rereading the FROM line.

The Brotli version number proves nothing

Node shows the version of its libraries in process.versions. I compared two Node 22 images, one vulnerable and one fixed:

docker run --rm node:22-alpine3.21 node -p "process.version + ' brotli ' + process.versions.brotli" # v22.21.1 brotli 1.1.0 docker run --rm node:22-alpine node -p "process.version + ' brotli ' + process.versions.brotli" # v22.23.4 brotli 1.1.0 

Both show Brotli 1.1.0. Node applied Google's fix to its copy without changing that number. Node 24 is the same, with 1.2.0 before and after. Compare the Node version with 22.23.4, 24.22.1 or 26.12.1 instead.

The fix also adds a constant to zlib.constants. In my tests, it only exists in the fixed versions. It makes a quick check in a running container:

docker exec my-container node -p "process.version + ' ' + ('BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH' in require('zlib').constants)" # v22.23.4 true (fixed) # v22.21.1 false (vulnerable) 

What to change

If you cannot deploy today, two settings reduce the risk. With Express 5, the inflate: false option rejects compressed bodies with a 415 error:

app.use(express.json({ inflate: false })) // a body sent with Content-Encoding: br gets a 415 error 

With axios, the decompress: false option turns decompression off. Your code then gets the compressed bytes, and handling them is up to you. For fetch, I found no matching setting. Only a Node update protects it.

The checklist

A Node fix only protects the container where it actually runs.

  • [ ] List every FROM node: line in your Dockerfiles
  • [ ] Spot end-of-life versions: Node 18, Node 20 and older
  • [ ] Spot tags tied to an Alpine version that is no longer built, such as alpine3.21
  • [ ] Run node -p process.version in every production container
  • [ ] Do not trust process.versions.brotli
  • [ ] Look for server-side fetch and axios calls to URLs you do not control
  • [ ] Look for Express 5 and direct calls to the Brotli functions of zlib
  • [ ] Rebuild with --pull, then redeploy

The takeaway

The Brotli flaw does not need you to call Brotli yourself. fetch, axios and Express 5 do it for you, on data that comes from outside. And a Docker tag can look current while it ships a version that is a year old.

Open your Dockerfiles today. Look for Node 18 and 20, and for tags that pin an Alpine version. Nobody will update them for you.

Want a review of your images and your build pipeline? See my DevSecOps pipeline offer, or let's talk.

Sources: Node.js, October 21, 2026 security releases · Node.js 22.23.4 release notes · CVE-2026-111497 record · NVD, CVE-2026-111497 · CISA KEV catalog · Singapore CSA, alert AL-2026-154 · Brotli 1.2.1 release notes · undici 6.29.1, fetch source · body-parser 2.4.0, body reading · axios HTTP adapter · Node.js zlib documentation · Node.js release schedule · docker-node, Alpine 3.21 removal · Docker Hub, node image · docker build documentation