October 28, 2026
Black October: Investigating Critical Vulnerabilities โ Node2Shell
Part 3 โ Node2Shell: How Compressed Data Can Become Code Execution

By Saniye Nur
5 min read
In the first part of this series, we explored how unsafe memory handling in PHP could lead to remote code execution. In the second, we followed a compromised LiteLLM release into Python environments.
This time, the starting point is compressed data.
An application receives a response, opens its contents, and continues its work. That ordinary operation becomes a security boundary when the decompression code contains a memory-safety flaw.
For incident responders, the central question is:
Which applications opened attacker-controlled data, and what happened inside those processes afterward?
What Is Node2Shell?
Node2Shell refers to CVE-2026โ111497, a vulnerability in the Brotli decoder bundled with Node.js.
Malformed compressed input can cause an out-of-bounds write. Possible consequences include a process crash, disclosure of process memory, or remote code execution.
Node.js addressed the issue in its October 21, 2026 security releases.
First, Imagine an Office Worker
Think of a Node.js application as an office employee processing assignments.
Some assignments arrive in compressed packages. Before reading the documents, the employee uses an unpacking machine.
In this analogy:
- The employee represents the Node.js process.
- The unpacking machine represents the Brotli decoder.
- A small temporary tray represents an internal memory buffer.
- Incoming packages represent compressed data.
The employee can receive a package directly from a customer. They can also request documents from another company and receive a compressed package in response.
Both routes bring outside material to the same unpacking machine.
The Problem: "Keep Adding More"
Imagine the machine encountering a package with invalid internal instructions.
It should stop and report:
"This package is malformed. Do not continue."
Instead, it interprets the problem as:
"The package is incomplete. Keep adding more material."
The temporary tray has limited capacity. Continuing to add material eventually pushes beyond its boundary.
The underlying bug follows this pattern: an invalid block switch is treated as a temporary need for additional input, allowing the streaming decoder to keep appending bytes to an eight-byte internal buffer.
This is more specific than simply receiving a large file. The failure concerns how the decoder handles malformed compressed input.
How Can an Overflow Become Code Execution?
In the office example, imagine the overflowing tray disturbing documents and instructions on the surrounding desk.
Actual memory corruption affects bytes belonging to the running process. Turning that corruption into code execution requires influencing what is overwritten and how the process subsequently uses it.
The analogy stops at that boundary. An overflowing tray illustrates the loss of separation; it does not explain the engineering required to control execution.
A crash demonstrates a failure. It does not independently demonstrate successful code execution.
Vercel's advisory reports demonstrated memory disclosure and command execution on unpatched Node.js builds. Execution occurs with the permissions of the affected process.
The Package Can Arrive Through Two Doors
The first route is an incoming request. An application or middleware component decompresses a Brotli-encoded body supplied by a client.
The second route is a fetched response. The application contacts another server, and its HTTP client decompresses the response.
Exposure therefore depends on whether application code or a dependency opens attacker-controlled Brotli data. The presence of Node.js alone does not establish a reachable attack path.
Return to the office worker. Security checks at reception may inspect deliveries handed to the company, while the employee also receives packages they ordered themselves.
That second route matters when reviewing services that retrieve external content.
How I Would Approach an Incident Investigation
The following workflow describes an investigation method. It does not claim that a particular organization was compromised.
I would separate three questions:
- Was a vulnerable decoder reachable?
- Is there evidence of an exploitation attempt?
- Is there evidence of unauthorized execution or data access?
1. Establish What Was Actually Running
Start with the runtime serving the workload.
Record the executable path, runtime version, package origin, container image digest, and deployment history.
A developer's terminal version does not establish which binary a production container uses. Similarly, a version declared in project configuration describes an expectation rather than proving what ran.
The Node.js fixes are available in:
Release line- Version containing the fix :
Review unsupported versions separately and account for vendor patch information where applicable.
Build the exposure timeline from deployment records through replacement of the affected processes.
2. Map the Decompression Paths
Work with application owners to establish where untrusted compressed data can be opened.
Useful questions include:
- Which routes accept compressed request bodies?
- Which services retrieve external URLs or API responses?
- Can a user influence the destination or returned content?
- Which libraries perform decompression automatically?
- Does a proxy transform the content before it reaches Node.js?
Code searches can identify explicit Brotli API usage, but dependencies may perform the operation indirectly.
Document the path from the outside data to the decoder. That path helps prioritize investigation and remediation.
3. Preserve Evidence and Check Visibility
Collect application, reverse proxy, WAF, endpoint, container, and network records before retention limits remove them.
Determine what each source records.
An access log may show a request without its body. An outbound connection record may identify a server without retaining the response headers. A short-lived container may have disappeared while its build and deployment records remain available.
These differences determine which conclusions the evidence can support.
4. Correlate Traffic with Process Failures
Look for unusual request or response activity around Node.js crashes, abnormal exits, and unexpected workload restarts.
Where available, correlate by:
- Timestamp and request identifier.
- Application route.
- Host, pod, or container identity.
- Process identity.
- Remote endpoint.
A Content-Encoding: br header is a useful lead for understanding compressed traffic. It is also legitimate HTTP behavior.
Likewise, a segmentation fault can result from an unrelated defect.
Neither a compression header nor a crash is a standalone exploit verdict.
5. Investigate What the Process Did Next
For candidate windows, examine subsequent process and identity activity.
Look for unexpected command interpreters, unusual child processes, new outbound connections, modified files, and access to sensitive configuration.
Compare those observations with the application's normal behavior. A service may legitimately invoke external tools; the command arguments, ancestry, timing, and execution identity determine whether the activity needs escalation.
Also avoid making a child shell mandatory evidence. Unauthorized activity may occur within the Node.js process itself.
If compromise is supported, follow the affected identity beyond the host. Review the use of credentials and sessions available to that workload.
6. Use Indicators with the Right Expectations
This investigation should not depend on finding one fixed malicious domain or file hash.
A vulnerability can be used by different attackers with different infrastructure and payloads. Specific campaign indicators become useful when trustworthy reporting connects them to observed activity.
Until then, distinguish:
- Exposure evidence: an affected runtime and reachable decoding path.
- Attempt evidence: suspicious input or a relevant security detection.
- Compromise evidence: unauthorized execution, access, or other attributable effects.
This separation prevents both over-escalating ordinary compressed traffic and overlooking activity because no known domain matched.
7. Verify Remediation in the Workload
Updating a base image does not replace containers already running from an older build.
Verify the complete sequence: patched artifact, rebuilt application image, deployment, and replacement of affected processes.
Vercel recommends rebuilding self-managed containers on patched Node.js images. Its advisory also explains that inbound WAF protection does not cover responses fetched from other servers.
Where compromise is suspected, preserve evidence and contain the workload. Where unauthorized access is established, address persistence and exposed credentials as part of recovery.
8. Close with an Evidence Boundary
The final assessment should identify the systems, dates, and telemetry reviewed.
Separate direct runtime evidence from application-owner statements. Record unavailable request bodies, expired logs, missing endpoint coverage, and unresolved search failures.
A defensible conclusion might be:
No evidence of successful exploitation was identified in the telemetry reviewed for the specified systems and period.
That statement becomes useful when the reader can understand its scope.
What Node2Shell Changes for Responders
Decompression is easy to overlook because it happens before the application begins using the content.
In the office analogy, the employee is still opening the package. They have not yet started reading the assignment.
For responders, that makes the handling path important: who supplied the data, which component opened it, which process handled it, and what that process did afterward.
The investigation connects the compressed input to the runtime โ and the runtime to any subsequent unauthorized activity.
References