CVE-2026-111497, also called Node2Shell, HTTP2Shell or Brotli2Shell, is an out-of-bounds write in Google's C Brotli decoder (DecodeBlockTypeAndLength in c/dec/decode.c). It is fixed in Brotli 1.2.1. A crafted Brotli stream can give an attacker remote code execution in any process that decodes it. It has a CVSS score of 9.2, it has been in the CISA Known Exploited Vulnerabilities catalog since 2026-10-27, and it is being exploited.
You are exposed in two ways:
- Servers that decode request bodies sent with
Content-Encoding: br: Express 5 / body-parser 2.x (raw(),json()…) on Node.js before 22.23.4, 24.22.1 and 26.12.1, ASP.NET Core request decompression, the ClickHouse HTTP interface, Python services that usebrotliorbrotlicffi, and the PHPbrotliextension. - HTTP clients that decode Brotli responses: Node.js
fetch(), .NETHttpClient, and Pythonrequests,httpx,aiohttporurllib3whenbrotliis installed. A server the attacker controls only has to answer with a crafted body.
What the edge does
The edge has a new shield, 1009 «Brotli Request Body». It refuses every request that has a body whose Content-Encoding lists br, before the request reaches your origin. The header is matched in any case, and br is found anywhere in the list: gzip, br or across several Content-Encoding lines. The request gets a 403 with your block page. The edge doesn't decode request bodies to look for the exploit.
Nothing else changes:
- requests without a body, and bodies encoded with
gzip,deflate,zstdoridentity, pass as before; - responses aren't touched;
- browsers never send Brotli request bodies, so visitors aren't affected.
Like every shield, 1009 follows the site's mode: it blocks in active, only records in log (the request reaches the origin) and doesn't run when the WAF is off.
The edge's own Brotli decoder is a Go port. It has the same logic bug but no memory corruption (in Go it was a crash, GHSA-c9gr-gmg4-2vqv). It has been updated, and an error while decoding now only ends that stream.
If your application receives Brotli request bodies
Some APIs legitimately accept bodies compressed with Brotli, for example an ingestion endpoint fed by your own agents. Patch that origin first (below), then add a WAF exception for rule 1009 on that path only, limited to the name that needs it. An exception with rule_id 0 does not switch the shield off.
Domain → Security → Firewall → Rules → WAF exceptions → Add WAF exception. Enter 1009 as the WAF rule, the Path (for example /v1/ingest), the name in Applies to and a Note.
openwaf-cloud ctl rules add example.com kind=waf_exception rule_id=1009 path_pattern='/v1/ingest' \
hosts:='["api.example.com"]' note="Patched origin, accepts br bodies" --reason "CVE-2026-111497: br bodies on /v1/ingest"curl -sS -X POST "https://dash.openwaf.dev/v1/orgs/$ORG/zones/example.com/rules" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "X-Change-Reason: CVE-2026-111497: br bodies on /v1/ingest" \
-H "Content-Type: application/json" \
-d '{"kind": "waf_exception", "rule_id": 1009, "path_pattern": "/v1/ingest", "hosts": ["api.example.com"], "note": "Patched origin, accepts br bodies"}'add_rule with {"zone": "example.com", "kind": "waf_exception", "rule_id": 1009, "path_pattern": "/v1/ingest", "hosts": ["api.example.com"], "reason": "CVE-2026-111497: br bodies on /v1/ingest"}; simulate, then confirm.
What the edge doesn't cover
- Responses your code fetches. If your servers or scripts call
fetch()or another HTTP client on a URL an attacker can choose, the crafted Brotli response goes straight to them. The edge only sees requests to your proxied names. - Traffic that skips the edge. Anyone who reaches your origin directly, by its IP address or another name, isn't inspected. Accept connections only from the OpenWAF edge and use Full (strict) origin TLS.
- Names that aren't proxied, sites in
logmode and sites with the WAFoff. - Other ways of feeding Brotli data to your software: uploaded files, queues, message brokers, files in storage.
What you must patch
| Software | Fixed in |
|---|---|
| Node.js | 22.23.4, 24.22.1 or 26.12.1 and later. Lines that are end-of-life get no fix: move to a supported line. Check node -v inside your container images too, not just on the host. |
Python brotli / brotlicffi | The releases built on Brotli 1.2.1. |
| PHP | The php.net releases of 2026-10-26. |
| .NET | The October 2026 servicing update. |
libbrotli from the OS | Your distribution's security update; then restart the services that load it. |
| Anything else that embeds Brotli before 1.2.1 | Its patched release: FFmpeg, libjxl, ClickHouse, Apache Arrow and others. |
Check whether you were targeted
Refused requests appear in security events with rule_id 1009 and rule_name Brotli Request Body. In Analytics › Traffic → Security events, select Add filter → Rule is 1009, or ask the API:
curl -s "https://dash.openwaf.dev/v1/orgs/$ORG/zones/$ZONE/events?rule_id=1009&since=2026-10-27T00:00:00Z" \
-H "Authorization: Bearer $OPENWAF_TOKEN"An event before you patched only tells you that someone tried through the edge. It doesn't tell you whether your origin was reached another way. Check the origin's own logs too.
Sources
- Node.js: October 2026 security releases (external link)
- Google Brotli: fix commit 0ed4268 (external link)
- Imperva: Node2Shell, from Brotli data to remote code execution (external link)
- JFrog: Brotli2Shell, beyond Node2Shell and HTTP2Shell (external link)
- Sophos: Node2Shell in active exploitation (external link)