Issue Nº 01 — October 15, 2026

@readmodelmag
MODEL

Follow MODEL on X @readmodelmag

Tools

Patched and unpatched Node both say brotli 1.1.0. Here's how to check for Node2Shell.

SummaryThe Brotli decoder flaw known as Node2Shell (CVE-2026-111497) sits inside Node.js, the PyPI Brotli packages and plenty of HTTP clients. Your app doesn't have to import Brotli to be exposed. We checked Node and Python on a Linux box and found the version numbers you'd look at first can mislead you.

· MODEL Editors · 4 min read

Some links may be affiliate links. Disclosure

MODEL builder tip card titled Patched and unpatched Node both say brotli 1.1.0. The version string won't tell you. One constant will. Left panel, Node v20.19.2: process.versions.brotli returns 1.1.0; zlib.constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH returns undefined; verdict unpatched, Node 20 is end of life, no fix. Right panel, Node v22.23.4: process.versions.brotli returns the same 1.1.0; the constant returns -17; verdict patched, fix cherry-picked October 21. Below: in Python, requests decoded a Brotli response with Brotli 1.2.0 installed even though our code never imported brotli. pip-audit 2.10.1 now flags it as PYSEC-2026-4322 and GHSA-9v8h-qqgm-4m35, exit 1, 0.4 seconds. Fix: Node 22.23.4, 24.22.1 or 26.12.1; Brotli 1.2.1 or later; brotlicffi 1.2.1.0 or later; then rebuild images. Catch: uninstalling Brotli removed br from Accept-Encoding, but a server that sent br anyway got status 200 and garbled text with no error. Distro fixes keep the old version number, for example Debian 1.1.0-2+deb13u1. Footer: tested by MODEL Editors, October 24, 2026.
Our own terminal output from October 24, 2026, trimmed. Illustration by MODEL. Original. Image source.

Brotli is the compression format most of the web uses next to gzip. On October 21 Google shipped Brotli 1.2.1, 1.1.1 and 1.0.10 to fix CVE-2026-111497, an out-of-bounds write in the decoder in versions 1.0.3 through 1.2.0. The GitHub advisory rates it critical (CVSS 4.0 score 9.2). Node.js shipped fixed releases the same day and calls the issue high severity: anyone who can get your app to decompress Brotli data they wrote can corrupt memory, which "can lead to a crash, disclosure of process memory, or remote code execution." The Node.js team credits OpenAI Codex Security with the report. Today Aikido's write-up on how far the bug reaches into Python and other HTTP stacks was on the Hacker News front page.

Why builders should care: agents and AI apps fetch a lot of URLs they don't control. Webhooks, link previews, crawlers and "read this page" tools all decode whatever the other server sends back. Here's how to check, with the gotchas we hit.

Node: don't trust the version string. node -p process.versions.brotli printed 1.1.0 on both Node 20.19.2 and Node 22.23.4. Only one of them has the fix. The fixed Node releases (22.23.4, 24.22.1, 26.12.1) didn't move to a new Brotli version. They cherry-picked the upstream fix (commit 0ed4268) and kept the old number. Those release notes also list a new constant, and that's the check that worked for us:

node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH"

It printed -17 on Node 22.23.4 and undefined on Node 20.19.2. Node 20 reached end of life on April 30, 2026, so it got no fix. Node's advisory says end-of-life versions "are always affected." Check every Node you actually run: CI images, Dockerfiles, a global install and the ones npx downloads. We found six node binaries on one Linux box.

Python: you don't have to import Brotli. We installed requests and Brotli==1.2.0 in a fresh virtual environment and pointed it at a local test server that always answers with a Brotli-encoded body. Our script never imported brotli. urllib3 2.8.0 still sent Accept-Encoding: gzip, deflate, br and requests handed back the decoded text. Its source shows why: urllib3 adds br whenever brotli or brotlicffi is installed. The httpx extra does the same thing. In a small uv project, httpx[brotli] locked against packages from before October 20 pinned brotli 1.2.0 (and brotlicffi 1.2.0.2 for non-CPython). uv tree --invert --package brotli showed httpx's extra as the source.

pip-audit catches it now. Early write-ups, including Aikido's, said audit tools wouldn't flag this yet. When we checked on October 24, GitHub's advisory had been reviewed on October 23, and OSV listed brotli below 1.2.1 and brotlicffi below 1.2.1.0. pip-audit 2.10.1 reported brotli 1.2.0 as PYSEC-2026-4322 and GHSA-9v8h-qqgm-4m35 and exited 1. It took about 0.4 seconds against a pinned requirements file and 0.7 seconds against our exported lockfile. Then uv lock --upgrade-package brotli --upgrade-package brotlicffi moved only those two packages, to 1.2.1 and 1.2.1.0, and pip-audit exited 0.

The fix. Node 22.23.4, 24.22.1 or 26.12.1 (or newer in that line). In Python, Brotli>=1.2.1 and brotlicffi>=1.2.1.0. Then regenerate lockfiles, rebuild container images and restart services. A fixed lockfile doesn't help an image built last week.

The catches.

  • pip-audit only checks for the interpreter it runs on. Our lockfile export pinned brotlicffi==1.2.0.2 with a non-CPython marker. On our CPython, pip-audit said "No known vulnerabilities found" for that line. The same line without the marker was flagged as PYSEC-2026-4323. If you ship PyPy, audit it on PyPy or just bump both packages. pip-audit also doesn't see the Brotli copies bundled into Node, curl or other native software.
  • Distro fixes keep the old number too. Debian's fixed library is 1.1.0-2+deb13u1 on trixie, so a plain "1.1.0" tells you nothing. The machine we tested on still had the unfixed 1.1.0-2+b7, and Debian's tracker still listed its own nodejs packages as vulnerable. For system packages, go by your distro's security tracker, not the upstream version.
  • Uninstalling Brotli is a stopgap, not a fix. With it removed, urllib3 stopped advertising br. But when our test server sent a Brotli body anyway, requests returned status 200 and garbled text with no error. If you go this route, check Content-Encoding before you trust the body.

Our take

Spend five minutes on this today. Run the one-line Node check on every Node you ship, run pip-audit on each Python lockfile, and rebuild your images. If your agent fetches URLs for users, do those services first.

What we did and didn't do: we ran everything above on a Linux box on October 24, 2026, with throwaway virtual environments and a local test server that sends a harmless Brotli-compressed string. We didn't use an exploit payload, didn't test Bun, Envoy, Go or Rust clients, and didn't test macOS or Windows. Facts come from the Node.js security release and release notes, Google's Brotli releases, the GitHub and OSV advisories, Debian's security tracker, urllib3's source and Aikido's write-up, all read today. Not sponsored; no affiliate links.

MODEL Editors

Sources

  1. Wednesday, October 21, 2026 Security Releases

    Node.js

  2. Node.js v22.23.4 release notes

    nodejs/node on GitHub

  3. Brotli v1.2.1 release

    google/brotli on GitHub

  4. GHSA-9v8h-qqgm-4m35: Out-of-bounds write in the Brotli decoder also affects the PyPI Brotli package

    GitHub Advisory Database

  5. OSV entry GHSA-9v8h-qqgm-4m35 (aliases PYSEC-2026-4322, PYSEC-2026-4323)

    OSV

  6. CVE-2026-111497 (Debian security tracker)

    Debian

  7. urllib3 2.8.0 util/request.py (Accept-Encoding and Brotli)

    urllib3 on GitHub

  8. Node.js release schedule

    Node.js Release WG

  9. Node2Shell evolves to HTTP2Shell

    Aikido Security

FREE · EVERY WEEK

The top AI tools this week, tested.

Every week MODEL's editors pick the AI tools worth your time, try them hands-on, and tell you what's good, what's not, and who it's for. Free, in your inbox.

Free. Unsubscribe anytime. Our picks are editorial and can't be bought.

The weekly three is editorial. A subscription does not buy a place in it.