Are you Mythos ready?

Read our latest report on validating and fixing vulnerabilities at machine speed.

Read the report
Read the report
Book a demo
Book a demo
For more information about how ZEST processes personal data, check out our Privacy Policy.
No items found.

HTTP2Shell / Node2Shell (CVE-2026-111497): find every Brotli copy

Dvir Sasson
Dvir Sasson
VP, Al & Security Research

This is a developing story. We will keep updating this post as vendors ship fixes and as exploitation details change. Last updated October 28, 2026, 11:21 UTC.

TL;DR

  • On October 26, ELLIO's sensors caught an attack aimed at Node.js 24.22.0 on x86-64. It failed in ELLIO's own tests, and no successful compromise has been reported. CISA added the CVE to its Known Exploited Vulnerabilities catalog on October 27, with a November 10 deadline for US federal agencies. Patch now.
  • Imperva reports running code carried in a Brotli response or request body inside applications on the official Node.js 24.22.0 Linux arm64 image; it took multiple attempts, and Node.js 24.22.1 rejects the same input. ELLIO reproduced code execution on x86-64 against the official Node.js 24.22.0 binaries with default settings and memory-layout randomization on: a plain node:http server, the node:24.22.0-slim image, Express 5.3.0 and Koa 3. Each Node.js build needs its own memory offset, so attackers work one out per build and reuse it.
  • Fixed in Brotli 1.0.10, 1.1.1 and 1.2.1, and in Node.js 22.23.4, 24.22.1 and 26.12.1, which bundles its own copy. Bun 1.4.4, .NET 8.0.33, 9.0.22 and 10.0.14, Python Brotli 1.2.1, brotlicffi 1.2.1.0 and the Ruby brotli gem 0.9.1 ship their own fixes.
  • Your code does not have to call Brotli to be exposed. Node's fetch() decodes br responses on its own, and so do requests, urllib3 and aiohttp whenever a Brotli package is installed.
  • On October 26, Shodan fingerprinted about 1.26 million internet-facing hosts as Node.js and flagged none of them for CVE-2026-111497. Banners do not show which decoder a server runs, so the search starts from your own inventory.

On October 21, Google tagged Brotli 1.0.10, 1.1.1 and 1.2.1, and Node.js released 22.23.4, 24.22.1 and 26.12.1 the same day. The root cause was fixed in Brotli's source in February 2026, but no Brotli release carried it until October 21.

The bug is also tracked as Node2Shell, Broccoli2Shell and Brotli2Shell. In the days after the release, OpenAI's Codex Security team, which reported the bug, described it as remote code execution in both directions, Imperva reported code execution on an official Node.js image for arm64, ELLIO repeated it on x86-64 and then recorded the first attack, and JFrog showed that HTTP is only one way in. Projects that vendor the Brotli C decoder are patching one at a time, and several still have not shipped a fix.

So there is no single "update Brotli" button. This post explains how the Brotli vulnerability works, where the copies of the decoder live, why internet scans miss them, and how to check and fix each one.

HTTP2Shell status: what is confirmed

Confirmed

  • On October 26, ELLIO's sensors caught an attack aimed at Node.js 24.22.0 on x86-64. It failed in ELLIO's own tests, and no successful compromise has been reported. CISA added the CVE to its Known Exploited Vulnerabilities catalog on October 27, with a November 10 deadline for US federal agencies. (source)
  • CISA added CVE-2026-111497 to its Known Exploited Vulnerabilities catalog on October 27, 2026, with a due date of November 10, 2026. (source)
  • Fixed in Brotli 1.0.10, 1.1.1 and 1.2.1, and in Node.js 22.23.4, 24.22.1 and 26.12.1, which bundles its own copy. (source)
  • ELLIO reproduced code execution on x86-64 against the official Node.js 24.22.0 binaries with default settings and memory-layout randomization on: a plain node:http server, the node:24.22.0-slim image, Express 5.3.0 and Koa 3. Each Node.js build needs its own memory offset, so attackers work one out per build and reuse it. (source)
  • Imperva reports running code carried in a Brotli response or request body inside applications on the official Node.js 24.22.0 Linux arm64 image; it took multiple attempts, and Node.js 24.22.1 rejects the same input. (source)
  • Before the attack, a public checker named node2shell-check sent Brotli request bodies to ELLIO's sensors to find hosts that decompress them. (source)
  • In ELLIO's lab most failed attempts crashed the Node.js worker, and where core dumps are kept, the dump holds whatever secrets were in memory, such as other users' session tokens. (source)
  • OpenAI, which reported the bug, says an unpatched server that accepts compressed input can be taken over by a malicious client, and an unpatched client that accepts compressed input can be taken over by a malicious server. (source)
  • The upstream fix is google/brotli commit 0ed4268 (February 27, 2026, "Detect and report invalid stream earlier."), which adds the hard format error BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17). (source)
  • Debian's tracker notes that its nodejs and python-brotlicffi packages use the system brotli library, and Ubuntu notes that nodejs on jammy and later uses the system brotli; for those packages the libbrotli update is the fix. (source)
  • Cloudflare's emergency managed rules (October 25) default to Log, and the Brotli rule matches request bodies sent with Content-Encoding: br, not responses. (source)

Reported

  • JFrog reached the bug through files as well as HTTP: FFmpeg via libjxl (JPEG XL metadata), ClickHouse (crash), pyarrow (CPU loop) and .NET Brotli APIs. (source)
  • For Python, researchers confirmed the bug with AddressSanitizer: Brotli 1.2.0 accepted a crafted stream of about 23 MB and 1.2.1 rejected it with error -17. (source)

Severity ratings differ by vendor. Google rates the bug 9.2 under CVSS 4.0, Node.js calls it high, and Red Hat and Microsoft each score it 8.1 under CVSS 3.1. Microsoft's advisory for .NET lists information disclosure, denial of service and elevation of privilege, not remote code execution.

How the Brotli decoder bug works

A Brotli stream is cut into blocks, and the decoder switches between block types as it reads. The upstream fix is google/brotli commit 0ed4268 (February 27, 2026, "Detect and report invalid stream earlier."), which adds the hard format error BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17).

Before that fix, the function that decodes a block switch returned a plain true or false, so its callers could not tell "this stream is malformed" apart from "I need more bytes". On the fast path the return value was ignored altogether. A crafted stream can ask for a block switch that cannot happen. The decoder treats it as a request for more input and keeps appending attacker bytes to an eight-byte buffer inside its own state, writing past the end and corrupting the fields next to it.

What happens next depends on the program around the decoder: a crash, a memory leak, or, as Imperva and ELLIO showed on official Node.js builds, code execution. A failed attempt is not harmless either. In ELLIO's lab most failed attempts crashed the Node.js worker, and where core dumps are kept, the dump holds whatever secrets were in memory, such as other users' session tokens. The bug is tracked as CWE-252 (unchecked return value) and CWE-787 (out-of-bounds write).

The name HTTP2Shell is misleading. The bug has nothing to do with the HTTP/2 protocol. HTTP stacks reach it because they decompress Content-Encoding: br bodies. Broccoli2Shell, another name for the same bug, tracks it across projects.

Who is exposed to HTTP2Shell

You are exposed if your code, or any library it uses, decompresses Brotli data that someone else controls. OpenAI, which reported the bug, says an unpatched server that accepts compressed input can be taken over by a malicious client, and an unpatched client that accepts compressed input can be taken over by a malicious server.

HTTP2Shell, two ways in: an attacker sends a Content-Encoding: br request body that your server decodes, or your app fetches a br response from a malicious server. The side that decodes the data suffers memory corruption.

Your app fetches a URL. Node's built-in fetch() decodes br responses automatically. In Python, urllib3 (and so requests) and aiohttp decode Brotli whenever the Brotli or brotlicffi package is installed, even if your code never imports it. curl --compressed decodes them too when curl links an unpatched libbrotli. Webhooks, link previews, crawlers and "import from URL" features all let an outside server pick the response.

Your app accepts request bodies. body-parser 2.0.0 and later inflate Brotli request bodies, and Express 5 depends on body-parser 2.x; a bare node:http server does not decode request bodies by itself. Express and body-parser do not contain the bug, so upgrading them does not fix it. They are one path to the decoder inside Node.js.

Your app calls the decoder directly. zlib.brotliDecompress(), createBrotliDecompress(), BrotliStream, brotli.decompress(), and any library that uses them on archives, caches or queue messages. JFrog reports reaching the bug through files as well as HTTP: FFmpeg through libjxl and JPEG XL metadata, a pyarrow CPU loop, the .NET Brotli APIs, and a ClickHouse crash through an HTTP request body. Filtering br at the network edge does not cover the file-based paths.

Every Brotli copy has its own fix

One Brotli decoder at the center of HTTP2Shell, with copies inside Node.js, Bun, .NET, Envoy and Istio, Python, OS packages, FFmpeg, ClickHouse and Arrow, and container images

Brotli is a C library that gets vendored as often as it gets linked. The official Node.js builds compile their own copy into the node binary, so for them only the Node.js version matters. Bun does the same. The Python wheels carry one, .NET carries one inside the runtime, and your distribution's libbrotli1 or brotli-libs is yet another. Debian's tracker notes that its nodejs and python-brotlicffi packages use the system brotli library, and Ubuntu notes that nodejs on jammy and later uses the system brotli; for those packages the libbrotli update is the fix.

  • Google Brotli (C library) · fixed in: 1.0.10, 1.1.1, 1.2.1 · exposed when: it decodes Brotli data someone else controls
  • Node.js (official builds) · fixed in: 22.23.4, 24.22.1, 26.12.1 · exposed when: fetch() of a br response, node:zlib, or a framework that decodes br request bodies; the advisory covers only 22, 24 and 26, and 18, 20, 23 and 25 are end of life
  • Bun · fixed in: 1.4.4 (every earlier release is affected) · exposed when: fetch() of a br response, node:zlib, DecompressionStream
  • .NET · fixed in: 8.0.33, 9.0.22, 10.0.14 · exposed when: Brotli turned on in ASP.NET Core request decompression or HttpClient, or BrotliStream on untrusted data; off by default; .NET Framework is not affected
  • Envoy · fixed in: 1.40.0, 1.39.3, 1.38.6, 1.37.8, 1.36.12 · exposed when: the Brotli decompressor is configured
  • Istio · fixed in: 1.31.2, 1.30.6, 1.29.9 · exposed when: Brotli decompression is enabled, for example through an EnvoyFilter
  • Python Brotli / brotlicffi · fixed in: 1.2.1 / 1.2.1.0 · exposed when: installed, and an HTTP client decodes br
  • Ruby brotli gem · fixed in: 0.9.1 · exposed when: Brotli.inflate on outside data
  • cdxgen (standalone binary or image) · fixed in: 12.8.9, 13.5.0 · exposed when: scanning untrusted input
  • Debian 12 / Debian 13 · fixed in: 1.0.9-2+deb12u1 / 1.1.0-2+deb13u1 · exposed when: any program linked to the system library

Debian 11 (1.0.9-2+b2) does not appear in Debian's tracker for this CVE. Ubuntu lists every release as needing evaluation, and Amazon Linux reports fixed brotli and Node.js 24 packages for Amazon Linux 2023, with no fix planned for its nodejs and nodejs20 packages. ClickHouse has merged fixes into its release branches but had not shipped a fixed release as of October 26, and Apache Arrow's C++ library bundles Brotli 1.0.9 with no fix yet.

Projects that bundle a private copy need their own release, and a distribution update does nothing for them. Broccoli2Shell, which reads the vendored headers directly, lists DuckDB, Apache Arrow's bundled build, 7-Zip-zstd, p7zip, EDK II and the woff2 Makefile build as still unpatched on October 26. It lists the PHP brotli extension 0.21.1, MuPDF 1.28.0 and UEFITool A76 as fixed, and NanaZip, GraalJS, YDB and libjxl as fixed on their main branches without a release yet. Its authors say the list is incomplete, because code search misses submodules and copies fetched at build time.

Decoders rewritten in other languages behave differently. According to Broccoli2Shell, the Java, JavaScript and C# ports cannot reach the bug, Rust's brotli-decompressor returns an error, and the Go port (andybalholm/brotli) had the same logic error but could only crash; it was fixed on October 23. Code execution needs the C decoder.

Do not wait for a dependency scanner to tell you about the Python packages. The CVE record names Google's repository, not the PyPI packages, and advisory entries for them (PYSEC-2026-4322 and PYSEC-2026-4323) only arrived on October 23.

Why internet scans miss HTTP2Shell

Shodan search for http.component:"Node.js" on October 26, 2026: about 1.26 million hosts, with banners such as X-Powered-By: Express that carry no Node.js version. Host details are blurred.

On October 26, Shodan fingerprinted about 1.26 million internet-facing hosts as Node.js. Its vuln:CVE-2026-111497 search returned no hosts that day, while the same filter returned about 1.04 million hosts for regreSSHion (CVE-2024-6387). Shodan's CVE database lists no affected products for this CVE, and the banners give it nothing to match on: Express servers send X-Powered-By: Express with no Node.js version, and nothing in a response shows whether a server decodes br request bodies.

Attackers are not waiting for Shodan. Before the attack, a public checker named node2shell-check sent Brotli request bodies to ELLIO's sensors to find hosts that decompress them. Once a host is known to decode Brotli bodies, what remains is matching its Node.js build, and ELLIO notes that versions leak through headers, lockfiles and container tags.

An empty result means a banner scan cannot see the bug, not that the hosts are patched. Outbound fetch() calls, Python clients and file parsers never show up in a banner at all. The copies have to be found from the inside: in package inventories, build manifests and container images.

How to check for CVE-2026-111497

Check everywhere code runs: production, staging, CI runners, build images, serverless runtimes and developer laptops.

Version strings mislead. Debian's fixed 1.0.9-2+deb12u1 sorts below the "fixed" 1.0.10, and the unfixed Debian 11 build 1.0.9-2+b2 differs from the fixed Debian 12 build only in its suffix. Comparing against upstream version ranges gets both wrong. Fixed Node.js releases also keep reporting the old Brotli version in process.versions.brotli, so check the Node.js version, not that field.

node.js
node --version
# 22.x is affected below 22.23.4, 24.x below 24.22.1, 26.x below 26.12.1
# 18.x, 20.x, 23.x and 25.x are end of life: move to a supported line
# distro nodejs packages (Debian, Ubuntu 22.04 and later) use the system library: check it below
python
pip show Brotli brotlicffi
# affected: Brotli below 1.2.1, brotlicffi below 1.2.1.0
system library
dpkg -l | grep -i brotli          # Debian, Ubuntu
rpm -qa | grep -i brotli          # RHEL, Fedora, Amazon Linux
apk info -v | grep -i brotli      # Alpine

On Debian, compare against the fixed builds above, not the upstream version. On Ubuntu, Alpine and the RHEL family, check your distribution's tracker before you assume a package is fixed.

How to fix HTTP2Shell

Update to a fixed version from the list above, then restart every process and rebuild every image. A running process keeps the old decoder until it restarts, and an image keeps it until you rebuild from a fixed base.

For Python, upgrade whichever of the two packages pip show found, and only that one, so you do not switch on Brotli decoding where it was off:

python upgrade
pip install -U "Brotli>=1.2.1"          # if Brotli is installed
pip install -U "brotlicffi>=1.2.1.0"    # if brotlicffi is installed

Then raise the minimum version in requirements.txt or pyproject.toml and regenerate poetry.lock or uv.lock, or the next build reinstalls the vulnerable version. For Node.js, move through your version manager or base image. Self-contained and NativeAOT .NET apps must be rebuilt and republished; apps on the shared runtime need the runtime update and a restart.

Patch first anything that fetches URLs you do not control or accepts Content-Encoding: br request bodies.

Workarounds if you cannot upgrade yet

Inbound traffic is the easier half. Reject request bodies sent with Content-Encoding: br at your proxy, load balancer or WAF, before any parser decodes them. Match the header case-insensitively and as a list, since gzip, br is legal, and expect to block some legitimate uploads. Cloudflare's emergency managed rules (October 25) default to Log, and the Brotli rule matches request bodies sent with Content-Encoding: br, not responses. Switch those rules to block if you rely on them.

Outbound traffic is the hard half. Removing br from Accept-Encoding alone does not stop a server from sending Brotli. Vercel's WAF rules do not cover responses your functions fetch from other servers. For Python, uninstall Brotli and brotlicffi so requests, urllib3 and aiohttp fall back to gzip, and drop extras such as httpx[brotli] so the next install does not bring them back. For .NET, remove the br provider from request decompression and DecompressionMethods.Brotli from HttpClient handlers, including handlers set to DecompressionMethods.All, which turns Brotli on. For Node.js there is no published workaround: upgrading the runtime is the only complete fix for this Node.js RCE.

How ZEST handles this

For every copy an inventory can see, ZEST works out the fix path per system. A Debian 12 host is told to install 1.0.9-2+deb12u1, an Ubuntu host is marked as waiting on Ubuntu, and a laptop with two Node.js installs gets both on its list. Each affected customer gets those systems with links to their findings, and systems that no scanner has reported yet get findings of their own, tracked in a campaign until they are fixed. We ran the same playbook for PHP2Shell.

HTTP2Shell FAQ

Is HTTP2Shell an HTTP/2 vulnerability?

No. The bug is in Google's Brotli decoder. HTTP clients and servers reach it when they decompress Content-Encoding: br bodies, over any HTTP version.

Does upgrading Express or body-parser fix it?

No. Express and body-parser only hand the body to the decoder inside Node.js. Upgrade Node.js itself, or the system Brotli library if your Node.js comes from Debian or Ubuntu.

Does an OS package update fix Node.js?

Only for distribution Node.js packages that use the system library, such as Debian's and Ubuntu's on 22.04 and later. Official Node.js builds carry their own copy and need Node.js 22.23.4, 24.22.1 or 26.12.1.

Is HTTP2Shell exploited in the wild?

Yes, treat it as exploited. On October 26, ELLIO's sensors caught an attack aimed at Node.js 24.22.0 on x86-64. It failed in ELLIO's own tests, and no successful compromise has been reported. CISA added the CVE to its Known Exploited Vulnerabilities catalog on October 27, with a November 10 deadline for US federal agencies. The attack copied its delivery from public exploit code, and ELLIO confirmed that the public code gets a shell on vulnerable builds.

Sources

About The Author

Dvir Sasson

Dvir Shimon Sasson is the VP of AI & Security Research at ZEST Security, where he drives innovation at the nexus of AI, autonomous agents, and cybersecurity. With over a decade of hands-on expertise across offensive and defensive security, his specializations include red team operations on AI systems and agents, incident response, threat intelligence, cloud security, and pioneering research into agentic security automation and emerging risks like Shadow AI. Certified CISSP and OSCP, Dvir is passionate about problem-solving, developing intelligent automation scripts in Python and PowerShell, and dissecting the mechanics of breaking things to build more resilient, AI-native defenses.

More Resources

View more
View more
BOOK a demo

Ready to see
ZEST in action?

"With ZEST, we can proactively resolve attack paths and quickly address cloud vulnerabilities"
Alexander Scheer Head of Cybersecurity @ Odyssey Therapeutics
Alexander Scheer
Head of Cybersecurity
@ Odyssey Therapeutics