Kai Ole Hartwig
19 Min. Lesezeit
Von

Node.js CVE-2026-111497: Der Brotli-Decoder rechnet nach einem ungültigen Blockwechsel einfach weiter

Brotli zerlegt komprimierte Daten in Meta-Blöcke, und innerhalb eines Blocks kann der Stream zwischen mehreren Blocktypen wechseln. Ein solcher Wechsel ist laut RFC 7932 nur erlaubt, wenn eine Kategorie mindestens zwei Blocktypen deklariert. Der Brotli-Decoder, den Node.js mitbringt, prüfte diese Bedingung zwar, meldete einen Verstoß aber mit demselben Rückgabewert wie fehlende Eingabedaten, und im schnellen Dekodierpfad wurde dieser Wert schlicht ignoriert. CVE-2026-111497 macht aus diesem Missverständnis einen Schreibzugriff außerhalb des Puffers, den Node.js als möglichen Weg zur Codeausführung einstuft. Der Fix stammt aus dem Brotli-Projekt selbst und war dort öffentlich, lange bevor er am 21. Oktober als Sicherheitsupdate in Node.js landete.

TL;DR — 90 Sekunden

Ein Rückgabewert mit zwei Bedeutungen

Brotli ist das Kompressionsformat aus RFC 7932, das Browser und Server als Content-Encoding: br aushandeln. Ein Stream besteht aus Meta-Blöcken, die jeweils bis zu 16 MiB entpacken. Innerhalb eines Meta-Blocks unterscheidet das Format drei Kategorien, nämlich Literale, Befehle und Distanzen, und jede Kategorie darf mehrere Blocktypen mit eigenen Präfix-Codes haben. Zwischen ihnen wechselt der Stream über Block-Switch-Befehle. Die RFC knüpft das an eine klare Bedingung: Die Codes für Blocktyp und Blocklänge erscheinen nur, wenn die Kategorie mindestens zwei Blocktypen deklariert, im Format ausgedrückt als NBLTYPES >= 2. Hat eine Kategorie nur einen Typ, gibt es nichts zu wechseln, und ein Wechsel ist ein Formatfehler.

Der Decoder in c/dec/decode.c kennt für viele Schritte zwei Varianten. Die sichere Variante rechnet damit, dass die Eingabe mitten in einem Symbol endet, und speichert ihren Zustand, um später weiterzumachen. Die schnelle Variante läuft in ProcessCommandsInternal, wenn genug Eingabe vorliegt, und spart sich diese Buchhaltung. Die Funktionen für Blockwechsel, darunter DecodeBlockTypeAndLength und die Switch-Funktionen für Literale, Befehle und Distanzen, lieferten bisher nur einen Wahrheitswert, und BROTLI_FALSE sollte dabei ausschließlich bedeuten, dass Eingabe fehlt. Die Varianten für den schnellen Pfad gaben überhaupt nichts zurück und verwarfen das Ergebnis, weil dort ja genug Eingabe garantiert war.

Genau hier lag der Fehler. Dieselben Funktionen gaben BROTLI_FALSE auch zurück, wenn ein Blockwechsel in einer Kategorie mit nur einem Blocktyp auftauchte. Im sicheren Pfad wurde aus dem Formatfehler damit ein harmloses „bitte mehr Daten“, im schnellen Pfad fiel er gar nicht auf, und der Decoder rechnete mit einem Zustand weiter, den das Format ausschließt. Wie daraus der Schreibzugriff außerhalb des Puffers entsteht, also welcher Puffer mit welchem Index getroffen wird, ist nicht öffentlich dokumentiert. Nach meiner Lesart des Diffs liegt nahe, dass der Decoder mit Längen oder Tabellen eines Blocktyps arbeitet, den es in dieser Kategorie nicht gibt. Das ist meine Ableitung und keine Aussage des Herstellers.

Die RFC hatte dieses Risiko vorhergesehen. Im Abschnitt zu Sicherheitsaspekten empfiehlt sie, dass Decoder mit jeder beliebigen Bytefolge umgehen, nicht konforme Daten ablehnen und jeden Speicherzugriff prüfen, der aus Werten des komprimierten Streams folgt. Davon getrennt behandelt sie Dekompressionsbomben, gegen die sie Plausibilitätsprüfungen für die Größe der Meta-Blöcke und des Schiebefensters vorschlägt. CVE-2026-111497 fällt in die erste Kategorie, und das hat Folgen für die Härtung, weil Schutzmaßnahmen gegen die zweite hier ins Leere laufen.

Was der Fix-Commit ändert

Der Fix stammt aus dem Brotli-Projekt. Commit 0ed4268a von Evgenii Kliuchnikov trägt die schlichte Beschreibung „Detect and report invalid stream earlier.“ und ändert die Signatur der betroffenen Funktionen: Statt eines Wahrheitswerts liefern sie jetzt einen BrotliDecoderErrorCode. Fehlende Eingabe meldet BROTLI_DECODER_NEEDS_MORE_INPUT, ein Blockwechsel bei max_block_type <= 1 dagegen den neuen Fehler BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH mit dem bisher reservierten Code -17. Ein neues Makro BROTLI_SAFE_WITH_STATUS sorgt dafür, dass auch der schnelle Pfad bei diesem Fehler abbricht und den Zustand sichert. Dazu kommt eine Debug-Assertion für die verbleibende Länge des Meta-Blocks, die in Release-Builds allerdings nicht greift.

Node.js übernimmt den Commit per Cherry-Pick nach deps/brotli, im 24er-Zweig als 49dff6f, in 22.x als 67d2947 und in 26.x als a6040b5. Ein zweiter Commit von Antoine du Hamel, im 24er-Zweig b5f4152, macht die neue Konstante in src/node_zlib.cc für JavaScript sichtbar und ergänzt einen Regressionstest, der einen Stream mit nur einem Blocktyp und trotzdem einem Blockwechsel über die synchrone und die Stream-API prüft. lib/zlib.js blieb unverändert.

Was der Fix über die Ursache verrät, ist lehrreicher als die Lücke selbst. Der Code kannte die Invariante aus der RFC und prüfte sie, meldete das Ergebnis aber über einen Kanal, der nur eine Bedeutung transportieren konnte. Ein Rückgabewert, der zugleich „warte“ und „brich ab“ heißen kann, ist in C ein Klassiker, und er wird gefährlich, sobald ein Aufrufer ihn aus Performancegründen ignoriert. Bemerkenswert ist auch die Vorgeschichte, denn dieselbe Funktion ProcessCommandsInternal war schon 2016 Schauplatz von CVE-2016-1624, einem Integer-Unterlauf. Der schnelle Pfad eines Decoders ist offenbar die Stelle, an der sich Annahmen über die Eingabe am leichtesten verselbstständigen.

Unbequem ist schließlich die Zeitachse. Der Commit 0ed4268a trägt das Datum 27. Februar 2026 und lag seitdem öffentlich im Upstream-Repository, ohne Hinweis auf seine Sicherheitsrelevanz und ohne getaggtes Release. Die Brotli-Releases 1.2.1, 1.1.1 und 1.0.10 erschienen erst am 21. Oktober zusammen mit der CVE. Wer den Diff früh gelesen hat, konnte die Schwäche erkennen, während Distributionen und gebündelte Kopien noch auf dem alten Stand waren.

Vom fremden Server in den Decoder

Node.js entpackt Brotli nicht von sich aus. node:http liefert Bodies so, wie sie über die Leitung kommen, und verwundbar wird erst, wer angreiferkontrollierte Brotli-Daten an den Decoder übergibt. Das klingt nach einer starken Einschränkung, ist in der Praxis aber eine kleine. Direkt im Spiel sind zlib.brotliDecompress, zlib.brotliDecompressSync und zlib.createBrotliDecompress, und auf diesen APIs bauen viele Bibliotheken auf, die Entpacken als Komfortfunktion eingeschaltet haben.

Der wichtigste Fall ist das eingebaute fetch(). undici, die HTTP-Implementierung dahinter, liest den Header Content-Encoding und dekodiert br über createBrotliDecompress. Ausgenommen sind nur HEAD- und CONNECT-Anfragen, Antworten ohne Body und Redirects, denen gefolgt wird. Einen Schalter, der das abstellt, gibt es in diesem Pfad nicht. undici bietet br im Accept-Encoding zwar nur bei HTTPS an, der Code zum Dekodieren prüft das Schema aber nicht. Daraus leite ich ab, dass ein Server die Kompression auch dann liefern kann, wenn der Client sie nicht angefordert hat, und Tests von Lilting zeigen, dass ein Accept-Encoding: identity nicht schützt. Jeder Dienst, der URLs aus Nutzereingaben abruft, etwa für Link-Vorschauen, Webhooks, Feed-Import oder Bildoptimierung, holt sich damit den Stream des Angreifers direkt in den Decoder. Vercel weist deshalb darauf hin, dass selbst betriebenes Next.js das Update braucht, weil die Bildoptimierung Brotli-Antworten nicht blockiert.

Auf der eingehenden Seite ist der bekannteste Kandidat body-parser, den Express 5 einsetzt. Laut README entpacken alle Parser gzip, br, deflate und zstd automatisch, und die Option inflate ist ab Werk aktiv. Ein Angreifer muss hier nur einen Request mit Content-Encoding: br schicken, der Rest passiert vor jeder Anwendungslogik. Für axios, got und node-fetch nennt Atlasum ebenfalls aktives Entpacken ab Werk, was ich nicht selbst im Code geprüft habe.

Die Folgen eines Treffers fasst Node.js im Titel der Meldung als Remote Code Execution zusammen, einen CVSS-Vektor gibt es bisher nicht. Vercel berichtet, dass Forscher auf ungepatchten Versionen von 22.x, 24.x und 26.x das Auslesen von Prozessspeicher und die Ausführung von Befehlen mit den Rechten des Node-Prozesses gezeigt haben. Wie aufwändig der Weg bis zur Codeausführung ist, beschreiben die Quellen nicht. Schon das Auslesen von Prozessspeicher kann aber Tokens und Zugangsdaten aus dem Heap preisgeben, und das ist auch ohne Codeausführung ein ernster Schaden.

Wirklich betroffen sind damit Dienste, die Inhalte fremder Server abrufen oder komprimierte Request-Bodies annehmen. Wer nur eigene Upstreams per HTTPS abfragt und eingehend keine Kompression akzeptiert, hat ein deutlich kleineres Risiko, auch wenn ein kompromittierter Upstream den Pfad wieder öffnet. Viele Betreiber halten sich dagegen nur deshalb für betroffen, weil sie Brotli am Reverse Proxy für Antworten an Browser einsetzen, doch Kompression in Richtung Client berührt den Decoder nicht.

CVEBetroffenFixVoraussetzungCVSS
CVE-2026-111497 (Brotli-Decoder)Node.js 22, 24, 26 vor den Fix-Versionen und alle EOL-Zweige; libbrotli vor 1.2.1, 1.1.1 und 1.0.10Node.js 22.23.4 / 24.22.1 / 26.12.1; Brotli 1.2.1 / 1.1.1 / 1.0.10Anwendung entpackt Brotli-Daten aus fremder Quelle, etwa über fetch() oder body-parsernoch nicht veröffentlicht (Node.js: high)
CVE-2026-106069 und CVE-2026-106072 (TLS-Hostnamenprüfung)Node.js 22, 24, 2622.23.4 / 24.22.1 / 26.12.1laut Titel: Ziel in nicht kanonischer IP-Schreibweisenoch nicht veröffentlicht (high / medium)
CVE-2026-105483 (TLS-Hostnamenprüfung, Fullwidth-Ziffern)Node.js 24, 2624.22.1 / 26.12.1laut Titel: IP-Adresse in Fullwidth-Ziffernnoch nicht veröffentlicht (high)
CVE-2026-105482 (NUL-Byte in dns.resolve)Node.js 22, 24, 2622.23.4 / 24.22.1 / 26.12.1Hostname aus Nutzereingabenoch nicht veröffentlicht (high)

Die übrigen fünf CVEs des Release stuft Node.js als mittel oder niedrig ein. Für Betreiber sind davon vor allem CVE-2026-106074 relevant, bei der Entropie-Caches von crypto.randomUUID() und randomInt() in Startup-Snapshots und Single Executable Applications landen, sowie die beiden Umgehungen des Permission Models CVE-2026-106071 und CVE-2026-106073, von denen die erste nur 26.x betrifft. Zusätzlich bringt das Release eine einkompilierte Sperrliste für die öffentlichen Schlüssel der kompromittierten eMudhra-Zwischenzertifikate mit, deren Hintergrund ich im Beitrag zur eMudhra-CA-Kompromittierung beschreibe. Im CISA-KEV-Katalog (Stand 21. Oktober) steht keine dieser Lücken, und EPSS hat mangels CVE-Eintrag noch keine Werte.

Node.js 22.23.4, 24.22.1, 26.12.1 und ihre Stolperfallen

Der vollständige Fix steckt in Node.js 22.23.4, 24.22.1 als aktueller LTS und 26.12.1. Für Node.js 20 und 25, die das Ende ihrer Unterstützung erreicht haben, gibt es kein Update. Laut Advisory sind EOL-Versionen grundsätzlich betroffen, und der Weg führt über den Umstieg auf einen unterstützten Zweig. Die offiziellen Docker-Images wurden laut Atlasum am 21. Oktober neu gebaut und stehen unter Tags wie node:22.23.4, node:24.22.1 und node:26.12.1 bereit. Ein auf node:24.22.0 gepinntes Basis-Image bleibt verwundbar, bis du den Tag anhebst, und ein schwebender Tag wie node:24-alpine hilft erst, wenn die Pipeline das Image frisch zieht und neu baut. Laufende Prozesse und Pods behalten die alte Runtime bis zum Neustart.

Eine Stolperfalle betrifft Node-Builds der Distributionen. Debian und Alpine bauen ihre Node.js-Pakete mit --shared-brotli, und die Runtime nutzt dann die System-Bibliothek libbrotlidec statt der gebündelten Kopie. Dort entscheidet das Brotli-Paket der Distribution über den Schutz, und das war am 22. Oktober noch nicht überall so weit. Der Debian-Security-Tracker führte die nodejs-Pakete in Bookworm, Trixie, Forky und Sid an diesem Tag noch alle als verwundbar. Im Alpine-Tracker war nur nodejs-current 26.12.1-r0 in edge-community als gefixt eingetragen. Wolfi war schneller: Die Paketdefinitionen in wolfi-dev/os stehen auf nodejs-24 24.22.1 und nodejs-26 26.12.1, und brotli 1.2.0 hat mit Epoch 7 einen Patch für genau diese CVE bekommen. Im Amazon Linux Security Center gab es zur CVE am 22. Oktober noch keinen Eintrag. Bun bündelt Brotli ebenfalls ohne den Fix, und der Pull Request mit dem Backport wartete am 22. Oktober noch auf eine Reaktion der Maintainer.

Das Update bringt einige Verhaltensänderungen mit, die du vor dem Rollout kennen solltest. Ungültige Brotli-Streams, die bisher etwa mit ERR__ERROR_FORMAT_BLOCK_LENGTH_1 abgelehnt wurden, können jetzt ERR__ERROR_FORMAT_BLOCK_SWITCH liefern, was Code betrifft, der auf Fehlercodes prüft. TLS lehnt Hostnamen in nicht kanonischer IP-Schreibweise ab, und dns.resolve* wirft bei NUL-Bytes einen ERR_INVALID_ARG_VALUE. Zertifikatsketten über die gesperrten eMudhra-Schlüssel scheitern mit CERT_REVOKED. In 26.12.1 steckt außerdem ein Revert der Änderung, die die meisten ES-Module synchron lädt. undici steigt in 26.12.1 auf 8.12.2, in 24.22.1 auf 7.30.1 und in 22.23.4 auf 6.29.1, und wer undici zusätzlich als npm-Paket einsetzt, aktualisiert dieses separat. Startup-Snapshots und Single Executable Applications musst du wegen CVE-2026-106074 mit der gepatchten Runtime neu bauen, weil der alte Zustand sonst im Artefakt erhalten bleibt.

Härtung über den Patch hinaus

Für die Einordnung lohnt der Blick darauf, wo der Fehler sitzt. Er liegt in nativem Code, der im Adressraum des Node-Prozesses läuft, bei den asynchronen zlib-APIs laut Node-Doku in einem Thread des internen Threadpools. Zwischen Decoder und Anwendung gibt es also keine Grenze, die ein Speicherfehler respektieren müsste. Wirksam ist deshalb alles, was verhindert, dass fremde Brotli-Daten den Decoder erreichen, oder was den Prozess, in dem sie entpackt werden, von Geheimnissen und Netz trennt. Einstellungen innerhalb von Node.js, die Ressourcen begrenzen oder Berechtigungen prüfen, setzen dagegen an der falschen Stelle an.

Komprimierte Request-Bodies am Eingang abweisen

Browser schicken Request-Bodies in aller Regel unkomprimiert, und viele Webanwendungen haben keinen legitimen Grund, Content-Encoding: br eingehend anzunehmen. Ein Reverse Proxy oder Ingress, der solche Requests mit 415 beantwortet, nimmt dem Angreifer den eingehenden Pfad. In der Anwendung erreichst du dasselbe mit inflate: false in body-parser, dann lehnt der Parser komprimierte Bodies ab. Prüfe vorher, ob API-Clients bewusst komprimierte Bodies schicken. Die Maßnahme kostet wenig, und wenn du die Regel auf jedes Content-Encoding außer identity ausdehnst, hält sie auch künftige Fehler in gzip- oder deflate-Decodern fern. Ihre Grenze ist eindeutig, denn gegen Antworten, die deine Anwendung selbst abruft, wirkt sie nicht. Ein Proxy wie nginx kann Brotli ohnehin nicht entpacken und prüfen, denn das Modul ngx_http_gunzip_module behandelt nur gzip-Antworten.

Ausgehende Abrufe begrenzen

Der schwierigere Pfad ist der ausgehende. Wo Bibliotheken das Entpacken als Option anbieten, etwa decompress: false bei axios und got, lässt es sich abschalten, und die Anwendung muss komprimierte Antworten dann bewusst behandeln. Für das eingebaute fetch() gibt es diesen Schalter nicht. Was bleibt, ist die Netzwerkebene. Dienste, die keine beliebigen URLs abrufen müssen, bekommen eine Egress-Sperre, die nur ihre bekannten Upstreams erlaubt, in Kubernetes als NetworkPolicy mit policyTypes: [Egress] und für Hostnamen über die FQDN-Regeln deines CNI. Damit sinkt die Zahl der Server, die einen Stream liefern können, auf die, denen du ohnehin vertraust. Dienste, die beliebige URLs abrufen müssen, schickst du über einen Forward-Proxy, der nur HTTPS zulässt, denn Klartext-HTTP öffnet den Pfad für jeden, der zwischen dir und dem Ziel sitzt. Eine vollständige Sperre ist das für solche Dienste nicht, weil ein Angreifer seinen Server einfach per HTTPS betreibt. Nach einer erfolgreichen Ausnutzung erschwert die Egress-Sperre aber das Nachladen von Werkzeugen und den Abfluss von Daten.

Entpacken fremder Inhalte isolieren

Für Dienste, die zwingend Inhalte aus fremder Quelle verarbeiten, ist Isolation die tragfähigste Antwort. Link-Vorschau, Feed-Import oder Bildoptimierung lassen sich in einen eigenen Prozess oder Pod auslagern, der keine Datenbank-Zugangsdaten und keine API-Tokens hält, mit automountServiceAccountToken: false ohne ServiceAccount-Token läuft und dessen Egress nach außen, aber nicht ins interne Netz führt. Ein Speicherfehler im Decoder trifft dann einen Prozess, in dem es wenig zu holen gibt. Das leite ich aus dem Mechanismus ab, Node.js selbst spricht diese Empfehlung nicht aus. Die technische Begründung ist aber schlicht: Einen Fehler im eigenen Adressraum begrenzt nur eine Prozessgrenze. Worker Threads reichen dafür nicht, weil sie denselben Prozess teilen. Der Aufwand liegt in der Architektur, denn der ausgelagerte Dienst braucht eine eigene Schnittstelle und eigenes Monitoring.

Container-Härtung als Schadensbegrenzung

Die bekannten Einstellungen im securityContext verhindern den Speicherfehler nicht und auch nicht die Ausführung von Code im Node-Prozess. Sie entscheiden aber, was danach möglich ist. Mit runAsNonRoot: true, allowPrivilegeEscalation: false, capabilities.drop: ["ALL"] und seccomp im Modus RuntimeDefault bleibt ein Angreifer ein unprivilegierter Prozess ohne Weg zu höheren Rechten, und readOnlyRootFilesystem: true erschwert es, Werkzeuge abzulegen oder sich dauerhaft einzunisten. Ein minimales Base-Image ohne Shell und Paketmanager nimmt ihm zusätzlich bequeme Hilfsmittel. Der Pod Security Standard „restricted“ fasst die meisten dieser Werte zusammen. Beim eigentlichen Schaden stößt das alles an Grenzen, denn Geheimnisse, die der Prozess legitim im Speicher oder in Umgebungsvariablen hält, kann ein Angreifer mit Codeausführung trotzdem lesen. Wie du diese Werte für ganze Namespaces erzwingst, beschreibe ich in der Serie TYPO3 auf Kubernetes unter IT-Grundschutz.

Was hier nicht hilft

Das Permission Model von Node.js mit --permission schützt nicht gegen diese Lücke. Die Doku sagt dazu unmissverständlich, dass die Funktion nicht gegen bösartigen Code schützt und dass bösartiger Code das Modell umgehen und beliebigen Code ausführen kann. Ein Speicherfehler in nativem Code liegt außerhalb der Prüfungen, die das Modell in den JavaScript-APIs vornimmt, und dasselbe Release schließt ohnehin zwei Umgehungen des Modells.

Auch die Optionen von node:zlib helfen nicht. maxOutputLength begrenzt die Ausgabemenge der Komfortfunktionen und ist gegen Dekompressionsbomben sinnvoll, gegen einen Fehler im Decoder-Zustand aber wirkungslos, weil der fehlerhafte Zugriff nicht von der Ausgabemenge abhängt. chunkSize bestimmt nur die Größe des Ausgabepuffers. Ähnlich steht es um WAF-Regeln: Vercel hat für eingehende Angriffe Regeln ausgerollt, betont aber selbst, dass gegen ausgehend abgerufene Antworten nur das Runtime-Update schützt. Ein CVE-Gate in der Pipeline greift ebenfalls zu kurz, solange die CVE keinen veröffentlichten Eintrag hat, denn viele Scanner kennen sie dann noch nicht. Und wer seine Runtime über process.versions.brotli prüft, sieht auf gepatchten und ungepatchten Builds dieselbe Versionsnummer, weil Node.js nur den einzelnen Commit übernommen und die Version nicht angehoben hat.

Erkennung und Prüfung

Ob eine Runtime gepatcht ist, verrät zuerst process.version, also 22.23.4, 24.22.1, 26.12.1 oder höher. Für Builds unklarer Herkunft gibt es einen zuverlässigeren Test: Die gepatchte Runtime kennt die Konstante zlib.constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH mit dem Wert -17, die ungepatchte nicht. Bei Distributions-Builds mit --shared-brotli sagt die Konstante allerdings wenig über die tatsächlich geladene Bibliothek aus. Dort zeigt ldd auf die Node-Binary, ob libbrotlidec aus dem System kommt, und dann zählt die Version des Brotli-Pakets. In Kubernetes reicht ein Blick auf die Image-Tags nicht, wenn Pods schon länger laufen, denn es zählt die Runtime im laufenden Container.

Ob jemand die Lücke bereits angegriffen hat, ist schwerer zu beantworten, denn Node.js und Vercel nennen keine Indikatoren. Ein plausibles Signal sind Abstürze von Node-Prozessen mit Speicherfehlern, also Segmentation Faults oder Pod-Restarts mit Exit-Code 139, die zeitlich mit Abrufen fremder URLs oder eingehenden Requests mit Content-Encoding: br zusammenfallen. Ein Fehler mit dem Code ERR__ERROR_FORMAT_BLOCK_SWITCH nach dem Update zeigt, dass jemand einen solchen Stream geschickt hat, den die gepatchte Runtime nun ablehnt. Beide Suchmuster sind meine eigene Ableitung und keine Vorgabe des Herstellers.

Bei einem Verdacht behandelst du den betroffenen Prozess wie kompromittiert. Pods werden aus einem gepatchten Image neu ausgerollt, und alle Geheimnisse, die der Prozess im Speicher oder in Umgebungsvariablen hatte, werden rotiert. Danach lohnt der Blick in die Egress-Logs des Dienstes nach Verbindungen zu Zielen, die er normalerweise nicht anspricht.

Betreiberempfehlung

Akut handeln solltest du, wenn ein Node-Dienst URLs aus Nutzereingaben abruft, Inhalte fremder Server mit fetch() oder einer Bibliothek mit automatischem Entpacken verarbeitet oder über Express 5 und body-parser komprimierte Bodies annimmt. Aktualisiere dann sofort auf die Fix-Versionen, baue die Images neu und starte die Pods neu. Bis das Update überall läuft, weist du Content-Encoding: br eingehend am Proxy ab.

Beobachten genügt, wenn deine Dienste nur eigene Upstreams per HTTPS abfragen und keine komprimierten Requests annehmen. Das Update gehört trotzdem in die nächsten Tage, schon wegen der TLS-Lücken im selben Release, und wer Distributions-Builds nutzt, behält zusätzlich das Brotli-Paket im Blick.

Unabhängig vom Einzelfall gilt, was ich bei jeder Lücke sage: Patches und Updates gehören automatisiert. Bei Node.js steckt die Runtime meist im Basis-Image, und genau dort veralten Versionen unbemerkt, weil ein gepinnter Tag nie von selbst wandert. Wenn Renovate oder Dependabot neue Image-Tags und Paketversionen als Merge Request vorschlagen und die Pipeline sie gegen Staging testet, ist ein Security-Release wie dieses eine Sache von Stunden statt Wochen. Eine Karenzzeit für Routine-Updates schützt vor kompromittierten Releases, und Sicherheitsupdates nimmst du davon aus. Die geänderten Fehlercodes und die strengere TLS-Prüfung in diesem Release sind genau die Art von Änderung, die im Test auffallen soll und nicht in Produktion. Wer dagegen von Hand aktualisiert, sitzt in der Urlaubszeit schnell wochenlang auf einer verwundbaren Runtime und spielt danach ein Sammel-Update ein, bei dem niemand mehr weiß, welche Änderung welchen Fehler auslöst.

Häufige Fragen zu CVE-2026-111497

Ich nutze Brotli nur für Antworten an Browser. Bin ich betroffen?+

Durch diese Nutzung nicht, denn Kompression in Richtung Client berührt den Decoder nicht. Betroffen ist, wer Brotli-Daten entpackt, also Antworten fremder Server über fetch() oder komprimierte Request-Bodies über body-parser.

Wie prüfe ich, ob meine Runtime gepatcht ist?+

Prüfe process.version und ob zlib.constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH den Wert -17 hat. Auf process.versions.brotli kannst du dich nicht verlassen, weil die Nummer gleich bleibt. Bei Distributions-Builds zeigt ldd, ob die System-Bibliothek geladen wird.

Schützt das Permission Model von Node.js?+

Nein. Die Node-Doku sagt selbst, dass das Permission Model nicht gegen bösartigen Code schützt und umgangen werden kann. Ein Speicherfehler im nativen Decoder liegt außerhalb seiner Prüfungen.

Reicht das Update auf Node.js allein?+

Bei offiziellen Builds und Docker-Images ja, sobald Prozesse und Pods mit der neuen Runtime laufen. Nutzt deine Distribution --shared-brotli, brauchst du zusätzlich ein Brotli-Paket mit dem Fix. Startup-Snapshots und Single Executable Applications baust du mit der gepatchten Runtime neu. Bun ist davon unabhängig und braucht ein eigenes Update.

Fazit

CVE-2026-111497 ist ein klassischer Fehler: Ein Decoder kannte die Regel seines Formats, meldete ihren Bruch aber über einen Rückgabewert, den ein schneller Pfad ignorierte. Für Betreiber reicht die Lehre über Brotli hinaus. Jeder Parser für komprimierte oder binäre Formate läuft in deinem Prozess mit allen Rechten und Geheimnissen, die dieser Prozess hat, und ein Komfortmerkmal wie automatisches Entpacken in fetch() macht aus jedem fremden Server einen Lieferanten für diesen Parser. Es lohnt sich deshalb, die eigenen Dienste daraufhin anzusehen, wo sie Daten aus fremder Hand entpacken, und genau diese Stellen in Prozesse ohne Geheimnisse und mit begrenztem Netz zu legen. Der stille Fix, der lange vor der CVE öffentlich lag, zeigt außerdem, dass ein Abgleich gebündelter Abhängigkeiten mit ihrem Upstream mehr verrät als ein Scanner, der auf CVE-Nummern wartet.

Quellen

Ich prüfe deine Node-Dienste darauf, wo sie fremde Daten entpacken, isoliere diese Pfade und automatisiere den Rollout gepatchter Runtimes und Images.

Audits von Node.js-Diensten auf Abrufe fremder Inhalte, Egress-Regeln und Isolation für Bildoptimierung und Link-Vorschau, Renovate-Setups für Basis-Images und Runtimes.

Plattform-Betrieb statt Beratung auf Papier: Ich prüfe, patche und härte deine Infrastruktur laufend.

Über den Autor