blog / · updated · 4 min read
Node.js October 2026 security release: Brotli RCE and certificate checks fooled by odd IPs
Ten fixes, four high severity. The one that matters most is an out-of-bounds write in the Brotli decoder, reachable from fetch() and Express 5 by default. What to patch, what to switch off and how to find exposed services.
In one paragraph
On 21 October 2026 Node.js shipped 22.23.4, 24.22.1 and 26.12.1, fixing ten vulnerabilities. The headline is CVE-2026-111497, a memory corruption bug in the bundled Brotli decompressor that researchers nicknamed Node2Shell. Any app that decompresses Brotli data from someone it doesn’t trust is exposed, and two very common paths do it by default. CISA added it to the KEV catalog on 27 October. Node.js 20 and every other end-of-life line get no fix.
The Brotli bug (CVE-2026-111497)
Brotli is a compression format, signalled on the web by Content-Encoding: br. Node’s decoder treated one kind of malformed input as “I need more data” instead of “this is broken”, and kept appending input into an 8-byte buffer past its end. The result can be a crash, a memory leak or remote code execution. Akamai rates it 9.2 critical; Node.js rates it high.
You are exposed if untrusted Brotli data reaches the decoder through:
| Path | Exposed by default? |
|---|---|
zlib.brotliDecompress() / createBrotliDecompress() on user data | Yes, if you do it |
fetch() to a server you don’t control | Yes. Responses are decompressed automatically, so a malicious or compromised server can attack the client. |
Express 5 / body-parser 2 (json, raw, text, urlencoded) | Yes. inflate is on by default and br request bodies are decoded. |
Bare node:http server | No, unless your code decodes bodies itself |
Reports disagree on whether the bug has been exploited against real deployments. CISA’s KEV listing means you should assume it has.
The other high-severity fixes
Three of them are about TLS certificate checks and DNS being fooled by unusual ways of writing an address:
- CVE-2026-105483: fullwidth digits such as
127.0.0.1are not recognised as an IP address, so a certificate for a DNS name can authenticate an IP endpoint. - CVE-2026-106069: leading-zero addresses such as
0127.0.0.1are read as octal for validation and as decimal for connecting, so the wrong certificate can be accepted. - CVE-2026-105482:
dns.resolve*()anddns.reverse()cut hostnames at a NUL byte and can return records for a different name.
They matter mostly where an attacker can influence the hostname or URL your service connects to, for example webhooks, link previews or SSRF-prone features.
The remaining fixes are medium and low severity: keep-alive response smuggling, repeated randomUUID() values in startup snapshots and SEAs, and gaps in the experimental Permission Model.
What to do
- Upgrade to 22.23.4, 24.22.1 or 26.12.1 or later. There is no other complete fix.
- Move off end-of-life lines (20.x, 23.x, 25.x and older).
- Rebuild images that use
node:base images, and check desktop apps and tools that bundle their own Node. - If you can’t patch today, stop accepting compressed request bodies:
// Express 5 / body-parser 2: reject compressed bodies with HTTP 415
app.use(express.json({ inflate: false }));
app.use(express.urlencoded({ extended: false, inflate: false }));
or at the reverse proxy:
if ($http_content_encoding ~* "br") { return 415; }
There is no equivalent switch for fetch(), so limit outbound connections from unpatched hosts to the services they actually need.
Finding exposed Node.js
Versions on a host:
node -v
which -a node nodejs 2>/dev/null
Container images built on Node:
docker image ls --format '{{.Repository}}:{{.Tag}}' | grep -iE '(^|/)node:|node'
for i in $(docker image ls -q | sort -u); do
v=$(docker run --rm --entrypoint node "$i" -v 2>/dev/null) && echo "$i $v"
done
Apps that decode compressed bodies by default:
npm ls express body-parser 2>/dev/null | grep -E 'express@5|body-parser@2'
Requests trying the Brotli path, if your proxy logs request headers:
grep -iE 'content-encoding: ?br' /var/log/nginx/*.log
Post-exploitation: Node spawning shells (Microsoft Defender for Endpoint, KQL). Build tools and npm scripts spawn shells all the time, so baseline first and focus on servers:
DeviceProcessEvents
| where Timestamp > datetime(2026-10-21)
| where InitiatingProcessFileName in~ ("node", "node.exe")
| where FileName in~ ("sh", "bash", "dash", "cmd.exe", "powershell.exe", "curl", "wget", "nc")
| where InitiatingProcessCommandLine !has "npm" and InitiatingProcessCommandLine !has "yarn"
| summarize Count = count(), Cmds = make_set(ProcessCommandLine, 10) by DeviceName, InitiatingProcessCommandLine