
A decompressor has to answer one question about every byte it reads: is this data broken, or is there just more of it coming? CVE-2026-111497, nicknamed Node2Shell, is what happens when the code answers both with the same word. Bad data gets treated as "send me more", the decoder keeps feeding bytes into a buffer that holds eight, and the write runs off the end.
If you run Node.js, Bun, .NET, Java, Python or Envoy anywhere that touches Brotli, this is for you. It's in CISA's Known Exploited Vulnerabilities (KEV) catalogue, so I'd rather you spend ten minutes checking now than find out from a log later. By the end you'll know what the bug is, which of your services can actually reach it, and what to patch.
What happened
Brotli is a compression format from Google. Browsers ask for it with Accept-Encoding: br, and servers reply with Content-Encoding: br. Runtimes ship the C decoder inside themselves, which is why one bug in one library shows up in so many products.
Node.js released security updates on 21 October 2026, alongside fixed Brotli releases. According to Datadog's write-up, the fix had actually been committed to the upstream Brotli repository on 27 February 2026, which is eight months earlier. A public proof of concept appeared on 22 October. .NET followed with an out-of-band update on 25 October. The CVE record scores it CVSS 4.0 9.2 (Critical), while Node.js itself rates it High. I haven't read the vector string for either score, so I'm not going to quote one.
On exploitation: Datadog states there is confirmed in-the-wild exploitation, and a Viakoo news round-up lists CVE-2026-111497 in KEV from 27 October, also noting Siemens advisories for products that bundle the decoder. Who is exploiting it, and how many victims, isn't public in anything I read. I'd treat any article that names an actor for this one as unverified until a vendor or incident response firm says so.
Eight months in a public repo
Fix committed to the upstream Brotli repository.
Node.js security releases and fixed Brotli versions ship.
Public proof of concept appears. It crashes and leaks a little memory.
.NET out-of-band update: 8.0.33, 9.0.22, 10.0.14.
Listed in CISA KEV, per Viakoo's round-up of the catalogue.
My opinion, and it's only an opinion: the dates are the real story here. A fix sitting in a public repository for months, with no release attached, gives anyone who reads commits a head start over everyone who only reads advisories. I'm not saying that's what happened in this case. I'm saying it's the kind of gap defenders can't see, and that's worth knowing when you decide how fast to patch.
The root cause: one boolean, two meanings
Datadog traces it to a function called DecodeBlockTypeAndLength() in the Brotli decoder. It returns a boolean. Callers read false as "I need more input", but the same false is also used for "this stream is malformed". They can't tell the two apart. On the decoder's fast path, the return value is dropped completely.
Akamai describes the trigger. A stream can signal a block switch that its own structure makes invalid. The decoder should reject that with a permanent format error. Instead it reports a temporary need for more input, so the streaming decoder keeps appending incoming bytes to an eight-byte internal buffer. It never checks the bound, and the write goes past the end. In Datadog's description the overflow spills into adjacent decoder state inside a single heap allocation.
Think of a form with a "page incomplete, please continue" box. If a clerk ticks that box for forms that are actually forged, the forger just keeps handing over more pages. Nothing ever says stop.
The upstream fix gives invalid block switches their own error, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, so the stream is rejected instead of waiting for more. There's also a second path in the same area that retries a forbidden block switch in a loop. That one pins a CPU core, which means a single request can cause denial of service even if nobody ever gets code execution.
Weakness class: out-of-bounds write (CWE-787). In ATT&CK terms the entry point is T1190, Exploit Public-Facing Application, when the decoder sits behind a server that takes request bodies.
How far from a crash to a shell?
I'll be careful here, because the sources disagree on how finished the exploit is. Datadog says the corrupted state controls where the decoder reads and writes next, which gives two building blocks: a repeatable heap read that comes back out through the decompressed response, and a redirected write. In their lab the write hit an unmapped page and crashed the process every time. They list three obstacles to a reliable shell: heap addresses that change on every request, the fact that the corruption bytes are also decoder input, and the managed runtime's allocator and defences. They call the gap engineering effort, and they did not produce a full shell.
Imperva reports getting code execution on the official Node.js 24.22.0 Linux ARM64 image after several attempts, and says it hasn't released the exploit. I haven't reproduced any of this and I won't be publishing exploit steps. The practical reading is that a crash and a memory leak are easy today, and remote code execution has been shown by at least one vendor. Plan for the worst of those.
Which of your services can actually reach it
Per Akamai, a bare Node.js HTTP server does not decode incoming Brotli bodies on its own. You need something that decompresses untrusted data. The paths they list:
zlib.brotliDecompress()orzlib.createBrotliDecompress()given input from outside.- The built-in
fetch(), which decompresses Brotli responses. Here a hostile or compromised server attacks your client, so the exposure is outbound, not inbound. - Express 5 and body-parser 2 parsers (json, raw, text, urlencoded). Their
inflateoption defaults totrue, so a request withContent-Encoding: bris decoded for you.
That last one is the dangerous default. Plenty of teams run an API on Express 5 and have never typed the word Brotli.
Affected and fixed versions
| Product | Affected | Fixed |
|---|---|---|
| Node.js 22, 24, 26 | Before 22.23.4, 24.22.1, 26.12.1 | 22.23.4, 24.22.1, 26.12.1 |
| Google Brotli | 1.0.3 to 1.0.9, 1.1.0, 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
| Python Brotli / brotlicffi | 1.2.0 and earlier | 1.2.1 / 1.2.1.0 |
| Bun | 1.0.21 to 1.4.3 | 1.4.4 |
| Java brotli4j | 1.23.0 and earlier | 1.24.0 |
| .NET 8, 9, 10 | Before the 25 October 2026 update | 8.0.33, 9.0.22, 10.0.14 |
| Envoy | Before the first fix on each branch | 1.36.12, 1.37.8, 1.38.6, 1.39.3, 1.40.0 |
| Istio | Before the first fix on each branch | 1.29.9, 1.30.6, 1.31.2 |
End-of-life Node.js lines (25.x, 23.x, 20.x and older) carry the vulnerable decoder and get no patch. If you're on one, the answer is to upgrade the major version, not to wait.
How to check your own estate
Start with the Node.js version on every host and in every container image:
node -v
node -e "console.log(process.versions.node)"
Compare the result with the table above. One trap to know about: Datadog notes that process.versions.brotli still reports 1.2.0 on the patched Node.js 24.22.1. So don't use it to decide whether you're fixed. Trust the Node.js version number.
Then search your code for the exposed paths:
grep -rn "brotliDecompress\|createBrotliDecompress" --include=*.js --include=*.ts src/
grep -rn "body-parser\|express.json\|express.urlencoded" --include=*.js --include=*.ts src/
Anything that uses Express 5 or body-parser 2 without inflate: false should be treated as exposed. Datadog adds that Java brotli4j bundles its own native build, so patching the operating system's libbrotli doesn't fix a Java service. Rebuild container images and Bun compiled executables as well, because the old decoder is baked in.
Common mistakes when patching this
First, patching the package but not restarting. Datadog says every process has to be restarted, or the old library stays in memory. Second, trusting the interim measures. Setting inflate: false on Express 5 and body-parser 2, or rejecting Content-Encoding: br at the reverse proxy, only closes the inbound request path. It can also break legitimate compressed clients. It does nothing for fetch(), axios, got, node-fetch or Python HTTP clients that decode Brotli responses automatically. Third, assuming a service with no public inbound route is safe. If it calls out to anything you don't control, it can still be hit through the response.
Detecting it
There's no clean log signature in what I read, only behaviour. Datadog suggests watching for:
- Decoder errors that differ between patched and vulnerable builds.
- Worker processes exiting with SIGSEGV (exit code 139) or sitting at 100 percent CPU.
- Unexpected decompressed output.
- Shells or new outbound connections from web workloads.
A web worker that crashes with 139 right after a request carrying Content-Encoding: br is worth an hour of your time, even if it turns out to be a bad client.
Quick recap
- CVE-2026-111497 (Node2Shell) is an out-of-bounds write in the Brotli decoder, scored CVSS 4.0 9.2, in CISA KEV.
- The decoder returned one value for "malformed" and "need more input", so invalid streams kept writing into an eight-byte buffer.
- Reachable through
zlib.brotliDecompress, outboundfetch(), and Express 5 or body-parser 2 request bodies by default. - Fixed in Node.js 22.23.4, 24.22.1 and 26.12.1. Other runtimes have their own fixed versions, and end-of-life Node lines get none.
- Patch, rebuild images, restart everything. Proxy blocks cover only inbound requests.
Check this today
Run node -v across your hosts and images, then grep your services for Express 5 without inflate: false. Write down every service that makes outbound calls to third parties, because those need the patch even if nothing points at them from the internet.
If you're testing this in your own lab, or you've found a fleet with a pattern I haven't covered, I'd be glad to compare notes on LinkedIn.
References
- Datadog Security Labs, Node2Shell: Reproducing CVE-2026-111497 (root cause, versions, timeline, mitigations)
- Akamai, Inside Node2Shell (trigger conditions, exposed code paths, fixed Node.js versions)
- Viakoo, Daily OT Security News, 31 October 2026 (KEV listing date, Siemens advisories)
- CISA Known Exploited Vulnerabilities Catalog (the authoritative KEV record)
FAQ
What is CVE-2026-111497?
CVE-2026-111497, nicknamed Node2Shell, is an out-of-bounds write in the Google Brotli decoder that Node.js and several other runtimes bundle. Malformed compressed data can overflow an internal buffer. The CVE record scores it CVSS 4.0 9.2. It is in CISA's Known Exploited Vulnerabilities catalogue, and fixed Node.js releases shipped on 21 October 2026.
Is my Node.js server exposed if it never uses Brotli?
A bare Node.js HTTP server does not decode incoming Brotli bodies on its own. You are exposed when your code decompresses untrusted data, for example with zlib.brotliDecompress, with fetch() reading a hostile server's response, or with Express 5 and body-parser 2, which decode Content-Encoding br request bodies by default.
Why does process.versions.brotli still say 1.2.0 after patching?
Datadog notes that process.versions.brotli still reports 1.2.0 on the patched Node.js 24.22.1. So that value cannot tell you whether you are fixed. Check the Node.js version itself instead: 22.23.4, 24.22.1 or 26.12.1 and later contain the fix, and end-of-life lines have no patch.
Can I block it at the reverse proxy instead of patching?
Only partly. Rejecting Content-Encoding br requests, or setting inflate to false in Express 5 and body-parser 2, closes the inbound request path. It does not cover outbound calls, where fetch(), axios and Python clients decode Brotli responses automatically. Patching and restarting every process is the only complete fix.
Related reading
- CVE-2026-110447: Nextcloud Collabora Injection (last week's unauthenticated bug in a bundled component)
- CVE-2026-8451: NetScaler CitrixBleed Overread (another memory-safety bug that leaks process memory)
- vCenter Wrote a Log Line Into /etc/cron.d, and Cron Ran It as Root (patch-to-exploit in days)
- Browse the whole Threats & Exploits track