Security people love a nickname, and the nickname usually hides the interesting part. “Node2Shell,” the label attached to CVE-2026-111497, suggests a trivial road to remote command execution on every affected box. The actual picture is messier and, in some ways, worse.
What is Node2Shell (CVE-2026-111497)?
The flaw is not in your JavaScript, in V8’s memory management, or in the Node.js event loop. It lives in the native C implementation of the Brotli decompression library, a heavily optimized parsing engine that Node.js bundles directly into the runtime.
The vulnerability was disclosed in the October 2026 security releases and affects the supported Node.js release lines 22.x, 24.x, and 26.x. Patches landed in 22.23.4, 24.22.1, and 26.12.1. The core defect is an out-of-bounds memory write triggered by malformed compressed input. That is a textbook memory-safety bug, and it managed to survive in a globally deployed, heavily audited library.
The Cyber Security Agency of Singapore scores it as high as CVSS 9.8, and the position of the bug explains why. Decompression routinely runs before any application-level authentication or authorization check. An attacker who can send a specially crafted HTTP payload gets direct access to the native memory of the server process.
How Brotli metablocks and block switching work
Brotli is a lossless compression format defined in RFC 7932. It combines LZ77-style backward references with context modeling through Huffman coding. Instead of a flat sequence, the compressed payload is divided into structured units called metablocks. Each metablock carries the instructions for rebuilding the plaintext: literal bytes, copy commands, and distance codes that point back at earlier output in the sliding window.
To compress mixed data well, the format lets the compressor define multiple block types for each of those categories of information. The decoder keeps an internal state machine that tracks the active block types and their Huffman trees. When the statistics of the data shift, the compressor issues a block switch command and the decoder moves to a different set of coding structures mid-stream. That flexibility is why Brotli does so well on JavaScript bundles, CSS stylesheets, and JSON payloads.
It also puts a burden on the C implementation. The decoder has to keep checking that every requested block switch is structurally permitted by the parameters the stream declared in its own header.
Root cause: the DecodeBlockTypeAndLength ambiguity
The bug sits in the DecodeBlockTypeAndLength function in c/dec/decode.c of the Google Brotli repository. Streaming decompression is what makes it possible. Data rarely arrives in one contiguous chunk, because TCP fragments payloads. The decoder therefore has to pause on an incomplete stream, keep its state, and resume exactly where it stopped when more bytes show up on the socket.
That means the C API must separate two failure states. One is an incomplete read, where the decoder just needs more bits to finish the current element. The other is a fatal format error, where the incoming bytes break the Brotli specification and nothing that follows can repair them.
The decoder failed to tell an invalid block switch from a temporary lack of input. When the parser hit a transition the format forbids, such as a block type switch in a category that declared only one block type, it returned an ambiguous failure value. The streaming wrapper read that value as a request for more data instead of a corrupted stream. The decoding loop kept running on an inconsistent state machine and waited for later bytes to resolve a structural impossibility.
📬 Stay Ahead of Cyber Threats
Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.
Subscribe to the Newsletter →From a bad return value to an out-of-bounds write
Treating a fatal format violation as a pause is rough on memory integrity. While the decoder waited for input to settle the supposedly incomplete state, it kept accumulating incoming bytes into an internal eight-byte ring buffer that manages the sliding window of decompressed output. The state machine had already entered an impossible configuration because of the invalid block switch, so the logic that governs how bytes reach this buffer skipped its usual boundary checks. The decoder lost track of its position in the allocated structures and wrote past the end of the buffer.
In C, that does not raise an exception. It silently overwrites adjacent memory. Depending on the heap layout, the allocation patterns of the specific Node.js process, and the architecture of the operating system, the overwrite can corrupt critical metadata, alter function pointers, or tamper with the internal state of the V8 isolate.

Can Node2Shell really give attackers remote code execution?
The nickname implies that every vulnerable instance falls to trivial RCE. Heap exploitation is less obliging. The attacker has to shape the malformed Brotli stream so that the overwritten memory holds a primitive capable of hijacking the instruction pointer. Researchers have done it anyway. The Imperva Red Team demonstrated reliable code execution against the official vulnerable build, the Node.js 24.22.0 Linux arm64 image, as long as the attacker can map the target’s memory layout accurately.
The embedded setting changes the exploitation dynamics compared with a standalone C program. V8 manages its own isolated heap spaces for JavaScript objects, while native C and C++ code sits in adjacent regions handled by the system allocator, such as glibc malloc or jemalloc. The Brotli ring buffer lives on that native heap. When the decoder overflows it, the bytes typically land on allocator metadata or on neighboring native structures used by the zlib bindings.
Stable RCE therefore needs a stream that does two things: it triggers the overflow, and it lines the overwritten bytes up with a target function pointer or a heap metadata structure that can be turned into an arbitrary write primitive. That takes detailed knowledge of the target’s memory layout, its ASLR mitigations, and the allocation patterns of the Node.js event loop. A denial of service is far easier. Corrupt a vital pointer, collect the segmentation fault, done. Reliable code execution takes a sophisticated payload, which is why the vulnerability counts as a potential RCE and not a guaranteed one across every deployment configuration and compiler hardening setting.
The fix: BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH
A bounds check alone would not have solved this. The error-handling contract between the low-level C library and its high-level wrappers had to change. The upstream Brotli maintainers fixed the defect in commit 0ed4268a4401abcb44720963 by adding a dedicated error code for the scenario, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, with the numeric value -17. The decoder can now tell the calling application that the stream is structurally invalid and that no further input will make it valid.
The Node.js project cherry-picked the upstream fix and exposed the new constant through its native compression bindings. JavaScript developers can read it as zlib.constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH. With the patch, the Node.js streaming API aborts decompression immediately, discards the malformed payload, and throws a recognizable exception instead of feeding more attacker-controlled bytes into a corrupted memory state.
The lesson for systems programmers is that a clear error code matters to memory safety as much as the allocation logic does. A parser should enforce its format invariants before it uses input to update internal state, and it should give callers an unambiguous signal when those invariants break.
Affected software beyond Node.js: Bun, Python, Envoy, curl, and git
Brotli is a foundational library, not a package tied to one language, so the blast radius of CVE-2026-111497 extends well past Node.js. Any software that statically links or bundles a vulnerable version of the C decoder, versions 1.0.3 through 1.2.0, inherits the same risk.
The Bun runtime implements its own web APIs and compression handlers, and it bundled the affected Brotli code. Bun patched the issue in version 1.4.4. The Python ecosystem is exposed through the widely used Brotli and brotlicffi packages on PyPI, which provide native bindings to the C implementation. A Python application that processes untrusted compressed data is as exposed to native heap corruption as a Node.js server, whatever memory-safety guarantees the interpreter itself offers.
Network edge components are hit hardest. Envoy, the proxy that forms the data plane of service meshes like Istio, uses Brotli to decompress traffic before routing it to upstream microservices. If Envoy is configured to decode compressed request bodies automatically, an attacker can trigger the out-of-bounds write inside the proxy process itself and potentially compromise the ingress layer before the request reaches any business logic. Even command-line tools like curl and git, which support Brotli for network transfers, had to audit and update their bundled dependencies.
Why the Brotli bug runs before authentication
The most alarming property of this vulnerability is where it sits in the HTTP request lifecycle. Web frameworks and HTTP clients hide content negotiation and payload decoding from the developer. When a client sends a request with the Content-Encoding: br header, the server or proxy usually starts decompressing as soon as it reads the socket stream, well before the request reaches the application’s routing middleware. The vulnerable C code therefore executes in a pre-authentication, pre-authorization context.
Traditional perimeter defenses do little here. TLS encrypts the payload in transit and protects it from eavesdroppers, but it says nothing about the structural integrity of the compressed bytes once they are decrypted in memory. Web Application Firewalls have a worse problem. To inspect a Brotli stream, the WAF must decompress it first, which means running the same vulnerable decoder logic it is supposed to be guarding against. Authentication mechanisms such as JWT validation or session cookie checks live in the JavaScript or Python layer, and that layer is bypassed entirely if the native process crashes or is compromised during the initial C-level parse.
The only dependable defense is a patched decoder that rejects malformed streams at the earliest possible instruction. Automatic decompression of untrusted network data is dangerous by nature, and it is only as safe as the error handling in the native parsers underneath it.
Python HTTP clients and the silent decompression blind spot
The Python exposure points to an architectural blind spot in modern HTTP client libraries. Packages like urllib3 and the standard urllib handle content negotiation automatically. They decompress response bodies according to the Content-Encoding header and hand plaintext back to the developer. When a Python application requests a resource from a third-party API, the Brotli C extension is invoked the moment the socket starts receiving the compressed stream.
If the remote server is compromised, or if the application follows a user-supplied URL that redirects to a malicious endpoint, the server can return a weaponized Brotli payload. Decompression happens at the native layer during the socket read, so Python exception handling and application logic never get a chance to intervene. The process simply hits a memory corruption event.
That is a problem for backend services that aggregate data from external sources, fetch webhooks, or process user-uploaded archives. Developers often assume that memory-safe Python makes them immune to buffer overflows, and forget that the native extensions handling network I/O are written in C and parse attacker-controlled binary formats. Compensating controls such as inspecting headers before invoking the read stream are fragile, because the underlying library may already have started decoding in the background.
Envoy and Istio: the service mesh attack surface
Brotli in cloud-native infrastructure, particularly in Istio and its Envoy proxies, creates a privileged attack surface. In a typical Kubernetes deployment, Envoy runs as a sidecar container and intercepts all ingress and egress traffic for its microservice. To save internal bandwidth and meet external client requirements, Envoy can be configured to decompress incoming HTTP requests before forwarding the plaintext payload to the application container.
If the Envoy binary was built against a vulnerable Brotli library, the memory corruption happens inside the sidecar itself. Sidecars often run with elevated network privileges and can reach internal service mesh certificates and routing tables, so a compromised Envoy process can lead to lateral movement across the cluster. The Istio security bulletins specifically warn about EnvoyFilter resources that enable decompression, since those custom filters call the vulnerable code path directly.
Remediation is painful. Updating the Istio control plane does not patch the data plane proxies that are already running. Operators have to orchestrate rolling restarts of the Envoy sidecars across thousands of pods, confirm that the new container images carry the patched Brotli dependency, and keep production workloads up the whole time.
How to patch and mitigate CVE-2026-111497
Fixing this vulnerability takes an audit of the whole deployment topology, because updating one package manager is rarely enough. For Node.js, upgrade the runtime to a patched release:
- Node.js 22.x: 22.23.4
- Node.js 24.x: 24.22.1
- Node.js 26.x: 26.12.1
In cloud-native setups, the runtime often hides inside container base images, single-executable application bundles, or CI/CD runner environments. A team can bump its package.json dependencies and never notice that the Docker image still ships a vulnerable Node.js binary or an outdated system-level Brotli package. Linux distributions publish their own advisories as well. Debian, for example, released DSA-6582-1 to patch the brotli package in Trixie, so OS-level security bulletins need tracking alongside the language ecosystems.
Where an immediate binary replacement is impossible because of operational freezes or complex vendor dependencies, compensating controls have to be applied carefully. Disabling Brotli decompression at the reverse proxy, or configuring HTTP clients to reject the br encoding before the body stream is read, reduces the risk. Both measures only work if they happen before the decoding function is invoked. Checking the Content-Encoding header after the HTTP library has already begun a streaming decode gives no protection at all.









