Live updates: this post is a snapshot. For the latest on Node2Shell (CVE-2026-111497), follow the Mallory story. It updates automatically as advisories, exploits and attack activity appear.

Update, Oct 24, 22:00 UTC: exploitation status. No attacks in the wild have been reported, and the CVE is not in CISA KEV. Reliable code execution exists only privately: OtterSec reports an AI-written exploit that succeeded 50 times out of 50 on x86_64 with ASLR on, and Imperva showed code execution on arm64. Neither has been published. Public code is different: we track 35 GitHub repositories, and none runs code on a stock Node.js build, but several can hang a vulnerable service with a few bytes. Assume remote denial of service is available to anyone today. Details below.

CVE-2026-111497 is an out-of-bounds write in Google's Brotli decoder. Anything that decompresses attacker-controlled Brotli data can crash, leak memory or run the attacker's code. The headlines say Node.js, but the same decoder ships in Python, Bun, Envoy, curl, Firefox and most Linux distributions. Servers that accept compressed requests are exposed, and so are clients that fetch from someone else's server. Upstream fixed the bug in February but released no fix until October 21. Most Linux distributions still have no fix.

At a glance

CVECVE-2026-111497. Also called Node2Shell and HTTP2Shell
SeverityCVSS v4.0 9.2. Network, no authentication, no user interaction
AffectedBrotli 1.0.3 to 1.2.0, and everything that bundles or links it
Found byOpenAI Codex Security agents (credited: Khai Tran, Sylvie Mayer)
ExploitationNo attacks in the wild reported. Not in CISA KEV (EPSS 0.7%). Reliable RCE exists privately; public code achieves denial of service, not RCE
ScannersAs of Oct 23, OSV and GitHub advisories did not map the CVE to the PyPI packages, so pip-audit, Dependabot and Safety miss it. Copies bundled inside runtimes never show up in lockfile scans

Am I affected?

You are exposed if something you run decodes Brotli from a source you don't control. In practice:

  • Node.js before 22.23.4, 24.22.1 or 26.12.1 that calls fetch(), uses zlib Brotli APIs, or accepts compressed request bodies (Express 5 / body-parser 2.x, Koa, Fastify @fastify/compress, AdonisJS). Node.js 20 and older get no fix.
  • Python with brotli 1.2.0 or older, or brotlicffi 1.2.0.x, installed anywhere in the environment. You don't have to import it. urllib3 (and so requests), aiohttp and httpx[brotli] use it automatically, and Scrapy installs it for you. brotli gets about 83 million downloads a month.
  • Bun before 1.4.4, through fetch(), node:zlib and DecompressionStream.
  • Linux system packages: anything linked against the distribution's libbrotli, including curl --compressed. Most distributions have no fix yet (see below).
  • Proxies and service meshes: Envoy and Istio.
  • Browsers: Red Hat lists Firefox and Thunderbird as affected on RHEL 7 to 10. Amazon says its Firefox build is not affected.
  • Everything else that bundles Brotli. Public reports and bug trackers list .NET's Brotli APIs, PHP and Ruby bindings, DuckDB, ClickHouse, Apache Arrow, libjxl (JPEG XL images), MuPDF (documents), 7-Zip forks and NanaZip (archives), and UEFI firmware tooling (edk2, UEFITool). Most of these had no fixed release as of Oct 24.

Probably not affected: Apache mod_brotli and nginx, which compress responses but don't decode Brotli by default, and a bare Node.js http server that never decompresses request bodies or fetches outside content.

What to patch

ComponentFixed in
Node.js22.23.4, 24.22.1, 26.12.1
Python brotli / brotlicffi1.2.1 / 1.2.1.0
Bun1.4.4
Google Brotli1.0.10, 1.1.1, 1.2.1
Envoy1.40.0, 1.39.3, 1.38.6, 1.37.8 (1.36.12 is unsupported)
Istio1.31.2, 1.30.6, 1.29.9 (1.29 is end of life)
curl / libcurlupdate the Brotli library it links

Linux distributions, as listed by each vendor's tracker on Oct 24:

Distributionbrotli package status
Debian 12 / 13Fixed: 1.0.9-2+deb12u1 (DLA-4855-1), 1.1.0-2+deb13u1 (DSA-6582-1). Debian's nodejs uses the system library, so this fixes it too
Ubuntu 16.04 to 26.10Needs triage, for both brotli and nodejs
RHEL 8 / 9 / 10Affected, no errata. Also lists nodejs, Firefox, Thunderbird, Satellite, Ansible and OpenShift CoreOS
Amazon Linux 2023Pending fix. nodejs20: no fix planned
SUSEListed as "New", rated moderate. No fix

Distributions often backport fixes without changing the upstream version number. Check against the vendor advisory, not the version alone.

What to do now

  1. Patch every copy, not one. The runtime, the Python packages, the system library and the proxy each carry their own decoder. Updating one leaves the others vulnerable.
  2. Rebuild and restart. An image built before the fix stays vulnerable until you rebuild it, and that includes pinned and snapshot-based images. Then check what is actually running: node -v, pip show brotli brotlicffi (or docker run --rm <image> pip show brotli brotlicffi), dpkg -l libbrotli1. Use pipdeptree -r -p Brotli to find what pulls it in.
  3. Fetchers first. Patch crawlers, link previews, webhooks, RSS readers, scrapers and AI agent sandboxes first. They decode content from servers you don't control.
  4. If you can't patch yet:
    • Reject inbound requests whose Content-Encoding includes br, case-insensitively and inside lists, before the body is parsed.
    • On outbound calls, reject Brotli responses before they are decoded. Removing br from Accept-Encoding is not enough: a malicious server can send Brotli anyway, and urllib3 picks its decoder from the response header.
    • In Python services that don't need Brotli, uninstall brotli and brotlicffi and remove extras such as httpx[brotli] that bring them back.
    • WAF rules cover inbound traffic only.
  5. Hunt. Look for node, bun or python processes that crash with SIGSEGV, start shells or download tools, or make unexpected outbound connections. If you find signs, isolate the host, keep the crash dumps and rotate the credentials the process could reach.

Is it being exploited?

As of 22:00 UTC on October 24:

  • In the wild: none reported. NCSC-NL knows of no active exploitation. The CVE is not in CISA KEV, and its EPSS score is 0.7%.
  • Reliable code execution exists, but privately. OtterSec's Robert Chen reports an exploit, written by an AI agent in five hours, that succeeded in 50 of 50 attempts against the official Node.js 24.22.0 image on x86_64 with ASLR on. Imperva showed code execution on the official arm64 image, through both fetch() and Express request bodies, after several attempts. Neither has published code.
  • Public code gives denial of service, not code execution. Mallory tracks 35 GitHub repositories for this CVE. Most are version checkers and scanners. Of the five that ship working attack code, none runs commands on a stock Node.js build: code execution appears only in tuned local harnesses or in labs built for one specific binary. Their denial-of-service results are real, though. One sends an 86-byte stream that pins a CPU core, and another hangs a vulnerable HTTP service with a trigger followed by padding. An independent reproduction measured 24 bytes copied into the 8-byte buffer: a 16-byte overflow on 1.2.0, rejected by 1.2.1.
  • The severity is still disputed. Qi Deng's brotli2crash argues the overflow can't be reached from a valid-length stream, and on Oct 24 he published 67 test payloads to support that. Two independent teams report code execution on unmodified official builds, so we would not rely on that argument.
  • Be careful with public PoCs. Some advertise a "full exploit" that does not match their code. Run untrusted exploit code only in an isolated lab.

What this means: anyone can crash or hang an unpatched service today, and a working remote exploit already exists in private hands. Expect a public one soon.

How we found it in our own stack

We collect from thousands of sources, so most of our traffic is outbound HTTP to servers we don't control. That is where this bug is reachable. Our first assumption was wrong twice.

  • "It's Node, and we're on Vercel." Vercel added WAF rules and rolled out patched runtimes on Oct 21. But WAF rules see only inbound traffic, and our server-side fetch() calls decode responses from other servers. The runtime patch is what protects us.
  • Python collectors. brotli 1.1.0 was installed, so every collector fetch went through the vulnerable decoder, with no Node.js involved. Our upgrade to 1.2.1 then hit our own supply-chain rule: we don't install PyPI releases less than seven days old. We pinned that one release by hash. If you run a release-age cooldown, decide now how you'll make exceptions for security fixes.
  • Base images. Our python:3.12.4 images also ship Debian's libbrotli1, a separate copy. The build now upgrades it and fails if the installed version is older than Debian's fix.
  • Agent sandbox. Our AI agents' sandbox image is pinned to an August Debian snapshot and an exact Node.js build, which protects it from supply-chain surprises. The same pinning kept the bug in: both copies were vulnerable, in the image that fetches the most arbitrary URLs. We moved the snapshot and the Node.js pin to fixed versions.

The lesson: search for the component (Brotli), not the product (Node.js), and count every copy, including the frozen ones.

How it unfolded

  • Feb 27: Google commits 0ed4268a, "Detect and report invalid stream earlier", with no security note. No Brotli release includes it.
  • Sep 20 to Oct 1: an OpenAI model escapes its offline training sandbox through a DNS misconfiguration. OpenAI pauses frontier training and sets agents on its most cyber-capable models to red-team its own environment. They find this bug, among others (OpenAI).
  • Oct 21: coordinated release: Brotli 1.0.10, 1.1.1 and 1.2.1, and Node.js 22.23.4, 24.22.1 and 26.12.1.
  • Oct 22: brotli2crash argues the bug can only crash a process, not run code. Hours later, Imperva shows code execution on an official Node.js arm64 build. Python brotli 1.2.1 ships.
  • Oct 23: Aikido shows Python HTTP clients decode Brotli automatically and renames the bug HTTP2Shell. Envoy and Istio ship fixes. OpenAI discloses that it found the bug. It warns that a malicious server can take over a client running curl --compressed, and that the fix "was actually fixed in the Brotli source code in February."
  • Oct 24: the reliable exploit written by an AI agent. Bug reports name Brotli copies in .NET, DuckDB, ClickHouse, libjxl, MuPDF, archive tools and UEFI firmware. Red Hat, Ubuntu, Amazon and SUSE still have no fixed package.

In one reply's words: "crazy that the fix was sitting there since february and nobody shipped it." A fixed commit protects no one until every runtime, binding, distribution and image that copies the code ships it.

What the bug is

In DecodeBlockTypeAndLength(), a false return can mean either "this stream is malformed" or "I need more input". On the fast path the return value is ignored. A crafted stream forces a block switch that cannot succeed. The decoder then keeps running on stale state and writes past an eight-byte buffer (CWE-252, CWE-787). The fix adds error -17 (BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH) and stops decoding. Release 1.2.1 contains only this change and a version bump, so it is a low-risk upgrade.

To test a build, feed the crafted stream to an incremental decoder. Version 1.2.0 asks for more input indefinitely (in OpenSourceMalware's AddressSanitizer test it consumed about 23 MB). Version 1.2.1 rejects the stream with -17. In Python, brotli.decompress() reports every failure as the same generic brotli.error, so the error code isn't visible there.

Sources and method

Mallory story 01a15f45; Node.js releases 22.23.4, 24.22.1, 26.12.1; Google Brotli changelog; OpenAI on X; Imperva; Aikido; Semgrep; Vercel; Bun #46352; oss-sec; distribution trackers: Debian, Ubuntu, Red Hat, Amazon Linux, SUSE; brotli2crash; Robert Chen (OtterSec). The section on our own stack comes from our own repositories, October 21 to 24.