2026

19. Mai 2026

Das Flight Protocol hat dein DoS zu meinem Problem gemacht

Am 6. Mai 2026 haben React und Next.js zwölf Schwachstellen gepatcht. Eine davon, CVE-2026-23870, ist ein einziger HTTP Request, der deinen Node Prozess lahmlegt. Der Bug ist nicht der Bug. Der Bug ist, dass die Framework Grenze schon immer eine Netzwerk Grenze war.

S
Sascha Becker
Author

14 Min. Lesezeit

Das Flight Protocol hat dein DoS zu meinem Problem gemacht

Das Flight Protocol hat dein DoS zu meinem Problem gemacht

Am 6. Mai 2026, zwischen einem gebündelten Advisory und einem leicht peinlichen pnpm up, haben die React und Next.js Teams Patches für zwölf Schwachstellen ausgeliefert.1 Die meisten davon sind Routine: eine Handvoll Middleware Bypasses, ein SSRF im WebSocket Upgrade Handling, ein XSS, der eine CSP Nonce brauchte, um zu landen. Normales Patch Tuesday Geschäft für ein Framework dieser Größenordnung.

Eine davon ist es nicht. CVE-2026-23870 ist ein High-Severity Denial-of-Service im Deserialisierungspfad von Reacts Flight Protocol. Ein einziger präparierter HTTP Request, die Art Sache, die ein gelangweilter Angreifer mit curl baut, kann einen Node Worker so lange auf exzessive CPU treiben, bis die Runtime aufgibt. Keine Authentifizierung nötig. Keine exotischen Voraussetzungen. Nur ein Request, der absichtlich falsch geformt ist, und ein Server, der der Form vertraut hat.

Der Bug selbst wird gepatcht und im nächsten Sprint vergessen sein. Was sich zu betrachten lohnt, ist, was der Bug darüber verrät, wie wir seit zwei Jahren Server Components schreiben.

Was Flight wirklich ist

Zuerst ein kurzer Primer. React Server Components sind Komponenten, die ausschließlich auf dem Server laufen und nie JavaScript an den Browser ausliefern. Der Server rendert sie, serialisiert den entstehenden Baum in Bytes und streamt diese Bytes an den Client, wo React die UI rekonstruiert. Dieser Serialisierungsschritt ist Flight.

Wenn du eine React Server Component ausgeliefert hast, hast du Flight ausgeliefert. Du hast das Protokoll wahrscheinlich nie gelesen.

Flight ist das Wire Format, mit dem React server-gerenderte Komponentenbäume, Server Function Aufrufe samt Ergebnissen und die Referenzen dazwischen vom Server zum Client transportiert (oder von einem Server zum anderen). Es ist nicht React. Es ist nicht HTML. Es ist ein eigenes, durch Newlines getrenntes Streaming Protokoll. Jede Zeile ist ein Chunk:23

0:"Hello from the server"
1:{"name":"Alice","age":30}
2:["$","div",null,{"children":"$0"}]

Drei Chunks: ein String, ein einfaches Objekt und ein React Element, dessen Children über "$0" auf Chunk 0 verweisen. Das $ taucht zweimal auf und bedeutet zwei verschiedene Dinge. Das nackte "$" im Array ist Reacts Marker für "das ist ein Element"; das "$0" ist ein Pointer auf einen anderen Chunk, sodass ein Wert, der an zehn Stellen vorkommt, nur einmal serialisiert werden muss.

Jeder Chunk hat eine ID in Hex, einen optionalen Einbuchstaben-Tag (I für Modulreferenzen, E für Errors, Q für Maps, W für Sets, o für Typed Arrays) und einen JSON Payload. Das Format dedupliziert by design und repräsentiert die Art zyklischer, referenzreicher Strukturen, die reines JSON nicht abbilden kann.

Das ist ein Protokoll. Es ist ein Protokoll, das dein Server gegenüber jedem spricht, der einen HTTP Client darauf richtet. Server Actions sitzen direkt darauf. Eine Server Action ist eine async Funktion, die du mit "use server" markierst und direkt aus einer Client Component aufrufst, häufig als action Prop eines <form>. Die Funktion sieht aus wie eine Funktion. Darunter verdrahtet das Framework eine HTTP Route, deren Request Body die Argumente deiner Funktion sind, als Flight Chunks kodiert und vom selben Deserialisierer geparst, den der Server für eingehende RSC Payloads nutzt.

Du hast ein Wire Format exponiert. Das Framework hat entschieden, wie streng der Parser sein würde.

Der Bug, in einem Absatz

CVE-2026-23870 betrifft react-server-dom-parcel, react-server-dom-webpack und react-server-dom-turbopack auf jeder veröffentlichten 19.x Linie bis 19.0.5, 19.1.6 und 19.2.5.4 Der Deserialisierer in diesen Paketen akzeptiert eingehende Flight Payloads, ohne strukturelle oder Typ Constraints angemessen durchzusetzen. Übersetzung: du kannst ihm einen Payload geben, dessen Chunk Graph fehlerhaft ist, dessen Tags nicht zur Wertform passen oder dessen Referenzen sich auf Weisen schachteln, für die der Parser nicht ausgelegt war, und der Parser wird CPU verbrennen beim Versuch, daraus etwas zu machen. Exzessive CPU. Genug, damit der Worker niemandem sonst mehr antwortet.

Der Fix ist die Parser-Hygiene Arbeit, die der ursprüngliche Deserialisierer von Anfang an hätte mitbringen sollen: strukturelle Validierung, Tiefen- und Size Caps, Typ-Tag Enforcement. Gepatcht in 19.0.6, 19.1.7 und 19.2.6. Cloudflares WAF (Web Application Firewall) hat generische Regeln vor dieselbe Klasse von Malformed-Payload Angriffen geschaltet.5 Das Next.js Release deckt App Router 13.x bis 16.x ab.

Bin ich betroffen?

Zwei Checks. Dauert dreißig Sekunden.

Prüfe die installierte Version. Führe je nach Package Manager einen dieser Befehle in deinem App Root aus:

bash
pnpm why react-server-dom-webpack
npm ls react-server-dom-webpack
yarn why react-server-dom-webpack

Wenn die aufgelöste Version unter 19.0.6, 19.1.7 oder 19.2.6 liegt (je nachdem, auf welcher 19.x Linie du bist), lieferst du den verwundbaren Deserialisierer aus.

Lass das Audit laufen. Dein Package Manager weiß schon Bescheid über CVE-2026-23870, das seit dem 6. Mai in der GitHub Advisory Database steht:

bash
pnpm audit
npm audit

Das Audit nennt das CVE direkt beim Namen. Wenn du von Next.js abhängst, flaggt es das gebundelte react-server-dom-* auch dann, wenn du es nicht selbst importierst.

Der Fix. Die meisten Apps müssen nur Next.js upgraden, was den gepatchten react-server-dom-* als transitive Dependency mitzieht:

next # neueste auf deinem Major; 13.x, 14.x, 15.x und 16.x haben alle gepatchte Linien
react-server-dom-webpack@19.2.6 # nur wenn du es direkt importierst; -parcel / -turbopack für die jeweiligen Bundler

Wenn du sehen willst, wie viele Endpunkte dieser Patch für dich schließt, grepe nach der Direktive:

bash
rg '"use server"' --type ts -l

Diese Zahl ist dein Server Action Footprint. Selbst mit null Treffern bist du betroffen: derselbe Deserialisierer läuft auf jedem gecachten RSC Payload und auf der eingehenden RSC Route, die Next.js für clientseitige Navigationen mountet. App Router Apps fallen in den Wirkungsbereich, unabhängig davon, ob du selbst Server Actions schreibst.

Die Framework Grenze war schon immer eine Netzwerk Grenze

Hier ist der Teil, der den Patch überleben sollte.

React Server Components wurden dem Publikum als Render-Zeit Optimierung verkauft. Du schreibst Komponenten auf dem Server, das Framework findet heraus, welche Teile als interaktives JavaScript an den Client gehen und welche als reines HTML bleiben, und du sparst dir die Kosten dafür, JavaScript zu bundeln und neu auszuführen für Teile der Seite, die nie interaktiv werden sollten. Das Framing war: "Es ist wie SSR, nur besser." Dieses Framing hat eine Generation von Frontend Engineers darauf trainiert, die Server / Client Grenze als internes Implementierungsdetail zu behandeln, genau wie sie die Grenze zwischen einer Eltern und einer Kind Komponente behandeln.

Die Runtime war nie einverstanden. Auf der Runtime Ebene ist die Grenze eine Serialisierungsgrenze. Jede Server Action, die deine Codebase exponiert, ist ein Deserialisierungs Endpunkt, der vom Angreifer kontrollierte Bytes akzeptiert. Jeder gecachte RSC Payload ist ein Record, der wieder durch denselben Deserialisierer geschickt wird. Die 'use server' Direktive ist keine Annotation. Sie ist ein eingehender HTTP Handler mit Flight-förmigen Erwartungen.

Wenn der Parser nachsichtig mit der Form ist, hat jeder Komponentenautor, der eine Server Action geschrieben hat, aus Versehen einen unauthentifizierten RPC Endpunkt ohne Input Validierung geschrieben. Bei dieser Beschreibung sollte jeder Backend Engineer zusammenzucken. Frontend Engineers, die denselben Code geschrieben haben, sind nicht zusammengezuckt, weil das Framing sie nicht dazu gebracht hat.

Das ist nicht das erste Mal, dass Reacts Wire Layer der Bug war. CVE-2025-55182 aus dem letzten Jahr war eine RCE durch Flight Payload Deserialisierung, in Writeups später auf "React2Shell" getauft.6 Dasselbe Protokoll. Dieselbe Vertrauensannahme. Eine andere Konsequenz. Der DoS ist der sanftere Cousin in derselben Familie.

Warum wir es übersehen haben

Das Versagen des mentalen Modells lässt sich leicht rekonstruieren.

Du schreibst eine Server Action, die formData akzeptiert. Sie sieht aus wie eine Funktion. Du gibst dem Argument einen Typ, weil TypeScript sonst schreit. Du gibst { ok: true } zurück. Von innerhalb der Codebase aus ist das eine Funktion. Von außerhalb der Codebase aus ist es ein HTTP Endpunkt, dessen Request Body von einem Streaming Deserialisierer für ein Protokoll geparst wird, das die meisten im Team nie gelesen haben.

Drei Dinge kamen zusammen.

Das Protokoll war für den Großteil von RSCs Frühzeit undokumentiert. Das React Team hat Flight als Implementierungsdetail behandelt. Community Writeups27 haben das Format aus dem Source reverse-engineered. Wenn das Verständnis deines Teams von Flight aus einem Blogpost von jemandem stammt, der den Source gelesen hat, ist dein Verständnis wahrscheinlich korrekt, aber es ist kein Threat Model.

Die Fehleroberfläche einer Server Action ist an der Aufrufstelle unsichtbar. Du kannst in einer Next.js Codebase nicht nach "Endpunkten, die untrusted Input akzeptieren" greppen, weil die Endpunkte aus 'use server' Direktiven abgeleitet werden. Ein Reviewer kann nicht zählen, was ein Reviewer nicht sehen kann.

Die Defaults des Frameworks haben Parsing als Performance behandelt, nicht als Sicherheit. Streaming, lazy, referenzreiche Deserialisierung ist ein echter Performance Gewinn. Sie heißt auch, dass der Parser optimistisch Referenzen folgt, bevor er weiß, ob der Payload überhaupt wohlgeformt ist. Ein auf den Happy Path optimierter Parser ist ein Parser, der auf dem adversarial Path schlecht alloziert.

Keines davon ist für sich genommen ein Smoking Gun. Zusammen lassen sie den Bug im Rückblick unvermeidlich wirken.

Was du tatsächlich tun solltest

Patche zuerst. Die Versions Bumps oben sind der billige Teil. Sie beenden dieses CVE. Sie beenden nicht die Klasse. Fünf Dinge, die in den gleichen PR oder den nächsten gehören.

1. Behandle Server Actions wie RPC Endpunkte

Jede Datei mit 'use server' ist Teil deiner öffentlichen Angriffsoberfläche. Auditiere sie so, wie du eine /api Route auditieren würdest. Jede Action bekommt explizite Argument Validierung (Zod, Valibot, selbst gebaut, egal), ein explizites Size Limit auf den eingehenden Payload und einen Auth Check, der nicht annimmt, dass die Action nur deshalb gelaufen ist, weil dein eigenes Form darauf gezeigt hat. Das Form ist eine UI. Der Endpunkt ist ein Vertrag.

ts
"use server";
import { z } from "zod";
const Input = z.object({
email: z.string().email().max(254),
message: z.string().max(2000),
});
export async function submitContact(raw: unknown) {
const { email, message } = Input.parse(raw);
// ...
}

Input.parse wirft eine Exception, wenn der Request Body nicht zum Schema passt, sodass der Rest von submitContact nur auf Inputs läuft, die die Anwendung tatsächlich erwartet. Dieser Validierungsschritt ist der Teil, den du vor sechs Monaten nicht geschrieben hast, weil das Framework dich überspringen ließ.

2. Begrenze den Parser

Der Flight Deserialisierer bietet keine Tunables, die du an der Framework Grenze setzen kannst, aber dein Reverse Proxy schon. Setze Request Body Size Caps am Edge (Vercel, Cloudflare, dein nginx) für jeden Pfad, der Server Action Payloads akzeptiert. Ein normaler Server Action Body liegt im Kilobyte Bereich. Ein Megabyte zu schicken ist schon ein Event, das man markieren sollte.

3. Halte deine WAF Regeln aktuell

Cloudflares bestehende Regeln 2694f1610c0b471393b21aef102ec699 und aaede80b4d414dc89c443cea61680354 decken CVE-2026-23870 generisch ab.5 Andere Edge Provider rollen ähnliche Regeln in den nächsten Tagen aus. Der Patch ist notwendig; die WAF ist die Schicht, die die Variante desselben Bugs fängt, die in drei Monaten droppt.

4. Vertraue gecachten RSC Payloads nicht mehr

GHSA-wfc6-r584-vfw7, das neben CVE-2026-23870 im gleichen Advisory Fenster sitzt, ist ein Cache Poisoning Bug in RSC. Der Cache für serialisierten Komponenten Output kreuzt dieselbe Trust Boundary wie der eingehende Request, und dieselbe Parser Fragilität gilt auch auf dem Rückweg. Überall dort, wo deine App RSC Payloads aus einem Cache ausliefert, den ein Angreifer beeinflussen kann, fällt der Cache in diese Bug Klasse.

5. Lies das Protokoll einmal durch

Zwanzig Minuten mit den Community Flight Writeups23 verändern, wie du das nächste CVE liest. Das Protokoll ist nicht kompliziert; das Problem ist, dass wir es als out of scope behandelt haben. Es ist nicht out of scope.

Das Muster hinter dem Muster

Jede Generation von "Framework Magic" begräbt ein neues implizites Protokoll in dem, was wie eine gewöhnliche API aussieht.

Server Components haben Komponenten in ein Wire Format verwandelt. tRPC hat Funktionsaufrufe in RPCs verwandelt (und war wenigstens ehrlich dabei). GraphQL hat das Schema in eine Netzwerk Oberfläche verwandelt und dann ein Jahrzehnt damit verbracht, Query-Depth Limits zu erklären. WebSockets haben Event Handler in eine langlebige TCP Session mit Backpressure und Framing verwandelt. Jedes davon ist ein echter Ergonomie Gewinn. Jedes davon hat eine Protokoll Grenze an einen Ort verschoben, an dem Entwickler gewohnheitsmäßig nicht hinschauen.

Die Lektion ist nicht "nutze die Magic nicht". Die Lektion ist, dass, wenn ein Framework eine Netzwerk Grenze in Entwickler Ergonomie auflöst, das Threat Model immer noch jemandem eine Postmortem schuldet. Normalerweise schreibt sie das Framework Team. Manchmal schreibt stattdessen ein CVE sie.

Patche den Parser. Begrenze den Body. Lies das Protokoll. Das nächste Flight CVE wird gerade irgendwo geschrieben, und die Version danach, und die danach. Die Version von dir, die unvalidierte Server Actions geschrieben hat, hatte einen guten Grund: das Framework hat ihr gesagt, sie müsse das nicht. Die Version von dir, die das hier gerade liest, weiß es besser.


S
Geschrieben von
Sascha Becker
Weitere Artikel