Node.js Brotli RCE: You Can Be Exposed by the Requests You Send

On 21 October 2026 the Node.js project published security releases for every supported line, fixing ten issues. The one that drew attention was CVE-2026-111497, described in the advisory as an out-of-bounds write in the bundled Brotli decoder that permits remote code execution. Researchers have since circulated the name Node2Shell, which the advisory and the CVE record do not use.

Most coverage treated it as a Node.js patching exercise. That framing is the reason some teams will still be exposed in three months. The defect is in Google’s Brotli decoder, not in Node.js code, and it reached Node.js, Bun, Python packages, Envoy and other software that bundle or link the same library. CISA added it to the Known Exploited Vulnerabilities catalog on 27 October, which moves it from a patching backlog item to something with a deadline attached for US federal agencies and a strong signal for everyone else.

This post covers the mechanism, why a version check does not settle your exposure, and what a SOC does in the window between disclosure and a completed rollout.

The Mechanism in One Paragraph

A Brotli stream can signal a switch between block types mid-decode. When a stream signalled a block switch that its own structure made invalid, the vulnerable decoder returned the condition as a temporary need for more input rather than as a permanent format error. The streaming decoder took that at face value and kept appending input bytes to an eight-byte internal buffer, writing past the end of it. That description comes from the Node.js advisory and is expanded in Akamai’s analysis.

The class of bug is an unchecked return value leading to an out-of-bounds write. The fix is correspondingly small. Upstream Brotli added a dedicated error, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, so the invalid switch fails immediately and is passed back to the caller. Node.js cherry-picked that commit.

Two details in that history are worth noting, because they say something about how fixes travel. The upstream change landed in Brotli’s source in February 2026 and was only distributed as a security release in October. And the severity is recorded differently depending on who is scoring it, with the Node.js advisory rating the issue high while Google, which maintains Brotli, scores it 9.2 critical under CVSS 4.0. If your vulnerability management process keys off a single severity field, it will treat those as different problems.

Why the Version Number Does Not Settle It

The instinct is to query every host for its Node version, compare against the fixed releases, and close the ticket. Four conditions break that method, and each one produces a different wrong answer.

  1. Shared versus bundled Brotli: Node.js normally bundles its own copy of Brotli, but several Linux distributions build their Node package against the system libbrotli instead. On those systems the fix arrives in the distribution’s brotli package rather than in the Node version number, so a host can report a patched-looking Node and still run vulnerable decoder code, or report an old Node that is already fixed underneath.
  2. End-of-life lines get nothing: the fixed releases are 22.23.4, 24.22.1 and 26.12.1. Node.js 20.x and other end-of-life lines carry the same code and receive no fix, so a version comparison against the patched list marks them as not-applicable when they are the most exposed systems you own.
  3. The same library reached other runtimes: Bun was affected until 1.4.4, Python’s Brotli package until 1.2.1, and the flaw also reaches software such as Envoy. A Node-only inventory sweep will miss a service mesh sidecar sitting in the same cluster.
  4. Containers freeze the old copy: an image built before 21 October carries the vulnerable decoder regardless of what the host runs. Rebuilding on a patched base image is a separate action from upgrading a package, and it is the one most commonly left undone.

Exposure Is a Behavioural Question

Running an affected version is necessary for exposure but not sufficient. The application has to decompress Brotli data that someone else controls. A bare Node.js HTTP server does not decode incoming Brotli request bodies on its own, as the Akamai write-up points out, because content decoding is left to the application. Four paths put you in scope.

  • Direct node:zlib calls: any Brotli decompression function invoked on input you did not generate.
  • Inbound request bodies: frameworks and middleware that unpack Brotli-encoded uploads, including Express 5 and body-parser 2 configurations that enable it.
  • Outbound responses: the built-in fetch(), and clients such as axios and got, decode a response sent with Content-Encoding: br automatically. The server you call does not have to be hostile, it only has to be able to return a crafted response.
  • Anything that fetches a URL a user supplied: webhook registration, link preview generation, avatar fetching, document import, SSRF-adjacent features. These turn an outbound code path into an inbound attack surface, and they are rarely on the list when teams scope the blast radius.

That third and fourth category are the uncomfortable ones. A team can correctly conclude that it never decompresses user uploads, and still be exposed through a feature that fetches a URL on the user’s behalf.

Finding Your Real Exposure

A useful inventory answers three questions per service rather than one, and the order matters because each narrows the next.

  1. Which decoder is this process using: bundled or system-shared. On Linux, inspect the running process for a linked libbrotli rather than trusting the package manifest.

# does this Node process link a shared Brotli, or use its bundled copy

ls -l /proc/<pid>/map_files 2>/dev/null | grep -i brotli

node -p “process.config.variables.node_shared_brotli”

 

  1. Does this service decode Brotli it does not control: search the codebase for the Brotli entry points and for HTTP clients that accept br encoding, then confirm against what the service does in practice rather than what the code allows.

rg -n “brotliDecompress|createBrotliDecompress|BrotliDecompress” .

rg -n “Accept-Encoding|content-encoding|\bbr\b” –type js –type ts

 

  1. Can an outsider influence what this service fetches: list every feature that takes a URL from a user, a webhook, or a third-party integration. This is the category that version scanning never surfaces.

What the SOC Does Before the Rollout Finishes

Patching a runtime across a GCC bank’s estate takes longer than an advisory does to circulate, and CrowdStrike’s 2026 Global Threat Report put average eCrime breakout time at 29 minutes in 2025, a pace HawkEye’s threat advisories track week to week. The question for the SOC is what is detectable in the meantime.

Exploitation of a memory-corruption bug in a decoder does not announce itself in application logs. It shows up as the second-order effects of a process being corrupted, which is a detection problem the SOC already knows how to solve if the signals are correlated rather than viewed separately.

  1. Unexpected child processes from the runtime: a node process spawning a shell, a network utility, or an interpreter. For most server applications this is close to decisive on its own, because the legitimate child process set is small and stable.
  2. Crash and restart loops: failed exploitation is far more common than successful exploitation, and it looks like a segmentation fault or an abrupt worker restart. A cluster of crashes on a service that handles compressed input is worth treating as attempted exploitation rather than as instability.
  3. New outbound connections from an application host: particularly to addresses outside the service’s normal egress set, shortly after a crash or an unusual request.
  4. Brotli-encoded traffic where it does not belong: requests carrying Content-Encoding: br to endpoints that have no reason to receive compressed bodies, and responses with that encoding arriving from hosts your services do not normally call.
  5. File writes in application directories: a runtime process writing to its own code directory or to a temporary path it does not normally touch.

Mapped to ATT&CK, the chain runs from Exploit Public-Facing Application through Command and Scripting Interpreter to whatever the operator does next. No single item on that list is conclusive. The sequence, joined on the same host and process within a short window, is the finding, which is the same structural point we make about destructive-attack staging in our wiper containment post.

Where the Agent Layer Earns Its Place

A KEV-listed vulnerability in an internet-facing runtime is the sort of event that appears in the next examination. For an institution under SAMA’s Cyber Security Framework or the UAE’s Information Assurance Regulation, the expectation is not that you patched instantly. It is that you can demonstrate the control operated.

  • When you knew: the timestamp the advisory entered your process, not the date it was published.
  • What you found: the inventory result, including the services ruled out and the basis for ruling them out.
  • What you did in the interim: the compensating controls, the detections added, and who approved them.
  • What you observed: exploitation attempts detected and how each was dispositioned, which is far stronger evidence than an assertion that nothing happened.
  • When it closed: per service, with the container rebuild recorded separately from the package upgrade.

That record is a byproduct of a SOC that documents its reasoning. It is a research project for one that does not.

The Evidence a Regulator Will Ask For

A KEV-listed vulnerability in an internet-facing runtime is the sort of event that appears in the next examination. For an institution under SAMA’s Cyber Security Framework or the UAE’s Information Assurance Regulation, the expectation is not that you patched instantly. It is that you can demonstrate the control operated.

  • When you knew: the timestamp the advisory entered your process, not the date it was published.
  • What you found: the inventory result, including the services ruled out and the basis for ruling them out.
  • What you did in the interim: the compensating controls, the detections added, and who approved them.
  • What you observed: exploitation attempts detected and how each was dispositioned, which is far stronger evidence than an assertion that nothing happened.
  • When it closed: per service, with the container rebuild recorded separately from the package upgrade.

That record is a byproduct of a SOC that documents its reasoning. It is a research project for one that does not.

Frequently Asked Questions about the Node.js Brotli RCE

What is CVE-2026-111497?

It is an out-of-bounds write in the Brotli decoder bundled with Node.js, disclosed and patched on 21 October 2026. An invalid block switch in a crafted Brotli stream was reported as a temporary need for more input rather than a format error, so the decoder kept writing past an eight-byte internal buffer. The Node.js advisory says the result can be a crash, disclosure of process memory, or remote code execution.

Which Node.js versions fix it?

Node.js 22.23.4, 24.22.1 and 26.12.1, all released on 21 October 2026. End-of-life lines including 20.x carry the same code and receive no fix, so those systems need migration to a supported line rather than an upgrade within it.

Is my application exposed if I never call the Brotli API?

Possibly. The built-in fetch(), and clients such as axios and got, decompress a response sent with Content-Encoding: br automatically, so any feature that fetches a URL influenced by a user puts you in scope. Framework middleware that unpacks Brotli request bodies does the same for inbound traffic.

Does upgrading Node.js always fix it?

Not on every system. Some Linux distributions build Node.js against the system libbrotli rather than the bundled copy, in which case the fix arrives through the distribution’s brotli package. Containers built before the patch also keep the vulnerable decoder until the image is rebuilt on a patched base.

Is there a workaround if we cannot patch immediately?

The Node.js advisory publishes none. Where a service does not need Brotli, removing br from its accepted encodings and rejecting Brotli-encoded bodies at the edge narrows the exposed path as a temporary compensating control. It reduces exposure rather than removing the defect, so it belongs alongside a rollout plan with a date on it.

Ready to get started?

Contact us to arrange a half day
Managed SOC and XDR workshop in Dubai

Ready to get started?

Contact us to arrange a half day Managed SOC and XDR workshop in Dubai

© 2026 HawkEye – Managed CSOC and XDR powered by DTS Solution. All Rights Reserved.
This is a staging environment