The node:zlib Brotli decoder had an out-of-bounds write that allows a crash, memory disclosure or code execution on malformed data; the fixed versions are 26.12.1, 24.22.1 and 22.23.4, lines 20, 23 and 25 get no fix, and the flaw is already in CISA's known exploited vulnerabilities catalog.
1. What happened#
On October 21, 2026 the Node.js project published security releases for its 22, 24 and 26 lines. The most serious of the ten vulnerabilities fixed is CVE-2026-111497, an out-of-bounds write in node:zlib's Brotli decoder. The project rates it high; Google, as the numbering authority, scores it 9.2 (critical) on CVSS v4.0, according to Rapid7. The press dubbed it Node2Shell or Broccoli2Shell.
| Node.js line | Fixed version | Status |
|---|---|---|
| 26.x | 26.12.1 | Supported |
| 24.x | 24.22.1 | Supported |
| 22.x | 22.23.4 | Supported |
| 20.x, 23.x and 25.x | None | Unsupported: affected and unfixed; you must migrate |
It is not just theory. According to Help Net Security, public exploits are on GitHub, Imperva demonstrated code execution against the official Node.js 24.22.0 image for Linux arm64 and ELLIO sees real exploitation traffic. Rapid7 reports that CISA added the flaw to its known exploited vulnerabilities catalog on October 27.
Documentation: Node.js: October 21, 2026 security releases ↗
2. How the flaw works#
The decoder treats an invalid block switch as a temporary need for more input rather than a permanent format error. It then keeps appending input to an internal 8-byte buffer and writes past its bounds. To trigger it, an attacker must get your application to decompress with node:zlib Brotli data that the attacker controls.
This precision matters: the Node.js page names only node:zlib. It does not say whether HTTP Content-Encoding: br handling or fetch are affected, so do not assume so. And according to Rapid7, the decoder merely existing in your environment does not confirm exposure: you need a path that processes attacker-controlled compressed data.
Documentation: Rapid7: CVE-2026-111497, out-of-bounds write in the Node.js Brotli decoder ↗
3. Who is affected#
Lines 26.x, 24.x and 22.x before the fixed versions are affected. Lines 20.x, 23.x and 25.x, which are no longer supported, are also affected and will get no fix, according to Help Net Security.
The problem originates in Google's Brotli library, so it is not only Node.js. Tenable, cited by Help Net Security, includes .NET, Bun, Envoy, Istio, Python HTTP clients, FFmpeg, Apache Arrow and ClickHouse. Affected versions and default exposure vary: check each vendor's advisory. The fixed library is 1.2.1, 1.1.1 or 1.0.10 depending on the branch.
Documentation: Help Net Security: the Brotli bug reaches far beyond Node.js ↗ · Node.js: release schedule ↗
4. How to check if you are affected#
Start with the version: the one that really runs, not the one in your terminal. In containers, the image that is deployed. The script compares a version with the fixed ones and searches your code for uses of the Brotli decoder, which is the vulnerable part.
# The Node version that actually runs: on the server, in the container and in CI
node -v
docker ps --format "{{.Names}} {{.Image}}"
docker exec NAME node -v
# After updating: rebuild the image (with a moving tag like node:22-alpine, pull the new base)
docker build --pull --no-cache -t my-app .const FIXED = { 22: [22, 23, 4], 24: [24, 22, 1], 26: [26, 12, 1] }; // first fixed version per line
const parse = version => String(version).replace(/^v/, "").split(".").map(Number);
function status(version) {
const [major, minor, patch] = parse(version);
const fixed = FIXED[major];
if (!fixed) return major > 26 ? "line newer than the fixed ones: check the advisory" : "NO FIX: unsupported line; migrate to 22, 24 or 26";
const [, fixedMinor, fixedPatch] = fixed;
const ok = minor > fixedMinor || (minor === fixedMinor && patch >= fixedPatch);
return ok ? "fixed" : `VULNERABLE: update to ${fixed.join(".")}`;
}
// Search your code for node:zlib's Brotli decoder (compression is not the vulnerable part)
const BROTLI_DECODER = /\b(?:createBrotliDecompress|brotliDecompress(?:Sync)?)\b/g;
const usesBrotli = code => (code.match(BROTLI_DECODER) ?? []).length;
for (const v of ["v22.23.3", "v22.23.4", "v24.22.0", "v24.22.1", "v26.12.0", "v26.13.0", "v20.19.0", "v25.1.0"]) console.log(v, "->", status(v));
console.log(usesBrotli("zlib.brotliDecompressSync(body)"), usesBrotli('const { createBrotliDecompress } = require("node:zlib")'), usesBrotli("zlib.brotliCompressSync(body)"), usesBrotli("gzip"));A nonzero search result does not mean you have a problem: it means you must check whether that path receives data from users or from servers you do not control.
5. How to patch#
- Update to 26.12.1, 24.22.1 or 22.23.4 according to your line.
- Rebuild container images from the new base and redeploy. If you use a moving tag such as node:22-alpine, force the download (docker build --pull) and confirm the version inside the container.
- Restart the services: a process that is already running keeps the old version.
- Check packages that bring their own Brotli: Bun, Python's brotli and brotlicffi packages, .NET, Envoy or Istio, as applicable.
- If you are on 20.x, 23.x or 25.x, plan the migration to a supported line; there will be no fix.
At Codifly, the site runs on Node.js 22.23.4, which already includes the fix: we checked it inside the production container.
6. If you cannot patch today#
Node.js published no workaround: its only guidance is to update. Rapid7 suggests, as a temporary measure, restricting or disabling the application paths that decompress untrusted Brotli.
As our own criterion, not from the advisories: check where your application accepts compressed bodies or files and limit who can send them while you update.
7. The other nine vulnerabilities in the same release#
The October 21 release fixes ten vulnerabilities. The table summarizes them according to the Node.js page; if you use TLS verification against IP addresses, DNS resolution with user-provided names or the permission model, read them too.
| CVE | Severity | Component | What happens |
|---|---|---|---|
| CVE-2026-111497 | High | node:zlib Brotli decoder | Out-of-bounds write on malformed data: crash, memory disclosure or code execution |
| CVE-2026-105482 | High | DNS (dns.resolve*, dns.reverse) | NUL bytes in hostnames are silently truncated |
| CVE-2026-105483 | High | TLS hostname verification | Fullwidth-digit IPs are not recognized as IPs (26.x and 24.x) |
| CVE-2026-106069 | High | TLS hostname verification | Leading-zero IPv4 is read differently than the system resolver does |
| CVE-2026-105481 | Medium | HTTP Agent keep-alive | A response received before the request was sent can be accepted as the new request's response |
| CVE-2026-106072 | Medium | TLS hostname verification | Non-canonical IPv4 forms let a DNS SAN authenticate an IP endpoint |
| CVE-2026-106074 | Medium | crypto.randomUUID(), randomUUIDv7(), randomInt() | Snapshots and SEAs repeat the same values in every process |
| CVE-2026-106070 | Low | zlib.ZipFile (26.x only) | After an invalid header, a later read can return attacker-controlled bytes |
| CVE-2026-106071 | Low | Permission Model (26.x only) | Wildcards like /etc/passwd.* also grant /etc/passwd |
| CVE-2026-106073 | Low | Permission Model, fs.realpathSync() | Lets code probe paths outside the permitted directories |
8. Keep your numbers current#
The versions and identifiers are those published through October 28, 2026. Exploitation moves fast: check the Node.js advisory and the CISA catalog again.
- Subscribe to the nodejs-sec list.
- Keep an inventory of the Node.js version of each service and image.
- Automate an alert when a version falls below the fixed one.
Sources and scope
Documentation checked on October 28, 2026. Examples and decision criteria are editorial proposals; adapt them to your application's contract and validate them in an authorized test environment.