OpenAI's Codex Found a Node.js Brotli RCE. Imperva Ran Code on an Official Build
Book: Ship It — the pragmatic startup tech stack Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub Eight b

Acht Bytes. Das ist die Größe des internen Puffers, über den hinaus der Brotli-Decoder in Node.js laut der Sicherheitshinweisen vom 21. Oktober weiterhin schrieb. Füttert man ihn mit dem falschen komprimierten Datenstrom, kann ein Angreifer, so die Formulierung im Hinweis, "den Prozessspeicher beschädigen, was zu einem Absturz, zur Offenlegung von Prozessspeicher oder zur Ausführung von Code aus der Ferne führen kann".
Die Schwachstelle trägt die Bezeichnung CVE-2026-111497. Der Hinweis schreibt Forschern von OpenAI Codex Security zu, den Fehler gemeldet zu haben. Am 23. Oktober beschrieb OpenAI die Lücke auf X als "eine Schwachstelle, die eine nicht authentifizierte Remote-Code-Ausführung in Brotli ermöglicht". Am selben Tag berichtete The Hacker News, dass Imperva Code über die Lücke im offiziellen Node.js-Image 24.22.0 für Linux arm64 ausführen konnte, ohne Debugger und ohne angepassten Build.
Wer Node 22, 24 oder 26 irgendwo einsetzt, ist mit einem Upgrade gefahrminded: Die Lösung ist ein Update. Wer Node 20 betreibt, erhält keine Korrektur.
Brotli ist das Kompressionsformat hinter Content-Encoding: br. Node bringt eine eigene Kopie des Google-Decoders mit und stellt sie über node:zlib bereit.
Der Hinweis nennt die Ursache in einem Satz: "Ein ungültiger Blockwechsel wurde als temporärer Bedarf an weiteren Eingabedaten gemeldet statt als permanenter Formatfehler, sodass der Streaming-Decoder weiterhin Eingabebytes an einen acht Byte großen internen Puffer anhing." Der Decoder geriet in einen unmöglichen Zustand, hielt dies für wartende weitere Daten und schrieb einfach weiter.
Node.js stuft die Schwachstelle als hoch, nicht als kritisch ein (siehe Hinweis). Einige Medien, etwa Crypto Briefing, haben einen CVSS-Score von 9,2 veröffentlicht. The Hacker News weist darauf hin, dass die Lücke "als hoch und nicht als kritisch eingestuft" ist. Maßgeblich sollte die Einstufung des Anbieters sein.
Die Upstream-Korrektur ist älter als der CVE-Eintrag. Die Änderung landete laut The Hacker News bereits im Februar 2026 im Brotli-Quellcode, und das Changelog von Brotli 1.2.1 vom 21. Oktober führt den CVE unter SECURITY auf. Erst in den Oktober-Veröffentlichungen wurde die Änderung als Sicherheitskorrektur gekennzeichnet.
Acht Slots, mehr Bytes: Der Decoder hängte weiterhin Eingabedaten an einen Puffer an, der keinen Platz dafür hatte.
Man muss zlib nicht selbst aufrufen, um betroffen zu sein. Die Analyse von Imperva nennt zwei Angriffspfade.
Antworten, die man abruft. Die eingebaute fetch()-Funktion von Node erreichte beim Dekodieren einer komprimierten Antwort eines anderen Servers den verwundbaren Decoder. Ein Dienst, der eine Drittanbieter-API aufruft, einer Webhook-URL folgt oder eine von einem Nutzer eingegebene URL abruft, befindet sich auf diesem Pfad, wenn der Server, von dem er abruft, von einem Angreifer kontrolliert werden kann.
Anfragekörper, die man parst. Die Body-Parser von Express 5, express.json(), express.urlencoded(), express.text() und express.raw(), dekodieren Brotli-Anfragekörper neben gzip und deflate (so The Hacker News). Imperva hat die Code-Ausführung auf Express 5.0.0 bis 5.3.0 und body-parser 2.0.0 bis 2.3.0 reproduziert. Ein schlichter node:http-Server dekodiert einen Brotli-Body nur, wenn Middleware oder eigener Code dies übernimmt.
Man sollte das Ergebnis im Verhältnis betrachten. Imperva sagt, "die Ausführung benötigte in unserem Setup mehrere Versuche", und das Team testete nur arm64 auf der 24.x-Linie (so The Hacker News). Ein früherer öffentlicher Proof of Concept führte nur auf einem absichtlich abgeschwächten Build zur Code-Ausführung; auf einem Standardbuild erhielt der Autor einen Absturz und einen begrenzten Speicherlese-Zugriff (so The Hacker News). Bis zum 22. Oktober sei keine Ausnutzung in freier Wildbahn gemeldet worden, so derselbe Artikel. Ein Absturz ist dennoch eine Denial-of-Service-Bedingung, und das arm64-Ergebnis zeigt, dass Code-Ausführung auf einem unveränderten Build erreichbar ist.
Der Hinweis ist zur letzten Zeile unmissverständlich: "End-of-Life-Versionen sind immer betroffen, wenn eine Sicherheitsveröffentlichung erfolgt." Er nennt keine Umgehungsmöglichkeit. Das Upgrade ist die Lösung.
Dieselbe Veröffentlichung behebt neun weitere CVEs, drei davon ebenfalls mit hoher Einstufung: zwei TLS-Zertifikat-Hostname-Umgehungen und eine dns.resolve-Kürzung bei NUL-Zeichen (siehe Hinweis). Ein einzelnes Update deckt alle ab.
Der eigene Laptop ist der einfache Fall. Die Node-Binärdateien in Container-Images, CI-Runnern und VMs sind diejenigen, die eine Abhängigkeitsprüfung übersieht. Imperva rät, die Node-Version in jedem ausgelieferten Image oder Host zu prüfen, weil "ein Paketinventar eine ältere, in einem Container gebündelte Laufzeitumgebung übersehen kann" (Imperva).
Speichere dies als check-node.mjs und führe es mit node check-node.mjs aus, oder innerhalb eines Images mit docker run --rm -v "$PWD":/w your-image node /w/check-node.mjs:
// check-node.mjs: exit 1 if this Node lacks // the CVE-2026-111497 fix const fixed = { 22: [ 23, 4 ], 24: [ 22, 1 ], 26: [ 12, 1 ] }; const [ maj, min, pat ] = process. versions. node. split ( '. ' ). map ( Number ); const f = fixed [ maj ]; const ok = Boolean ( f ) && ( min > f [ 0 ] || ( min === f [ 0 ] && pat >= f [ 1 ])); console. log ( process. versions. node, ok? ' patched ': ' NOT patched ' ); process. exit ( ok? 0: 1 ); Enter fullscreen mode Exit fullscreen mode Jede Major-Version, die nicht in der Tabelle steht, darunter 20, schlägt absichtlich fehl. Eine Major-Version neuer als 26 schlägt ebenfalls fehl, also die Tabelle erweitern, sobald eine erscheint. Führt man das Skript als CI-Schritt aus, wird aus dem nächsten Hinweis eine einzeilige Tabellenänderung.
Anschließend sucht man die Stellen, an denen der eigene Code Brotli direkt dekodiert:
git grep -nE "BrotliDecompress|brotliDecompress" \ -- '*.js' '*.ts' '*.mjs' '*.cjs' Enter fullscreen mode Exit fullscreen mode Für die Versionsuntergrenze akzeptiert das engines-Feld in der package.json einen Semver-Bereich, sodass man je unterstützter Linie eine Untergrenze mit || verknüpfen kann:
{ "engines": { "node": "^22.23.4 || ^24.22.1 || >=26.12.1" } } Enter fullscreen mode Exit fullscreen mode npm gibt bei einer Abweichung von engines standardmäßig nur eine Warnung aus, daher sollte man dies als Dokumentation und nicht als Sperre betrachten. Das Basis-Image des Dockerfile sollte auf eine feste Version gepinnt und neu gebaut werden. Ein laufender Container behält die Node-Version, mit der er gebaut wurde.
Entscheidend ist die Node-Version im ausgelieferten Image, nicht die auf dem eigenen Laptop.
Vercel hat am 21. Oktober WAF-Regeln eingesetzt, die den eingehenden Angriff für alle Projekte blockieren, und verwaltet die Patch-Versionen für seine 22.x- und 24.x-Runtimes. Sein Bulletin nennt auch, was davon nicht abgedeckt ist: Die WAF "deckt keine Antworten ab, die Ihre Funktionen von anderen Servern abrufen", und sie "ersetzt das Patchen nicht". Projekte, die noch auf Node 20 laufen, müssen auf 22 oder 24 umsteigen und neu ausliefern.
Imperva hat ebenfalls WAF-Regeln ergänzt und weist darauf hin, dass ein Node.js-Update für eine vollständige Behebung weiterhin erforderlich ist. Eine WAF filtert, was hereinkommt. Ein fetch()-Aufruf zieht Brotli von Servern herein, die man nicht kontrolliert, und diese Antwort passiert nie eine eingehende Regel.
Ergänze check-node.mjs im Build jedes Image-Tags, der noch ausgeliefert wird, und prüfe, welche fehlschlagen. Welche Images hättest du vergessen?
Der schwierige Teil dieser Korrektur ist zu wissen, wo die eigenen Runtimes liegen: Basis-Images, CI-Runner, das verwaltete Node der Plattform, die eine VM, die seit einem Jahr niemand mehr angerührt hat. Ship It behandelt diese Strecke zwischen localhost und Produktion für ein kleines Team, vom Hosting über Deployments bis zu CI/CD und dem Monitoring, das zeigt, was tatsächlich läuft.
Ich baue außerdem Hermes IDE, daher sollte man dies mit im Hinterkopf bewerten: Sie ist als Quellcode verfügbar (BSL 1.1), läuft auf macOS, Windows und Linux und gibt jeder Aufgabe ihren eigenen Branch und Worktree, sodass Claude Code und Codex nebeneinander arbeiten können. Sie ist auf GitHub zu finden.
Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.
Original (Englisch) anzeigen
Eight bytes. That is the size of the internal buffer the Brotli decoder inside Node.js kept writing past, according to the Node.js security advisory of October 21. Feed it the wrong compressed stream and, in the advisory's words, an attacker "can corrupt process memory, which can lead to a crash, disclosure of process memory, or remote code execution."
The bug is CVE-2026-111497. The advisory credits researchers from OpenAI Codex Security with reporting it. On October 23, OpenAI described it on X as "a vulnerability that allows for unauthenticated remote code execution in Brotli." The same day, The Hacker News reported that Imperva had run code through it on the official Node.js 24.22.0 image for Linux arm64, with no debugger and no custom build.
If you run Node 22, 24 or 26 anywhere, the fix is an upgrade. If you run Node 20, no fix is coming.
Brotli is the compression format behind Content-Encoding: br. Node ships its own copy of Google's decoder and exposes it through node:zlib.
The advisory gives the root cause in one sentence: "An invalid block switch was reported as a temporary need for more input instead of a permanent format error, so the streaming decoder kept appending input bytes to an eight-byte internal buffer." The decoder hit an impossible state, decided it was waiting for more data, and kept writing.
Node.js rates the bug high, not critical ( advisory ). Some outlets, such as Crypto Briefing, have printed a CVSS score of 9.2. The Hacker News notes the flaw "is rated high rather than critical." Go by the vendor's rating.
The upstream fix is older than the CVE. The change first landed in Brotli's source in February 2026, per The Hacker News, and the Brotli 1.2.1 changelog, dated October 21, lists the CVE under SECURITY. The change was labelled a security fix only in the October releases.
Eight slots, more bytes: the decoder kept appending input to a buffer that had no room for it.
You don't have to call zlib yourself to be exposed. Imperva's analysis names two paths.
Responses you fetch. Node's built-in fetch() decoding a compressed response from another server reached the vulnerable decoder. A service that calls a third-party API, follows a webhook URL or fetches a URL a user typed is on this path when the server it fetches from can be controlled by an attacker.
Request bodies you parse. Express 5's body parsers, express.json(), express.urlencoded(), express.text() and express.raw(), decode Brotli request bodies alongside gzip and deflate ( The Hacker News ). Imperva reproduced execution on Express 5.0.0 to 5.3.0 and body-parser 2.0.0 to 2.3.0. A plain node:http server does not decode a Brotli body unless middleware or your own code does it.
Keep the result in proportion. Imperva says "execution took multiple attempts in our setup," and the team tested only arm64 on the 24.x line ( The Hacker News ). An earlier public proof of concept reached code execution only on a deliberately weakened build; on a standard build its author got a crash and a limited memory read ( The Hacker News ). No exploitation in the wild had been reported as of October 22, per the same article. A crash is still a denial of service, and the arm64 result shows code execution on a stock build is reachable.
The advisory is blunt about the last row: "End-of-Life versions are always affected when a security release occurs." It lists no workaround. Upgrading is the fix.
The same release patches nine other CVEs, three more of them high: two TLS certificate hostname bypasses and a dns.resolve truncation on NUL characters ( advisory ). One upgrade covers all of them.
Your laptop is the easy one. The Node binaries baked into container images, CI runners and VMs are the ones a dependency audit misses. Imperva says to check the Node version inside each deployed image or host, because "a package inventory can miss an older runtime bundled in a container" ( Imperva ).
Save this as check-node.mjs and run it with node check-node.mjs, or inside an image with docker run --rm -v "$PWD":/w your-image node /w/check-node.mjs:
// check-node.mjs: exit 1 if this Node lacks // the CVE-2026-111497 fix const fixed = { 22: [ 23, 4 ], 24: [ 22, 1 ], 26: [ 12, 1 ] }; const [ maj, min, pat ] = process. versions. node. split ( '. ' ). map ( Number ); const f = fixed [ maj ]; const ok = Boolean ( f ) && ( min > f [ 0 ] || ( min === f [ 0 ] && pat >= f [ 1 ])); console. log ( process. versions. node, ok? ' patched ': ' NOT patched ' ); process. exit ( ok? 0: 1 ); Enter fullscreen mode Exit fullscreen mode Any major not in the table, including 20, fails on purpose. A major newer than 26 will fail too, so extend the table when one ships. Run it as a CI step and the next advisory becomes a one-line table edit.
Then find the places your own code decodes Brotli directly:
git grep -nE "BrotliDecompress|brotliDecompress" \ -- '*.js' '*.ts' '*.mjs' '*.cjs' Enter fullscreen mode Exit fullscreen mode For the version floor, engines in package.json takes a semver range, so you can join one floor per supported line with ||:
{ "engines": { "node": "^22.23.4 || ^24.22.1 || >=26.12.1" } } Enter fullscreen mode Exit fullscreen mode npm only warns on an engines mismatch by default, so treat it as documentation, not a gate. Pin your Dockerfile's base image to a fixed release and rebuild. A running container keeps the Node it was built with.
The Node that matters is the one inside the image you deployed, not the one on your laptop.
Vercel deployed WAF rules on October 21 that block the inbound attack for every project, and it manages patch versions for its 22.x and 24.x runtimes. Its bulletin also says what that does not cover: the WAF "does not cover responses your functions fetch from other servers," and it "does not replace patching." Projects still on Node 20 have to move to 22 or 24 and redeploy.
Imperva has added WAF rules as well, and says a Node.js update is still required for full remediation. A WAF filters what comes in. A fetch() call pulls Brotli in from servers you don't control, and that response never passes an inbound rule.
Add check-node.mjs to the build of every image tag you still deploy, then see which ones fail. Which of your images would you have forgotten?
The hard part of this fix is knowing where your runtimes live: base images, CI runners, the platform's managed Node, the one VM nobody has touched in a year. Ship It covers that stretch between localhost and production for a small team, from hosting and deploys to CI/CD and the monitoring that tells you what you're actually running.
I also build Hermes IDE, so weigh this with that in mind: it's source-available (BSL 1.1), runs on macOS, Windows and Linux, and gives each task its own branch and worktree so Claude Code and Codex can work side by side. It's on GitHub.