CVE
CVE-2026-111497, CVE-2026-114148, CVE-2026-115936, CVE-2026-116566, CVE-2026-105482, CVE-2026-105483, CVE-2026-106069, CVE-2026-106072, CVE-2026-105481, CVE-2026-106074, CVE-2026-106070, CVE-2026-106071, CVE-2026-106073
CWE
CWE-787, CWE-170, CWE-295, CWE-297, CWE-330
Affected Surface
- Node.js 26.x before `26.12.1`, 24.x LTS before `24.22.1`, and 22.x LTS before `22.23.4` (plus end-of-life Node.js 20.x and older lines, which receive no patch)
- Upstream Google `brotli` (`libbrotli`) before `1.2.1`, `1.1.1`, and `1.0.10`, .NET 8 before `8.0.33`, .NET 9 before `9.0.22`, .NET 10 before `10.0.14`, and .NET 11 before `RC3` (`GHSA-p3jp-9v83-76w9`), PHP 8.2 to 8.5 Windows builds before `8.5.12-1`, `8.4.27-1`, `8.3.36-1`, and `8.2.35-1`, Bun before `1.4.4`, PyPI `Brotli` (`<= 1.2.0`, fixed in `1.2.1`) and `brotlicffi` (`<= 1.2.0.2`, fixed in `1.2.1.0`), Envoy before `1.40.0`, `1.39.3`, `1.38.6`, and `1.37.8`, Istio before `1.31.2` and `1.30.6`, Perl CPAN `IO::Compress::Brotli` (`>= 0.004001, < 0.023`, `CVE-2026-115936`), and native consumers (`git-remote-https` via `libcurl`, FFmpeg via `libjxl` `brob` boxes, ClickHouse, and Apache Arrow `pyarrow`)
- Node.js servers (including Express 5 and `body-parser` 2.x), proxies, and SSR frameworks that decompress `Content-Encoding: br` request bodies or call `zlib.brotliDecompress*` / `zlib.createBrotliDecompress()`, as well as Node.js, Bun, Envoy, and Python (`urllib3`, `requests`, `httpx[brotli]`, `aiohttp`, `Scrapy`) applications that fetch Brotli-compressed responses from untrusted remote URLs
- Applications using `dns.resolve*()` after suffix-based hostname validation, `tls.connect()` / `https.request()` with user-influenced hostnames, or V8 startup snapshots (`--build-snapshot`) and Single Executable Applications (SEAs)
Update, 29 October 2026: On 29 October (17:46 UTC), the Python Security Response Team announced CVE-2026-116566 (oss-security/2026/10/29/4, python/cpython#160898, PR #160899) for the exact CPython counterpart of Node.js CVE-2026-105482: socket.getaddrinfo() previously accepted embedded null bytes (\x00) inside both the host and port parameters, allowing C string truncation inside the underlying OS resolver to resolve "attacker.example\x00.trusted.corp" after passing Python-level suffix checks.
Update, 28 October 2026: On 27 October 2026, CISA added CVE-2026-111497 (Google Brotli Unchecked Return Value Vulnerability) to its Known Exploited Vulnerabilities (KEV) Catalog alongside Adobe Commerce CVE-2026-48356, citing evidence of active exploitation in the wild and setting a federal remediation due date of 10 November 2026 under BOD 26-04. FIRST EPSS for CVE-2026-111497 has reached 55.48% (99.03rd percentile). In addition, Go’s pure-Go Brotli port github.com/andybalholm/brotli shipped v1.2.7 (commit 33c2a98842) to reject streams with invalid block-switch types early, and Wiz Research reported that 91% of cloud environments carry resources vulnerable to CVE-2026-111497.
Update, 26 October 2026: Between 25 and 26 October, CVE-2026-111497 (also tracked as Brotli2Shell / Broccoli2Shell) triggered emergency out-of-band releases across .NET and PHP Windows builds and was demonstrated in non-HTTP file parsers:
- Microsoft .NET out-of-band updates (
8.0.33,9.0.22,10.0.14, and11 RC3,GHSA-p3jp-9v83-76w9): On 25 October, Microsoft released emergency updates for .NET 8, 9, 10, and 11 RC3 because the native Brotli decoder compiled into the .NET runtime (System.IO.Compression.Brotli,BrotliStream,BrotliDecoder, ASP.NET CoreAddRequestDecompression, andHttpClientAutomaticDecompression = DecompressionMethods.Brotli) carried the vulnerableDecodeBlockTypeAndLength()path. The same out-of-band release also patchesCVE-2026-114148(unsafe Linux.tar.gzownership metadata) and re-releases complete fixes forCVE-2026-70197andCVE-2026-102870. Self-contained .NET deployments and container images must be rebuilt and redeployed. - PHP for Windows
-1rebuilds (8.5.12-1,8.4.27-1,8.3.36-1, and8.2.35-1): On 26 October, the PHP project rebuilt its official Windows zip distributions after discovering that the 21 October Windows releases (8.5.12through8.2.35) bundledbrotli 1.2.0insidephp_curl.dll(reachable wheneverCURLOPT_ENCODINGis set to""or"br", which PHP’s Guzzle HTTP client enables by default). Becausephp -vstill prints8.5.12/8.4.27/8.3.36/8.2.35, Windows administrators must verify the-1archive filename (php-8.5.12-1-nts-Win32-vs17-x64.zip) or check thelibbrotlidec.dll/php_curl.dllSHA-256 digest. - Non-HTTP and native file-parser vectors (FFmpeg
libjxl, ClickHouse, Apache Arrow, andgit-remote-https): On 25 October, JFrog Security Research demonstrated instruction-pointer control (0x414141414141crash on decoder teardown) in FFmpeg via a crafted JPEG XL (.jxl) image whosebrobExif metadata box feeds200010414141fcffff07002228f00b60f7f41f000000intolibjxl->BrotliDecoderDecompressStream(), a single-requestSIGSEGVcrash in ClickHouse viaContent-Encoding: br(fixed inClickHouse/ClickHouse#127419), and an infinite CPU loop in Apache Arrow (pyarrow) via a 534-byte Brotli-compressed Parquet page (apache/arrow#55166). On 26 October, Harry Sintonen noted onoss-security(2026/10/26/16) thatgit-remote-https(git clone https://...) setsCURLOPT_ACCEPT_ENCODING(http.c:2284) and therefore exposesgitclients whenever systemlibcurlis linked against an unpatchedlibbrotlidec.
Update, 24 October 2026: On 23 and 24 October, the downstream blast radius of CVE-2026-111497 (now tracked across vendors as HTTP2Shell) widened beyond Node.js, Bun, and Python:
- Envoy and Istio: Envoy published
GHSA-3wvj-j6qm-62mrand shipped1.40.0,1.39.3,1.38.6, and1.37.8(plus1.36.12on the unsupported1.36branch), while Istio shipped1.31.2and1.30.6(plus end-of-life1.29.9) to update the bundledlibbrotliused by Envoy’s Brotli HTTP decompressor filter. - Perl CPAN (
CVE-2026-115936): The CPAN Security Group assignedCVE-2026-115936(oss-security/2026/10/23/31) toIO::Compress::Brotliversions0.004001before0.023. BecauseIO::Compress::Brotlicompiles its own bundled Cbrotlisubmodule (1.0.4through1.2.0) rather than linking the OS library, upgrading systemlibbrotlidoes not patch Perl callers usingunbroordecompress; upgrade toIO-Compress-Brotli0.023(commit39a62a4454). - Python advisory mappings: On 24 October, the PyPI security team and GitHub Advisory Database published
PYSEC-2026-4322(Brotli <= 1.2.0),PYSEC-2026-4323(brotlicffi <= 1.2.0.2), and updatedGHSA-9v8h-qqgm-4m35, allowingpip-auditand Dependabot to flag vulnerable Python environments automatically. - Origin of the February 2026 commit: OpenAI disclosed that its researchers reported the out-of-bounds write to Google in February 2026 as a bidirectional RCE vector affecting both servers and HTTP clients. Although commit
0ed4268a4401landed ingoogle/brotlimasterin February 2026, no upstream release was tagged untilv1.2.1,v1.1.1, andv1.0.10on 21 October 2026.
Update, 23 October 2026: On 22 October, Imperva Red Team published its Node2Shell research demonstrating remote code execution against the official, unmodified node:24.22.0 Linux arm64 container image via both inbound Content-Encoding: br request bodies (express@5.0.0 to 5.3.0 and body-parser@2.0.0 to 2.3.0, which automatically inflate Brotli bodies in Express 5) and outbound fetch() responses. Google CNA and NVD also scored CVE-2026-111497 at CVSS v4.0 9.2 (Critical). Because the flaw lives in Google’s C brotli decoder (c/dec/decode.c), it extends beyond Node.js: Bun shipped v1.4.4 on 22 October (oven-sh/bun#46352), and on 22 and 23 October the maintainers of the PyPI Brotli (<= 1.2.0 -> 1.2.1) and brotlicffi (<= 1.2.0.2 -> 1.2.1.0) packages quietly published updated wheels compiling the patched C source without release notes or PyPA/GHSA advisory records. Any Python environment with Brotli or brotlicffi installed causes urllib3, requests, httpx, aiohttp, and Scrapy to advertise Accept-Encoding: br and feed remote HTTP responses into the same C decoder.
TL;DR
On 21 October 2026 (followed by oss-security and container image rollouts on 22 October), the Node.js project released v26.12.1 (Current), v24.22.1 (Active LTS), and v22.23.4 (Maintenance LTS) to fix ten security vulnerabilities alongside the eMudhra intermediate CA public-key blocklist. Google tagged upstream brotli v1.2.1, v1.1.1, and v1.0.10 the same day.
Four of the ten Node.js CVEs are rated High severity, and two Medium-severity fixes close subtle runtime primitives:
CVE-2026-111497(High): An out-of-bounds write in the C Brotli decoder (deps/brotli/c/dec/decode.c) permits remote code execution when decompressing crafted Brotli streams. On the fast (“unsafe”) decode path used when sufficient input bytes are buffered,BROTLI_SAFE()ignored the return value ofDecodeCommandBlockSwitch(),DecodeDistanceBlockSwitch(), andDecodeLiteralBlockSwitch(), allowing decoding to continue with corrupted block-switch state whenmax_block_type <= 1. This is reachable both on inboundContent-Encoding: brrequests and on outboundfetch()calls, because Node’s built-infetch()(Undici) automatically decodes Brotli responses from remote servers.CVE-2026-105482(High):dns.resolve*()andresolver.setLocalAddress()validated inputs withvalidateString()instead ofvalidateStringWithoutNullBytes(). Becausec-arestreats the hostname as a NUL-terminated C string, passing'attacker.example\0.trusted.corp'passes a JavaScript.endsWith('.trusted.corp')check and then resolvesattacker.example.CVE-2026-106069andCVE-2026-105483(High, plusCVE-2026-106072Medium):tls.checkServerIdentity()normalized hostnames withurl.domainToASCII()in ways that disagreed withnet.isIP()and OS resolvers. Fullwidth IPv4 digits (\uFF11\uFF12\uFF17.0.0.1), ideographic dots (127\u30020.0.1), and non-canonical IPv4 strings (0127.0.0.1,0x7f.0.0.1,2130706433,127.1) either fell through to DNS-name / Common Name matching or validated the certificate against87.0.0.1(127in octal) while the OS connected to127.0.0.1.CVE-2026-106074(Medium): When building a V8 startup snapshot (--build-snapshot) or Single Executable Application (SEA), any call tocrypto.randomUUID(),crypto.randomUUIDv7(), orcrypto.randomInt()during snapshot creation left pre-generated entropy batches insiderandomCacheanduuidData. Every process spawned from that snapshot or SEA binary replayed the exact same UUIDs and random integers.
Upgrade to Node.js 26.12.1, 24.22.1, or 22.23.4 (and migrate off end-of-life Node.js 20.x). Because Node cherry-picked the Brotli patch without bumping process.versions.brotli, verify process.versions.node rather than the embedded Brotli version string.
Affected packages and projects
| Ecosystem | Package / runtime | CVE | Severity | Affected versions | Fixed versions | Component |
|---|---|---|---|---|---|---|
| Node.js | node | CVE-2026-111497 | High (CVSS v4.0 9.2 Critical) | 22.x < 22.23.4, 24.x < 24.22.1, 26.x < 26.12.1, <= 20.x (EOL) | 22.23.4, 24.22.1, 26.12.1 | deps/brotli/c/dec/decode.c (zlib Brotli decompression, Express 5 / body-parser 2.x, and fetch()) |
| C / C++ / distros | google/brotli (libbrotli) | CVE-2026-111497 | CVSS v4.0 9.2 Critical | < 1.0.10, 1.1.0, 1.2.0 | 1.0.10, 1.1.1, 1.2.1 | c/dec/decode.c (DecodeBlockTypeAndLength / ProcessCommandsInternal) |
| Bun | bun | CVE-2026-111497 | CVSS v4.0 9.2 Critical | < 1.4.4 (<= 1.4.2 bundles brotli 1.1.0; 1.4.3 bundles 1.2.0) | 1.4.4 | Vendored brotli C decoder (fetch(), node:zlib, DecompressionStream("brotli")) |
| PyPI | Brotli | CVE-2026-111497 (PYSEC-2026-4322) | CVSS v4.0 9.2 Critical | <= 1.2.0 | 1.2.1 | CPython C extension compiled from c/dec/decode.c (auto-used by urllib3, requests, httpx, aiohttp) |
| PyPI | brotlicffi | CVE-2026-111497 (PYSEC-2026-4323) | CVSS v4.0 9.2 Critical | <= 1.2.0.2 | 1.2.1.0 | CFFI binding compiled from vendored c/dec/decode.c |
| Envoy / Istio | envoy, istio | CVE-2026-111497 (GHSA-3wvj-j6qm-62mr) | CVSS v4.0 9.2 Critical | Envoy < 1.37.8, 1.38.x < 1.38.6, 1.39.x < 1.39.3, 1.40.x < 1.40.0; Istio < 1.30.6, 1.31.x < 1.31.2 | Envoy 1.37.8, 1.38.6, 1.39.3, 1.40.0; Istio 1.30.6, 1.31.2 | Envoy Brotli HTTP decompressor filter (libbrotli) |
| .NET / NuGet | Microsoft.NETCore.App, Microsoft.DotNet.ILCompiler | CVE-2026-111497 (GHSA-p3jp-9v83-76w9) | CVSS 8.1 High | 8.0.x < 8.0.33, 9.0.x < 9.0.22, 10.0.x < 10.0.14, 11.0 < RC3 | 8.0.33, 9.0.22, 10.0.14, 11.0.0-rc.3 | Native libbrotli compiled into .NET runtime (BrotliStream, BrotliDecoder, HttpClient, ASP.NET Core AddRequestDecompression) |
| PHP (Windows) | php Windows x64/x86 builds | CVE-2026-111497 | CVSS v4.0 9.2 Critical | 8.2 <= 8.2.35, 8.3 <= 8.3.36, 8.4 <= 8.4.27, 8.5 <= 8.5.12 | 8.2.35-1, 8.3.36-1, 8.4.27-1, 8.5.12-1 | Bundled brotli 1.2.0 in Windows php_curl.dll (CURLOPT_ENCODING / Guzzle) |
| CPAN (Perl) | IO::Compress::Brotli | CVE-2026-115936 | High | >= 0.004001, < 0.023 | 0.023 | Bundled brotli submodule (1.0.4..1.2.0) compiled into module (unbro / decompress) |
| Node.js | node | CVE-2026-105482 | High | 22.x < 22.23.4, 24.x < 24.22.1, 26.x < 26.12.1 | 22.23.4, 24.22.1, 26.12.1 | lib/internal/dns/* (dns.resolve*, Resolver.setLocalAddress) |
| Node.js | node | CVE-2026-105483 | High | 24.x < 24.22.1, 26.x < 26.12.1 | 24.22.1, 26.12.1 | lib/tls.js (checkServerIdentity fullwidth/ideographic IP regression) |
| Node.js | node | CVE-2026-106069, CVE-2026-106072 | High / Medium | 22.x < 22.23.4, 24.x < 24.22.1, 26.x < 26.12.1 | 22.23.4, 24.22.1, 26.12.1 | lib/tls.js (checkServerIdentity non-canonical IPv4 parsing) |
| Node.js | node | CVE-2026-106074 | Medium | 22.x < 22.23.4, 24.x < 24.22.1, 26.x < 26.12.1 | 22.23.4, 24.22.1, 26.12.1 | lib/internal/crypto/random.js (V8 startup snapshots and SEAs) |
| Node.js | node | CVE-2026-105481 | Medium | 22.x < 22.23.4, 24.x < 24.22.1, 26.x < 26.12.1 | 22.23.4, 24.22.1, 26.12.1 | lib/_http_agent.js (keep-alive socket processing pre-request data) |
| Node.js | node | CVE-2026-106070, 106071, 106073 | Low | 26.x < 26.12.1 (106073 also 22.x, 24.x) | 22.23.4, 24.22.1, 26.12.1 | zlib ZipFile header cache and --permission radix/realpath checks |
Technical analysis
1. CVE-2026-111497: ignored block-switch errors on Brotli’s fast decode path
Reported by Khai Tran and Sylvie Mayer (OpenAI Codex Security), CVE-2026-111497 sits in deps/brotli/c/dec/decode.c (upstream commit 0ed4268a4401abcb4472096340477cf4af7d1a2b, cherry-picked into Node.js in commit a6040b50d9).
Brotli’s command loop ProcessCommandsInternal(int safe, BrotliDecoderState* s) is compiled twice: once with safe = 1 (used near the end of an input chunk when the bit reader might run out of bytes) and once with safe = 0 (the fast path used when at least 28 bytes of input remain buffered). Before the fix, both paths wrapped helper calls in the BROTLI_SAFE(METHOD) macro:
/* NB: METHOD should return BROTLI_FALSE only in case there is not enough input;
in case of "unsafe" execution, when input is guaranteed to be sufficient,
result is ignored. */
#define BROTLI_SAFE(METHOD) \
{ \
if (safe) { \
if (!Safe##METHOD) { \
result = BROTLI_DECODER_NEEDS_MORE_INPUT; \
goto saveStateAndReturn; \
} \
} else { \
METHOD; \
} \
}
Look at the comment and the else branch: when safe == 0, BROTLI_SAFE() assumes METHOD cannot fail for any reason other than running out of input, so it calls METHOD as a void statement and discards the result.
However, the three block-switch helpers (DecodeLiteralBlockSwitch, DecodeCommandBlockSwitch, and DecodeDistanceBlockSwitch) call DecodeBlockTypeAndLength(safe, s, tree_type), which can fail on a format error even when plenty of input bytes are buffered:
static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
int safe, BrotliDecoderState* s, int tree_type) {
brotli_reg_t max_block_type = s->num_block_types[tree_type];
...
if (max_block_type <= 1) {
return BROTLI_FALSE;
}
When a crafted stream causes s->block_length[tree_type] to hit 0 while max_block_type <= 1:
- On the
safe == 1path,DecodeBlockTypeAndLength()returnedBROTLI_FALSE, whichBROTLI_SAFE()misinterpreted asBROTLI_DECODER_NEEDS_MORE_INPUTinstead of a fatal stream error. - On the
safe == 0fast path,DecodeCommandBlockSwitch(s),DecodeDistanceBlockSwitch(s), andDecodeLiteralBlockSwitch(s)returnedvoid, silently ignoring theBROTLI_FALSEreturn value without updatings->block_length[tree_type],s->htree_command, ors->dist_context_map_slice. Execution then jumped straight back intoCommandBeginorCommandInnerwith desynchronized Huffman table pointers and block lengths, leading to an out-of-bounds write.
The patch introduces a dedicated error code BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17), changes DecodeBlockTypeAndLength() and all three *BlockSwitch* functions to return BrotliDecoderErrorCode, and replaces BROTLI_SAFE at those three call sites with BROTLI_SAFE_WITH_STATUS:
--- a/deps/brotli/c/dec/decode.c
+++ b/deps/brotli/c/dec/decode.c
@@ -1187,7 +1187,7 @@ static BROTLI_INLINE BrotliDecoderErrorCode DecodeBlockTypeAndLength(
brotli_reg_t* ringbuffer = &s->block_type_rb[tree_type * 2];
brotli_reg_t block_type;
if (max_block_type <= 1) {
- return BROTLI_FALSE;
+ return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH;
}
...
+#define BROTLI_SAFE_WITH_STATUS(METHOD) \
+ { \
+ BrotliDecoderErrorCode status; \
+ if (safe) { \
+ status = Safe##METHOD; \
+ } else { \
+ status = METHOD; \
+ } \
+ if (status != BROTLI_DECODER_SUCCESS) { \
+ result = status; \
+ goto saveStateAndReturn; \
+ } \
+ }
2. CVE-2026-105482: dns.resolve*() NUL-byte truncation in c-ares
In Node.js, dns.lookup() already rejected strings containing NUL bytes (\0), but the c-ares-backed resolver methods (dns.resolve(), dns.resolve4(), dns.resolve6(), dns.resolveTxt(), dns.resolveSrv(), and their dns.promises equivalents) and resolver.setLocalAddress() only called validateString(name, 'name').
When JavaScript passed 'evil.example\0.trusted.corp' into the C++ QueryReqWrap binding, c-ares read the buffer as a NUL-terminated C string and queried the DNS server for evil.example. Any SSRF filter or domain allowlist that validated hostname.endsWith('.trusted.corp') before calling dns.resolve4(hostname) could be bypassed. Commit 6b78ea171d replaces validateString with validateStringWithoutNullBytes across lib/internal/dns/callback_resolver.js, lib/internal/dns/promises.js, and lib/internal/dns/utils.js.
3. CVE-2026-106069, CVE-2026-105483, and CVE-2026-106072: non-canonical IPv4 and fullwidth digit mismatches in tls.checkServerIdentity()
Per RFC 6125 and RFC 2818, when a TLS client connects to an IP address, the server certificate must be matched against IP Address: Subject Alternative Names (SANs), never against DNS: SANs or the subject Common Name (CN).
Two bugs in lib/tls.js broke that invariant:
- Fullwidth digits and ideographic dots (
CVE-2026-105483, affecting 24.x and 26.x): An earlier IPv6 fix changedcheckServerIdentity()to testif (net.isIP(hostname))on the rawhostnamebefore IDNA normalization (url.domainToASCII(hostname)). Becausenet.isIP('\uFF11\uFF12\uFF17.0.0.1')andnet.isIP('127\u30020.0.1')return0,checkServerIdentity()treated them as DNS names, normalized them viadomainToASCII()into'127.0.0.1', and matched them againstDNS:127.0.0.1,DNS:*.0.0.1, orCN=127.0.0.1. - Non-canonical octal, hex, and integer IPv4 hosts (
CVE-2026-106069andCVE-2026-106072, affecting 22.x, 24.x, and 26.x): WHATWGurl.domainToASCII('0127.0.0.1')parses the leading0127as octal and returns'87.0.0.1', whereas many OS resolvers (or customlookuphooks) treat0127.0.0.1as decimal127.0.0.1(or vice versa for0177.0.0.1,0x7f.0.0.1,2130706433, and127.1). When!net.isIP(hostname)was paired withdomainToASCII(hostname), Node validated the peer’s certificate against thedomainToASCII()IP or DNS SAN (87.0.0.1) while the socket connected to a different IP (127.0.0.1).
Commit cd4b05469c and commit ad788ccd07 resolve both issues by rejecting any hostname that is not already a canonical IP literal (net.isIP(hostname) === 0) yet becomes an IP literal after domainToASCII() (net.isIP(hostnameASCIIWithoutFQDN) !== 0):
--- a/lib/tls.js
+++ b/lib/tls.js
@@ -439,6 +439,8 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
hostname = unfqdn(hostname);
const hostnameASCIIWithoutFQDN = unfqdn(hostnameASCII);
+ const hostnameIsIP = net.isIP(hostname);
+ const hostnameASCIIIsIP = net.isIP(hostnameASCIIWithoutFQDN);
...
+ // Non-canonical IP address spellings can be interpreted differently by
+ // domainToASCII() and the operating system resolver. Reject them instead of
+ // validating a certificate against an address that may not be the peer.
+ if (!hostnameIsIP && hostnameASCIIIsIP) {
+ return new ERR_TLS_CERT_ALTNAME_INVALID(
+ 'Host is a non-canonical IP address', hostname, cert);
+ }
4. CVE-2026-106074: crypto.randomUUID() and randomInt() cache replay in startup snapshots and SEAs
To reduce CSPRNG syscall overhead, lib/internal/crypto/random.js pre-generates random bytes in module-scoped TypedArray buffers (randomCache for randomInt() and uuidData for buffered randomUUID() / randomUUIDv7()). When a developer builds a V8 startup snapshot (node --build-snapshot) or a Single Executable Application (SEA) and any code calls randomUUID() or randomInt() during the snapshot build, the remaining pre-generated bytes in uuidData and randomCache are serialized into the snapshot blob. Every process later launched from that snapshot starts with the same uuidBatch counter and emits identical UUIDs and random integers.
Commit acffa610db registers resetRandomCaches() both after user serialization callbacks (addAfterUserSerializeCallback) and before user deserialization callbacks (addBeforeUserDeserializeCallback), zeroing randomCache, uuidData, and uuidNotBuffered so even caches refilled by process.nextTick or exit handlers during snapshot teardown are cleared before the new process runs.
Detection and remediation
1. Inventory Node.js runtimes, container images, and system libbrotli links
Because Node.js cherry-picked the Brotli patch without changing process.versions.brotli, check process.versions.node and inspect whether your Node binary is statically or dynamically linked against libbrotlidec:
# Check running Node.js version and shared library configuration
node -e 'console.log({ node: process.versions.node, brotli: process.versions.brotli, sharedBrotli: Boolean(process.config.variables.node_shared_brotli) })'
# On Linux, check if the node binary links dynamically against system libbrotlidec
ldd "$(command -v node)" 2>/dev/null | grep -i brotli || true
# Search Dockerfiles, CI workflows, and version files for unpatched Node 22, 24, 26, or EOL Node 20
rg -n 'FROM\s+(\S+/)?node:|node-version:\s*["'\'']?(20|22|24|26)' \
Dockerfile* .github/workflows .nvmrc .node-version package.json 2>/dev/null
# Check Bun runtime and Python Brotli / brotlicffi packages (which are not flagged by pip-audit yet)
bun --version 2>/dev/null || true
pip show Brotli brotlicffi 2>/dev/null || true
rg -n -i '"?(brotli|brotlicffi)"?\s*([~=!<>]=|:)' \
requirements*.txt pyproject.toml uv.lock poetry.lock Pipfile.lock 2>/dev/null
2. Upgrade runtimes, Python wheels, and SEA binaries
- Upgrade Node.js to
26.12.1,24.22.1, or22.23.4, Bun to1.4.4+, and PyPIBrotli/brotlicffi:- If you use official Docker images (
node:24-slim,node:24-alpine,node:22-bookworm,node:26), pull the images pushed on or after2026-10-21T17:38Z(-slim) or2026-10-22T02:39Z(full Debian and Alpine tags) and redeploy. - If your Linux distribution builds
nodejswithnode_shared_brotli: trueagainst the systemlibbrotlipackage, also updatelibbrotli1/brotlito1.2.1,1.1.1, or the distribution’s backported package forCVE-2026-111497. - For Bun workloads, upgrade to
bun >= 1.4.4(bun upgrade). - For Python workloads where
Brotliorbrotlicffiis present, upgrade toBrotli>=1.2.1andbrotlicffi>=1.2.1.0(pip install -U "Brotli>=1.2.1" "brotlicffi>=1.2.1.0"). - If a Node.js service using Express 5 (
express.json(),express.urlencoded(),express.raw(),express.text()) orbody-parser2.xcannot be upgraded immediately, drop incoming requests carryingContent-Encoding: brat your reverse proxy or load balancer as a temporary inbound stopgap. - Migrate any workload still on Node.js
20.x(end-of-life since 30 April 2026) to22.23.4or24.22.1.
- If you use official Docker images (
- Rebuild V8 startup snapshots and Single Executable Applications (SEAs): Any SEA binary or
--snapshot-blobbuilt with an older Node.js release embeds both the vulnerablelibbrotlimachine code (CVE-2026-111497) and potentially primedrandomUUID()/randomInt()entropy buffers (CVE-2026-106074). Rebuild them with a patched Node binary and rotate any tokens, session IDs, or keys that were generated at startup by a snapshot-backed binary.
Timeline
- 2026-10-14: The Node.js project posts a pre-alert announcing that security releases for the
22.x,24.x, and26.xlines will ship on or shortly after 21 October 2026. - 2026-10-21 (10:38 UTC): Node.js publishes
v22.23.4,v24.22.1, andv26.12.1on GitHub andnodejs.org, disclosing ten CVEs (CVE-2026-111497,CVE-2026-105482,CVE-2026-105483,CVE-2026-106069,CVE-2026-105481,CVE-2026-106072,CVE-2026-106074,CVE-2026-106070,CVE-2026-106071, andCVE-2026-106073). - 2026-10-21 (19:26 to 19:27 UTC): Google tags
brotliv1.0.10,v1.1.1, andv1.2.1with the security note forCVE-2026-111497. - 2026-10-21 (17:38 UTC) to 2026-10-22 (02:41 UTC): Docker Official Images publish updated
library/nodetags for22.23.4,24.22.1, and26.12.1. - 2026-10-22 (10:11 UTC): Bun releases
v1.4.4with the backportedCVE-2026-111497patch and RustBrotliDecoderErrorCode2-17enum update (oven-sh/bun#46352). - 2026-10-22 (13:57 and 16:19 UTC):
Brotli1.2.1andbrotlicffi1.2.1.0are uploaded to PyPI, and Imperva Red Team publishes its Node2Shell write-up demonstrating RCE on the officialnode:24.22.0Linuxarm64image. - 2026-10-22 (18:45 UTC): The full Node.js release summary is posted to
oss-security(2026/10/22/8). - 2026-10-23 (05:31 UTC): OpenSourceMalware publishes sdist diff and AddressSanitizer verification confirming that PyPI
Brotli <= 1.2.0andbrotlicffi <= 1.2.0.2carried the same vulnerable C decoder without a PyPA or GHSA advisory.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.
References
- Node.js: Wednesday, October 21, 2026 Security Releases
- Node.js v26.12.1 release notes and commit list
- Node.js v24.22.1 (LTS) release notes
- Node.js v22.23.4 (LTS) release notes
- oss-security: Node.js security release 2026/10/21 summary
- nodejs/node commit a6040b50d9: cherry-pick Brotli fix 0ed4268a4401 (CVE-2026-111497)
- nodejs/node commit 6b78ea171d: validate resolve inputs for null bytes (CVE-2026-105482)
- nodejs/node commit ad788ccd07: reject non-canonical IP addresses in TLS (CVE-2026-106069, CVE-2026-106072)
- Safeguard: Node.js Brotli CVE-2026-111497 analysis and patching guide
- Imperva Red Team: Node2Shell, from Brotli data to remote code execution
- Aikido: Node2Shell evolves to HTTP2Shell (CVE-2026-111497 blast radius)
- OpenSourceMalware: Python packages Brotli and brotlicffi vulnerable to Node2Shell
- Envoy advisory GHSA-3wvj-j6qm-62mr (CVE-2026-111497)
- oss-security: CVE-2026-115936 in Perl IO::Compress::Brotli (< 0.023)
- oven-sh/bun issue #46352: Brotli decoder missing CVE-2026-111497 fix (fixed in Bun v1.4.4)
- Microsoft .NET Blog (25 October 2026): .NET October 2026 Out-of-Band Security Updates (8.0.33, 9.0.22, 10.0.14, 11 RC3)
- Microsoft Security Advisory GHSA-p3jp-9v83-76w9: .NET Brotli Decoder Out-of-bounds Write (CVE-2026-111497)
- JFrog Security Research (25 October 2026): Brotli2Shell, beyond Node2Shell and HTTP2Shell (FFmpeg libjxl, .NET, ClickHouse, Apache Arrow)
- The Hacker News (26 October 2026): PHP issues second Windows security update in five days (8.5.12-1 to 8.2.35-1) for Brotli flaw
- oss-security (26 October 2026): Brotli CVE-2026-111497 and git-remote-https (libcurl CURLOPT_ENCODING)
- CISA (27 October 2026): CISA adds CVE-2026-111497 and CVE-2026-48356 to the Known Exploited Vulnerabilities catalog
- Wiz Research (25 October 2026): Broccoli2shell, everything you need to know about CVE-2026-111497
- oss-security (29 October 2026): CPython CVE-2026-116566 socket.getaddrinfo() embedded null bytes in host and port