Node2Shell and HTTP2Shell: The Same Brotli Vulnerability, and a Response Plan

Node2Shell and HTTP2Shell both refer to Common Vulnerabilities and Exposures (CVE) entry CVE-2026-111497, an out-of-bounds write in the decoder of Google’s Brotli compression library (versions 1.0.3 through 1.2.0).

Published
October 26, 2026
Share this post

See BlinkOps in action.

Put AI to work across your security operations.

Request a Demo

Executive Summary

Node2Shell and HTTP2Shell both refer to Common Vulnerabilities and Exposures (CVE) entry CVE-2026-111497, an out-of-bounds write in the decoder of Google’s Brotli compression library (versions 1.0.3 through 1.2.0). Imperva named it Node2Shell and Aikido named it HTTP2Shell. The Hypertext Transfer Protocol version 2 (HTTP/2) plays no part in the bug, and disabling it changes nothing.

  • A crafted Brotli stream can crash a process, leak memory, or lead to code execution in any software that decompresses attacker-controlled data.
  • Google scores it 9.2 under Common Vulnerability Scoring System (CVSS) 4.0. Red Hat and Amazon Linux score it 8.1 under CVSS 3.1, and other sources cite 9.8.
  • As of October 24, 2026, no exploitation in the wild is confirmed, and the Cybersecurity and Infrastructure Security Agency (CISA) has not added it to its Known Exploited Vulnerabilities (KEV) catalog. The Exploit Prediction Scoring System (EPSS) puts exploitation probability under 1 percent. Public proof-of-concept repositories exist, and Imperva demonstrated code execution on an official Node.js 24.22.0 build without releasing the exploit.
  • Fixed releases: Brotli 1.0.10, 1.1.1, and 1.2.1; Node.js 22.23.4, 24.22.1, and 26.12.1; Bun 1.4.4; Python Brotli 1.2.1 and brotlicffi 1.2.1.0; patched Envoy and Istio builds. Node.js 18, 20, 23, and 25 are end-of-life with no fix. .NET, Apache Arrow, DuckDB, and ClickHouse had no fix at the time of writing.
  • Patch internet-facing services that decode untrusted Brotli data now. EPSS lags newly published flaws, the patch diff is public, and code execution has been shown on a real build.

Where the Risk Sits

The decoder is reachable from two directions. Inbound, a request body with Brotli content encoding reaches it when a parser inflates the body, which Express 5 and body-parser 2.x do by default. Outbound, any client that calls a server an attacker controls decodes the response automatically, including fetch() in Node.js and Bun, requests, urllib3, aiohttp, httpx, and .NET with automatic decompression. Services that call user-supplied addresses (webhook delivery, link previews, importers, image proxies), crawlers, and artificial intelligence (AI) browsing agents rank highest. A web application firewall (WAF) sees only the inbound path.

Embedded copies widen the exposure. The upstream fix landed on February 27, 2026 with no security label, and downstream projects went nearly eight months without a reason to update. Bun-compiled binaries and Electron apps carry their own decoder, and lockfile scanners miss them. Image-level software bill of materials (SBOM) data and binary evidence find them. Envoy and Istio are affected only where Brotli decompression is configured.

What to Do Now

  1. Upgrade Node.js, Bun, Python Brotli packages, Envoy, Istio, and system libbrotli, then rebuild images and restart processes. A running process keeps the old decoder in memory.
  2. Until patched, reject Brotli-encoded request bodies at the edge and turn off automatic Brotli decoding in services that fetch untrusted addresses. For Node.js fetch(), upgrading is the only fix. Cloudflare and Microsoft have published WAF rules for the inbound path, and those rules are a stopgap.
  3. Rotate credentials held by internet-facing processes that decoded untrusted Brotli data. Memory disclosure leaves little trace.
  4. Move workloads off end-of-life Node.js lines.

Detection

No exploit-specific indicators of compromise have been published. Detection rests on behavior: Brotli-encoded request bodies (legitimate clients rarely send them), decoder error -17 (BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH) on patched hosts, SIGSEGV or SIGABRT crashes shortly after Brotli input, and Node.js or Bun processes that spawn shells. Community Suricata and Sigma rules are experimental. Tanium warns that some public “Node2Shell checkers” carry malware; vet any tool before running it.

Queries for hunts or detections should look for behavior: a runtime process spawning a shell or a download tool. That catches the post-exploitation step on the inbound path and on the outbound path.

Kusto Query Language (KQL) for Microsoft Defender XDR advanced hunting or Microsoft Sentinel:

kql

let children = dynamic(["sh", "bash", "dash", "zsh", "ash", "curl", "wget", "nc", "ncat", "socat"]);
DeviceProcessEvents
| where Timestamp > ago(24h)
| where InitiatingProcessFileName in~ ("node", "bun", "envoy")
     or InitiatingProcessFileName startswith "python"
| where FileName in~ (children)
// drop build and ops tooling, extend this list for your environment
| where not(InitiatingProcessCommandLine matches regex @"npm-cli\.js|yarn\.c?js|pnpm\.c?js")
| summarize Events = count(),
            FirstSeen = min(Timestamp),
            LastSeen = max(Timestamp),
            SampleCommands = make_set(ProcessCommandLine, 5)
    by DeviceName, AccountName, InitiatingProcessFileName, FileName
| order by LastSeen desc


Python parents are noisy because Ansible and similar tooling spawn shells constantly, so scope the query to web-tier hosts. On Windows servers, add cmd.exe and powershell.exe to the child list.

Search Processing Language (SPL) for Splunk. This assumes your endpoint data maps to the Common Information Model (CIM) Endpoint.Processes data model. Run it over the last 24 hours:

spl

| tstats summariesonly=false count min(_time) AS first_seen max(_time) AS last_seen
    values(Processes.process) AS child_commands
    FROM datamodel=Endpoint.Processes
    WHERE (Processes.parent_process_name IN ("node","bun","envoy")
           OR Processes.parent_process_name="python*")
      AND Processes.process_name IN ("sh","bash","dash","zsh","ash","curl","wget","nc","ncat","socat")
    BY Processes.dest Processes.user Processes.parent_process Processes.process_name
| rename Processes.* AS *
| search NOT parent_process IN ("*npm-cli.js*","*yarn.js*","*yarn.cjs*","*pnpm.cjs*")
| convert ctime(first_seen) ctime(last_seen)
| sort - count


YARA
for vendored copies of the Brotli decoder that lack the fix for Common Vulnerabilities and Exposures (CVE) entry CVE-2026-111497:

yara

rule Brotli_Decoder_Missing_CVE_2026_111497_Fix
{
    meta:
        description = "Executable contains the Brotli decoder but not the error string added by the CVE-2026-111497 fix"
        use         = "Hunting lead for vendored copies. Confirm the version before acting"

    strings:
        $d1  = "EXUBERANT_NIBBLE"
        $d2  = "SIMPLE_HUFFMAN_ALPHABET"
        $d3  = "CONTEXT_MAP_REPEAT"
        $d4  = "BLOCK_LENGTH_1"
        $fix = "BLOCK_SWITCH"

    condition:
        filesize < 500MB
        and ( uint32(0) == 0x464c457f        // Linux
           or uint16(0) == 0x5a4d            // Windows
           or uint32(0) == 0xfeedfacf        // macOS
           or uint32be(0) == 0xcafebabe )    // macOS universal binary
        and 3 of ($d*)
        and not $fix
}


This rule finds vendor decoders. It matches executables that contain the decoder’s error strings but not BLOCK_SWITCH, the error name the fix adds. Stripped or link-time optimized builds can drop strings, so a miss proves nothing, and copies older than 1.0.3 will match even though they are not affected. I built the fix string from the new error name and have not run the rule against real binaries. Test it on one patched and one unpatched Node.js binary before scanning production, and treat every hit as a lead until you confirm the version.

Response

Pseudo logic for three workflows. They are tool-agnostic, and BlinkOps can run each one through its integrations and approval steps.

Workflow 1: Find and gather information on vulnerable hosts

Download the workflow YAML

Workflow 2: Fix the host

This one fixes the host. It checks the running version, saves evidence, kills the process, rebuilds on the fixed version, confirms the new process is on it, and posts the result to Microsoft Teams.

Download the workflow YAML

Kill-first fits a host that showed a compromise signal. A host that is only vulnerable can take the fixed image first and let the rollout replace the old process, so the service stays up. Keep the approval step for production, since an unattended kill takes a live service down until the redeploy finishes. The start-time check catches a rebuilt image whose old process is still running.

Workflow 3: Rotate credentials and certificates

Download the workflow YAML

This one covers whatever the host could have leaked. It lists the secrets and certificates on the host, opens a ticket, waits for a person to approve, and rotates after the host is verified on a fixed version.


Conclusion

BlinkOps is a viable way to augment identification, detection, response, and recovery on this vulnerability. The work crosses scanners, endpoint tools, repositories, pipelines, and chat, and BlinkOps documents integrations with each.

For identification, workflows can pull findings from Tenable, Wiz, and other scanners, merge them with endpoint and cloud inventory, and open an owner-assigned case per exposed service. An agent can read repositories and configuration to judge whether untrusted Brotli data reaches the decoder, and a person confirms that verdict on high-risk assets.

For detection, workflows can deploy and tune rules through log and endpoint integrations, attach patch state and reachability to each alert, and watch KEV and EPSS so a change in exploitation status raises priority. Your detection team or the community supplies the rules.

For response, a workflow can open the case, collect evidence, notify on-call staff, and ask for approval in Slack or Microsoft Teams before it isolates a host through CrowdStrike or starts credential rotation.

For recovery, workflows can trigger rebuilds through GitHub or GitLab pipelines, rescan, close a finding only after the running image is verified, and draft the incident timeline to notify owners.

Plan around the limits. BlinkOps orchestrates the scanners, endpoint detection and response (EDR) tools, and pipelines you already run, and the quality of that data sets the ceiling for identification. The documented Cloudflare actions do not cover WAF rules, so virtual patching there needs a custom action you build and test. Unfixed code in .NET, Arrow, DuckDB, and ClickHouse stays vulnerable until its maintainers ship patches. Keep agents in a recommend-only role for enrichment and reachability, put every write action in a deterministic workflow with an approval step, and stream the 30-day audit log to your security information and event management (SIEM) platform.

Sources (patch and scoring details were changing daily, so confirm current status with vendors before acting)

No items found.
No items found.