Software

Node.js Brotli CVE-2026-111497: is your app exposed?

By · Thu Oct 22 2026 · 10 min read · 1 views

View as a Web Story

Software#security#node.js#CVE#express#brotli

Node.js Brotli decoder vulnerability CVE-2026-111497 and which HTTP clients reach it

Upgrade to Node.js 26.12.1, 24.22.1 or 22.23.4, then check whether your code decodes Brotli data it did not produce. CVE-2026-111497 is a Brotli decoder flaw that Node.js rates high and says permits remote code execution. It matters most where untrusted bytes reach the decoder. Our lab found that is more common than most teams assume.

This post lists which popular clients decode Brotli on their own, which Express versions decode Brotli request bodies, and a short checklist for finding the paths in your own code.

What is CVE-2026-111497?

CVE-2026-111497 is a flaw in the Brotli decoder that ships inside Node.js. The Node.js security release post of October 21, 2026 describes an out-of-bounds write that permits remote code execution. It labels the severity high and lists the 26.x, 24.x and 22.x release lines as affected.

Brotli is a compression format from Google that web servers use for the Content-Encoding: br header. It usually shrinks text better than gzip. Node.js has included a Brotli decoder in its zlib module since version 10.16.

The same release fixed nine other issues, so ten CVEs in total. This post stays on the Brotli one, because it is the only one the Node.js project labels as permitting remote code execution.

According to the Node.js post, the issue was reported by OpenAI Codex Security, credited to Khai Tran and Sylvie Mayer. Matteo Collina (mcollina) and aduh95 wrote the fix. Some secondary articles name a different reporter, so rely on the Node.js page for credit.

Release line Fixed in Status
Node.js 26.x 26.12.1 Patched
Node.js 24.x 24.22.1 Patched
Node.js 22.x 22.23.4 Patched
Node.js 20.x and older None End of life, always affected

How exposed is a typical app?

Sources disagree on how widely the flaw can be reached, so treat any single headline with care. The answer depends on whether attacker-controlled bytes reach the Brotli decoder in your app.

Three outlets frame it differently:

  • The Astra Security advisory says the remote code execution applies to any Node.js application accepting Brotli-encoded input.
  • The GBHackers report says the advisory gives no exploitation prerequisites and that not every application is remotely exploitable.
  • The Vercel security bulletin says exposure requires application or library code that decompresses attacker-controlled Brotli. It also says researchers demonstrated process-memory disclosure and command execution on unpatched 22.x, 24.x and 26.x.

The Node.js post says nothing about exploitation in the wild. Astra says none has been reported yet. Those statements can change, so check the Node.js page before you decide how fast to move.

The practical reading is the Vercel one. The decoder is only a risk when your process feeds it bytes that someone else controls. The rest of this post measures where that happens.

Advertisement

How we tested

The test setup was a Node.js v22.23.3 process on an Apple M2 laptop. That version is one patch behind the fixed 22.23.4, so it still carries the flaw. We used only benign data: a JSON string under 40 bytes, compressed with zlib.brotliCompressSync. No malformed Brotli data was sent and nothing was exploited.

We ran two tests.

  1. Response side. A local server always replied with Content-Encoding: br. Eight clients each fetched that URL. For every client, we logged the Accept-Encoding header it sent and whether it returned the decoded JSON.
  2. Request side. We started an Express server with express.json() and posted the Brotli body with Content-Encoding: br. We repeated it with Express 4 and body-parser 1.20.9.

Client versions were axios 1.20.0, got 11.8.6, node-fetch 2 and 3.3.2, undici 8.12.3, and the fetch built into Node.js 22.23.3. Express was 5.3.0 with body-parser 2.4.0.

Which Node.js clients decode Brotli on their own?

Six of the eight clients decoded a Brotli reply without any option set. Only the raw http.get and undici.request handed back the compressed bytes.

Six of eight Node.js HTTP clients decode a Brotli response by default; http.get and undici.request do not

Client Sends Accept-Encoding Decoded the Brotli reply
fetch (built in) gzip, deflate Yes
undici.fetch gzip, deflate Yes
undici.request none No
axios 1.20.0 gzip, compress, deflate, br Yes
got 11.8.6 gzip, deflate, br Yes
node-fetch 2 gzip,deflate Yes
node-fetch 3.3.2 gzip, deflate, br Yes
http.get none No

The table is the quotable finding of this post (our lab, 2026). The surprise is in the first row. The built-in fetch asks only for gzip, deflate, yet it still decoded a Brotli reply. A well-behaved server would never send Brotli to that client. A hostile server does not have to be well-behaved.

That has a plain consequence. Reading the request header is not a safe test of exposure. If your server calls fetch() on a URL that a user can influence, such as a webhook target, a link preview or an image proxy, then the remote host chooses the encoding. It can reply with Brotli whatever your client asked for.

The takeaway: a client's Accept-Encoding header tells you what it prefers, not what it will accept.

Does Express decode Brotli request bodies?

Express 5 does, and Express 4 does not. That is a version difference most teams will not know about, and it changes who can reach the decoder.

In our lab, express.json() from Express 5.3.0 and body-parser 2.4.0 accepted a Brotli-encoded JSON body and parsed it. The route returned {"parsed":true} with HTTP 200 (our lab, 2026).

The same request to Express 4 with body-parser 1.20.9 failed with this error:

UnsupportedMediaTypeError: unsupported content encoding "br"

A gzip body parsed fine on both. So an Express 4 service rejects Brotli request bodies before they reach the decoder, and an Express 5 service decodes them for any caller who can post JSON.

This is a measurement, not a claim of exploitability. Our test sent a harmless body to a Node.js build that still had the flaw. It shows that a default Express 5 JSON endpoint is a place where outside bytes meet the Brotli decoder. Whether those bytes can do harm is the question the patch answers.

Two details help you scope it:

  • The middleware must be mounted for the route. A route with no body parser never calls the decoder.
  • Reverse proxies may already strip or reject the header. Check yours before assuming either way.

The body-parser repository documents which encodings each major version accepts.

How do you find Brotli paths in your own code?

Search for four things: direct zlib Brotli calls, HTTP clients pointed at untrusted URLs, body parsers that accept br, and frameworks that decode request bodies for you. This takes about ten minutes in a typical service.

  1. Direct calls. Run grep -rn "brotliDecompress\|createBrotliDecompress" src node_modules/your-deps. Any hit that takes user input is a path.
  2. Outbound fetches. List every place the server calls fetch, axios, got or node-fetch with a URL derived from user input. Webhooks, URL previews, feed readers, image proxies and OAuth discovery are the usual ones.
  3. Body parsers. Run npm ls body-parser express. Express 5 and body-parser 2.x accept Brotli. Express 4 and body-parser 1.x do not.
  4. Other frameworks. Fastify, Hono, Koa and Next.js route handlers each have their own rules for Content-Encoding. Read the docs for your version, or send a harmless Brotli body to a test environment as we did.

Also look at tools, not just services. A CLI that downloads files or a build step that calls fetch on remote URLs runs the same decoder.

Where does the decoder hide in dependencies?

Most teams never call zlib themselves, so the decoder usually sits inside a dependency. The HTTP client table above is one layer. Three more layers are worth a look.

  • SDKs. Cloud, payment and AI SDKs wrap fetch or axios. If an SDK follows a redirect to a host you did not choose, it uses the same decoder.
  • Scrapers and feed readers. These fetch many unknown hosts by design. Every reply is untrusted bytes, so they are the highest-risk place to run an unpatched Node.js.
  • Build and CI tooling. Package installs, screenshot tools and link checkers download remote content during builds. A build agent on an old Node.js image has the same flaw as a web server, though a smaller audience can reach it.

For example, an image proxy that accepts a URL and returns a resized file sits in the first two groups at once. Patch those services first. A static site with no server code and no fetches comes last.

What can you do before you upgrade?

The Node.js project lists no workaround, and the real fix is the new version. If you cannot upgrade today, reduce the number of paths that reach the decoder.

Astra suggests two stopgaps, and both are its own advice rather than the Node.js project's: disable Brotli decompression, or strip Content-Encoding: br from untrusted input. At an nginx reverse proxy, rejecting Brotli request bodies looks like this:

if ($http_content_encoding = "br") {
    return 415;
}

This only covers inbound request bodies. It does nothing for responses that your own server fetches from other hosts. For those, avoid fetching from user-supplied URLs until you have patched.

If you deploy on a managed platform, Vercel says its firewall rules block the attack vector for projects it hosts. It also says those rules do not cover responses your functions fetch from other servers. So a managed host closes the inbound door and leaves the outbound one open. Contact your hosting provider to ask which Node.js version your runtime uses and when it moves to the fixed release.

How do you upgrade safely?

Move to the fixed patch release in the same major line you already run. A patch bump inside a line carries the least risk.

  1. Check the current version with node -p process.version.
  2. Pick the target: 22.23.4, 24.22.1 or 26.12.1.
  3. Update the FROM node: line in your Dockerfile, or the engines and CI matrix entries, and rebuild images instead of patching in place.
  4. Run your tests. Watch for anything that depends on HTTP behaviour, since the same release updated Undici and c-ares.
  5. Deploy, then confirm with node -p process.version inside the running container.

A rebuild matters because a long-lived image that was pulled last month still ships the old binary. Pinning a tag such as node:22 does not update it until you pull again.

Teams on Node.js 20 have a harder choice. The Node.js project does not patch end-of-life lines, so the only fix is moving to 22, 24 or 26. Our post on which Node 20, 22, 24 or 26 version to run after October 28 explains the trade-offs. Our earlier post on Node's built-in WebSocket client and the version that fixes it makes the same case for staying on a supported line.

Should you upgrade today or wait?

Upgrade this week if your service decodes Brotli from untrusted sources. Schedule it within the normal patch window if it does not. Do not wait if you run Node.js 20, because there will be no patch to wait for.

Your situation Action Urgency
Express 5 public JSON API Upgrade, and consider rejecting br at the proxy This week
Server fetches user-supplied URLs Upgrade, and restrict targets This week
Express 4 API, no outbound fetches of user URLs Upgrade in the normal window Routine
Internal tool, no untrusted input Upgrade in the normal window Routine
Node.js 20 or older Move to a supported line Now

The cost of upgrading a patch release is small. The cost of leaving an unpatched decoder on an internet-facing service is not known, because the severity is rated high and there is a stated remote code execution label. When the downside is that open-ended, a patch bump is the cheaper option.

What we could not verify

We could not confirm exploit conditions beyond what the three vendor pages state. We did not attempt to trigger the flaw, and we do not publish anything that would help someone do so. The lab above shows only where Brotli decoding is reachable, not whether it can be abused.

Independent CVE databases had no entry for the identifier when we searched on October 22, 2026, so the Node.js release post is the source of record for the identifier, versions and severity.

Advertisement

FAQ

Is Node.js 22 affected by CVE-2026-111497?

Yes, Node.js 22.x versions before 22.23.4 are affected, according to the Node.js security release post of October 21, 2026. Node.js 24.x is fixed in 24.22.1 and 26.x in 26.12.1. Node.js 20 and older are end of life and will not be patched.

Does my app need to accept Brotli to be vulnerable to CVE-2026-111497?

Not necessarily. Your app is exposed when it decodes Brotli data from an untrusted source, including responses from servers it calls. In our lab, the built-in fetch decoded a Brotli reply even though it advertised only gzip and deflate.

Does Express 4 reject Brotli request bodies?

Yes, with body-parser 1.20.9 in our lab. It returned HTTP 415 with the message unsupported content encoding br. Express 5.3.0 with body-parser 2.4.0 parsed the same Brotli JSON body, so Express 5 services reach the decoder through request bodies.

How do I check which Node.js version my server runs?

Run node -p process.version in the environment that serves traffic, such as inside the running container. It must read 22.23.4 or later on the 22 line, 24.22.1 or later on 24, or 26.12.1 or later on 26. A version printed on your laptop says nothing about production.

Is CVE-2026-111497 being exploited in the wild?

The Node.js post does not say, and Astra Security reports no in-the-wild exploitation as of its publication. Vercel states researchers demonstrated memory disclosure and command execution on unpatched versions. Check the Node.js security page for updates.

Comments

Loading…

Sign in to join the conversation.

Related posts