
SonicWall Capture Labs threat research team became aware of the threat CVE-2026-111497, assessed its impact, and developed mitigation measures. The flaw, which circulates as Node2Shell, Broccoli2Shell, and HTTP2Shell, is a critical out-of-bounds write in the Google Brotli decoder that Node.js bundles to implement the br content coding. It is reachable by any application that decompresses an attacker-supplied Content-Encoding: br HTTP body, so a single unauthenticated request carrying a crafted Brotli stream drives the decoder past an eight-byte buffer and corrupts its state, a condition that public exploitation turns into remote code execution. Classified under CWE-252 (Unchecked Return Value) leading to CWE-787 (Out-of-bounds Write), it is rated CVSS 9.8 (Critical) by NVD, with Google, the coordinating CNA, scoring it 9.2 under CVSS 4.0. It was found by OpenAI Codex Security (Khai Tran and Sylvie Mayer) and fixed in the Node.js October 2026 security releases as GitHub Security Advisory GHSA-9v8h-qqgm-4m35. Its EPSS score is 88% (99.8th percentile), and CISA added it to the Known Exploited Vulnerabilities catalog on October 27, 2026, with exploitation observed in the wild from October 26. Affected releases are Node.js before 22.23.4, the 23.x and 24.x lines before 24.22.1, and the 25.x and 26.x lines before 26.12.1, all of which bundle Google Brotli 1.0.3 through 1.2.0; the same decoder also ships in .NET, Bun, Envoy, and Istio. Fixes are available in Node.js 22.23.4, 24.22.1, and 26.12.1, and administrators should upgrade immediately.
Node.js bundles Google's Brotli library to decompress the br content coding. Any Node application that inflates an attacker-controlled Brotli stream reaches the vulnerable code: Express services using express.json() or body-parser with default inflation, code that reads a Brotli-encoded response through the built-in fetch(), and the many HTTP client and proxy libraries that embed the decoder. No authentication is involved, because decompression runs before any application logic. The flaw lives in the decoder's block-switch path in deps/brotli/c/dec/decode.c, exercised while the decoder walks the command stream of a metablock.

The root cause is an unchecked, overloaded return value. DecodeBlockTypeAndLength decodes a block-type switch and returns a BROTLI_BOOL. When a metablock declares only a single block type there is nothing to switch to, so the function returns BROTLI_FALSE to reject the request. That same BROTLI_FALSE is used elsewhere to mean "needs more input", so a single boolean conflates a fatal format error with a benign, recoverable condition.

That signal is then thrown away. The command-stream block switch is invoked through DecodeCommandBlockSwitch, which is declared static void and keeps no return value, and through the BROTLI_SAFE macro, whose non-safe branch simply calls the function and ignores what it returns. The BROTLI_FALSE from the invalid switch never stops decoding. The decoder proceeds as though the switch succeeded, with its block-length counter left at zero and its position and distance state carried forward on a path the format should have rejected.

Continuing in that corrupted state produces the out-of-bounds write. Later in ProcessCommandsInternal, a backward-reference copy computes its destination straight from the running position, copy_dst = &s->ringbuffer[pos], and performs a sixteen-byte vector store through memmove16. Because the earlier error was ignored, pos and the copy length are no longer constrained to the ring buffer, so the store lands outside the allocation. Under a debugger the faulting instruction is a single movups %xmm0,(%r8) with r8 equal to ringbuffer + pos: a sixteen-byte write whose destination the attacker steers through the preceding corruption.

Node.js 24.22.1 and the companion releases fix the flaw by replacing the overloaded boolean with an explicit status. DecodeBlockTypeAndLength now returns a BrotliDecoderErrorCode, and the invalid single-block-type case returns BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH instead of BROTLI_FALSE. The block-switch wrappers propagate that code, and a new BROTLI_SAFE_WITH_STATUS macro in ProcessCommandsInternal checks it and aborts decoding on any non-success result, so a malformed block switch now ends the stream cleanly rather than being silently ignored.

The following conditions must be met for successful exploitation of CVE-2026-111497:
Exploiting CVE-2026-111497 requires no authentication and no application-specific knowledge, only that the target decompresses a Brotli request or response. The attacker sends the crafted stream in two stages over one chunked HTTP request. The first stage is the oversized trigger that drains the block-length counter and drives the decoder into the invalid block switch, where the ignored error leaves it parked with a corrupted state and an eight-byte input buffer. After a brief pause the second stage appends a small chunk that writes past that eight-byte buffer, forging the decoder's position and distance fields. On the next copy the sixteen-byte store lands at an attacker-chosen address.
Video Demonstration
The request is an ordinary POST whose body is declared Content-Encoding: br and sent with Transfer-Encoding: chunked so the two stages can be delivered separately. The server treats it as a routine compressed upload and begins inflating it at once, which is why a successful attack looks like normal traffic until the write occurs.

| Component | Value | Purpose |
|---|---|---|
| Transport | POST with Content-Encoding: br and Transfer-Encoding: chunked | Unauthenticated path into the decoder; chunking separates the two stages |
| Stage 1 Trigger | ~22 MB stream, 2^24 block-switch commands, single block type | Drains the block-length counter and reaches the invalid block switch |
| Overloaded Return | DecodeBlockTypeAndLength returns BROTLI_FALSE | Invalid switch mis-signaled as recoverable, then discarded by the caller |
| Stage 2 Overflow | Small chunk appended past the 8-byte buffer | Forges pos and distance in the parked BrotliDecoderState |
| Write Primitive | memmove16 to ringbuffer + pos | Sixteen-byte out-of-bounds write-what-where |
| Code Execution | Overwrite a saved return address, pivot to a ROP chain reaching execvp | Turns the memory write into a reverse shell running as the service account |
To ensure SonicWall customers are prepared for any exploitation that may occur due to this vulnerability, the following signatures have been released:
| Signature ID | Signature Name |
|---|---|
| IPS: 22584 | Google Brotli Upload Informational |
| IPS: 22585 | Google Brotli Node id Command Execution (CVE-2026-111497) 1 |
| IPS: 22588 | Google Brotli Node id Command Execution (CVE-2026-111497) 2 |
| IPS: 22589 | Google Brotli Node id Command Execution (CVE-2026-111497) 3 |
| IPS: 22591 | Google Brotli Node id Command Execution (CVE-2026-111497) 4 |
| IPS: 22593 | Google Brotli Node id Command Execution (CVE-2026-111497) 5 |
| SPY: 7371 | Malformed-br br.TL_1 |
The risks posed by CVE-2026-111497 can be mitigated or eliminated with the following measures:
The vulnerability was found by OpenAI Codex Security (Khai Tran and Sylvie Mayer) and disclosed through the Node.js project as GitHub Security Advisory GHSA-9v8h-qqgm-4m35. Fixes shipped in the Node.js October 2026 security releases (22.23.4, 24.22.1, and 26.12.1).
Third-party vulnerability database mirrors:
Share This Article

An Article By
An Article By
Security News
Security News