Am 9. September 2026, 14:00 UTC, schaltete Cloudflare das Modell @cf/zai-org/glm-5.3 in Workers AI auf privat. Ab diesem Moment erhielten alle Anfragen HTTP 400. Vorausgegangen war eine HackerOne-Meldung vom Vorabend: Bei sehr langen Prompts gab das Modell gelegentlich Text aus fremden Sitzungen zurück. Bei solchen Berichten erwartet man einen Fehler in der Zugriffskontrolle – ein falsch gesetztes Tenant-Tag, eine vergessene Berechtigungsprüfung, ein durchgereichtes Token. Hier lag der Fehler woanders. Die Grenze zwischen zwei Kunden wurde nicht durch eine fehlerhafte Abfrage überschritten, sondern durch eine Speicherfläche, die aus Geschwindigkeitsgründen zwischen zwei Anfragen nie vollständig geleert wird.
155 Antworten, die fremde Fragmente enthielten
Cloudflare hat den Vorfall in einem Bericht dokumentiert, der zeitgleich mit einer Stellungnahme der SGLang-Maintainer erschien. Betroffen waren zwischen dem 27. August und dem 9. September 2026 insgesamt 155 Antworten – ausschließlich auf Prompts mit mehr als 681.280 Tokens. Gemeldet hat den Fehler der Sicherheitsforscher Gadi Evron von Knostic über das HackerOne-Programm des Unternehmens. Nach Abzug der eigenen Tests und der Reproduktion des Forschers, so Cloudflare, passen die übrigen Anfragen in Zeitpunkt und Umfang zu gewöhnlicher Long-Context-Nutzung, nicht zu wiederholter Ausnutzung. Hinweise auf eine gezielte Ausbeutung fand das Unternehmen nach eigenen Angaben nicht.
Für die Einordnung ist das wichtig, an der Schwere ändert es nichts. Ein Datenleck wird nicht dadurch harmlos, dass es niemand bemerkt hat. Wer Prompts mit mehreren hunderttausend Tokens an ein Modell schickt, tut das meist mit Codebasen, Vertragswerken oder internen Dokumenten – Material, das nicht für fremde Augen gedacht ist. Dass nur ein Modell betroffen war und nur oberhalb einer sehr hohen Prompt-Länge, verkleinert den Kreis der Geschädigten. Die Aussage bleibt: Eine Isolation, die nur unterhalb einer Größenschwelle hält, ist keine Isolation, sondern eine Wahrscheinlichkeitsaussage.
Warum der KV-Cache zwischen zwei Anfragen nicht gewischt wird
Ein Blick auf die Art, wie große Sprachmodelle heute bedient werden, hilft beim Verständnis. Cloudflare betreibt GLM 5.3 in einer disaggregierten Konfiguration: Ein Pool von Prefill-GPUs liest den Prompt und berechnet daraus den KV-Cache, also die Zwischenstände, die das Modell für jedes Token bereits geleistet hat. Per RDMA über Mooncake gehen diese Zwischenstände an einen Pool von Decode-GPUs, die daraus die Antwort Token für Token generieren. Das spart Rechenzeit, weil Prefill und Decode unterschiedliche Hardwareprofile besser ausnutzen.
Der KV-Cache liegt im GPU-Speicher als Pool aus gleich großen Seiten. Man kann sich das als lange Werkbank vorstellen, unterteilt in feste Segmente. Wer einen Auftrag beginnt, bekommt so viele Segmente, wie sein Prompt lang ist, und legt dort seine Zwischenergebnisse ab. Ist der Auftrag fertig, gibt er die Segmente frei – abgewischt werden sie nicht. Der nächste Auftrag überschreibt sie mit seinem eigenen Material. Solange jeder Auftrag alle angeforderten Segmente vollständig beschreibt, sieht niemand die Reste. Wird ein Segment dagegen angefordert und dann nicht beschrieben, bleibt das alte Material liegen. Der nächste Leser sieht es.
Das ist branchenüblich und nötig für konkurrenzfähigen Durchsatz; Millionen Tokens Speicher pro Anfrage auf null zu setzen, wäre zu teuer. Der Preis: Die Inferenz-Engine muss die Eigentumsverhältnisse an diesen Seiten sauber führen. Das ist anspruchsvoller, als es klingt. Seiten werden innerhalb eines Knotens mehrfach referenziert, etwa beim Offloading und bei der Token-Generierung mit unterschiedlichen Lebensdauern. Im disaggregierten Betrieb hält zusätzlich der entfernte Prefill-Knoten eine veränderliche Referenz auf dieselbe Seite. Wer hier den Überblick verliert, verliert die Isolation.
Zwei Ursachen, die sich überlagern: Konfiguration und Engine-Bug
Cloudflare beschreibt das Versagen als Zusammenspiel zweier unabhängiger Probleme. Das erste war eine Fehlkonfiguration: GLM 5.3 wurde mit einem Kontextfenster von 1.048.576 Tokens ausgeliefert, der KV-Cache-Pool auf den Prefill-GPUs fasste aber nur 681.280 Tokens. Diese Diskrepanz war dem Team vor dem Start bekannt. Man ging davon aus, dass die Engine überlange Anfragen mit einem Fehler ablehnt, und die eigenen Long-Context-Tests lieferten erfolgreiche Antworten – die Differenz galt deshalb nicht als Problem. Die Poolgröße ist kein Wert, den man frei setzt. SGLang berechnet sie beim Start aus dem GPU-Speicher, der nach dem Laden der Modellgewichte übrig bleibt. Die beworbene Kontextlänge ist ein separater Wert. Nichts erzwingt, dass beide übereinstimmen.
Das zweite war ein Bug in SGLang, der genau den Ablehnungspfad betraf, der hätte schützen sollen. Wurde ein Prompt abgelehnt, weil er nicht in den Pool passte, notierte der Scheduler den Fehler im Feld to_finish und ersetzte den Prompt intern durch einen Ein-Token-Platzhalter. So konnte die Anfrage ohne echte Speicherallokation durch die Batch-Maschinerie auslaufen. Auf einem einzelnen Knoten funktionierte das. Im disaggregierten Modus prüften die Admission-Queues auf Prefill und Decode jedoch, ob eine Anfrage finished ist, nicht ob sie abgelehnt wurde – und eine abgelehnte Anfrage meldete sich als lebendig. Der Router teilte dem Decode-Knoten acht Seiten zu, der Prefill-Knoten nahm die Anfrage an, rechnete den Platzhalter durch und übertrug eine einzige Seite. Der Decode-Knoten erhielt ein „Transfer complete“ und ging davon aus, dass der Prefill-Knoten den Cache mit dem vollständigen Prompt überschrieben hatte. Dann generierte er eine Antwort, bei der die Attention über alle acht reservierten Seiten lief – sieben davon enthielten noch Fragmente früherer Anfragen anderer Nutzer. Der Client bekam HTTP 200 statt HTTP 400, prompt_tokens passte zur langen Eingabe, die Antwort war kohärent. Sie kam nur verdächtig schnell und enthielt fremdes Material.
Vom Report bis zur Wiederherstellung: der Zeitplan
Die Meldung ging am 8. September um 23:11 UTC ein. Am 9. September um 08:00 begann die Verifikation, um 13:22 wurde der Sicherheitsvorfall erklärt. Innerhalb von 25 Minuten reproduzierte das Product-Security-Team das Leck mit zwei unabhängigen Konten. Um 14:00 wurde GLM 5.3 auf privat gesetzt. Um 14:35 war die Ursache als zu kleiner Prefill-Pool identifiziert und ein Canary-Knoten mit größerem Pool als nicht anfällig bestätigt. Um 14:52 waren alle Prefill-Knoten auf die korrigierte Konfiguration umgestellt. Um 19:04 war das Deployment mit zusätzlichem, selbst geschriebenem Engine-Patch neu gebaut und gegen das Reproduktionsskript des Forschers validiert. Um 19:40 wurde das Modell wieder öffentlich zugänglich gemacht.
Danach folgte die langsamere Arbeit. Am 10. September traf man sich mit den SGLang-Maintainern, zwischen dem 11. und 19. September wurden zusätzliche Härtungen auf alle produktiven SGLang-Deployments ausgerollt, bis Mitte Oktober flossen die Korrekturen upstream ein. Ein Detail aus der Aufarbeitung: Der zugrunde liegende Bug war bereits am 6. Juli 2026 als Issue #30233 bei SGLang gemeldet worden. Cloudflare bemerkte das erst bei der Untersuchung dieses Vorfalls. Bekannte Fehler in Abhängigkeiten können lange unentdeckt im eigenen Betrieb liegen.
Was Cloudflare und die SGLang-Maintainer dauerhaft geändert haben
Auf Konfigurationsseite wurde die GLM-5.3-Einstellung an die von GLM 5.2 angeglichen. Dadurch wurde genug GPU-Speicher frei, um den Prefill-Pool auf etwa 2,04 Millionen Tokens zu bringen – rund das Doppelte der Kontextlänge von 1.048.576. Ein Prompt, der ins Kontextfenster passt, passt damit immer in den Pool; der Ablehnungspfad wird für gültige Anfragen nie erreicht. Cloudflare betont aber selbst, dass diese Maßnahme allein fragil wäre. Jede Änderung an Quantisierung, Tensor-Parallelität, CUDA-Graph-Größen oder eine künftige Modellversion verschiebt die Poolgröße auf eine Weise, die man leicht übersieht. Deshalb wurde zusätzlich die Engine gepatcht: Eine Anfrage mit ausstehender Ablehnung wird schon an der Disaggregation-Admission-Queue mit ihrem ursprünglichen Fehler beendet – vor Bootstrap, Allokation und KV-Transfer.
Dieser Patch wurde als #38704 upstream vorgeschlagen und enthält zusätzlich eine Prüfung, mit der der Decode-Knoten verifiziert, dass die Anzahl übertragener Tokens der reservierten Anzahl entspricht. Der Fix war in angepasster Form bereits über #36988 auf dem SGLang-Hauptzweig gelandet, gemergt am 6. September für einen verwandten Fehlerpfad, aber noch nicht veröffentlicht. Die Maintainer übernahmen mit #38935 zusätzlich den früheren Ablehnungszeitpunkt. Cloudflare empfiehlt allen Betreibern, auf SGLang v0.5.22 oder neuer zu aktualisieren; die Korrekturen wurden über v0.5.20 bis v0.5.22 ausgeliefert. Bestätigt ist außerdem, dass ausschließlich GLM 5.3 betroffen war. Bei allen anderen disaggregierten Deployments auf Workers AI sind die KV-Pools auf Prefill- und Decode-Seite größer als die Kontextlänge, sodass keine Anfrage den Ablehnungspfad erreichen konnte. Die Engine-Korrektur und die Härtungen gelten inzwischen für jedes mit SGLang betriebene Modell. Neue Deployments werden ohne diese Prüfungen nicht mehr ausgerollt.
Was Betreiber eigener Deployments daraus mitnehmen sollten
Die wichtigste Lehre liegt nicht im Detail des SGLang-Bugs, sondern in der Testlücke, die ihn so lange unentdeckt ließ. Cloudflare gibt offen zu, beim Benchmarking nur HTTP-Statuscodes und Engine-Performance geprüft zu haben – nicht die Kohärenz der Modellausgabe. Eine Antwort mit Status 200 und flüssigem Text sieht in einem Lasttest wie ein Erfolg aus, auch wenn sie aus fremden Speicherseiten stammt. Wer eigene Inferenz-Stacks betreibt, sollte hier ansetzen: Ein Statuscode sagt nichts über Korrektheit, und ein Benchmark, der nur Durchsatz misst, prüft die Sicherheitsgrenzen überhaupt nicht.
Konkret heißt das, die Kopplung zweier Werte sichtbar zu machen, die aus verschiedenen Quellen stammen. Die Kontextlänge steht in der Modellkonfiguration, die Poolgröße ergibt sich beim Start aus dem freien GPU-Speicher. SGLang protokolliert diese Zahl beim Hochfahren und stellt sie als Metrik bereit. Sie gehört in ein Deployment-Gate und ins Alerting, getrennt für Prefill- und Decode-Seite. Ergänzen lassen sich Canary-Marker: kurze, eindeutig erkennbare Zeichenketten in Testanfragen, die man anschließend in den Antworten anderer Sitzungen sucht. Cloudflare hat seine Korrekturen so validiert; das Verfahren lässt sich mit überschaubarem Aufwand nachbauen.
Die zweite Lehre betrifft Ablehnungspfade im Allgemeinen. Code, der nur im Fehlerfall läuft, wird im Normalbetrieb nie berührt und entsprechend selten getestet. Hier war es ein Pfad, der auf einem einzelnen Knoten korrekt aussah und im verteilten Betrieb still die Reihenfolge der Zustände verletzte. Ein Feld wurde gesetzt, ein anderes abgefragt, und dazwischen lag die Annahme, dass beides dasselbe bedeutet. Wer verteilte Systeme baut, kennt diese Fehlerklasse: selten dramatische Abstürze, eher stille Divergenzen zwischen dem, was ein Knoten glaubt, und dem, was ein anderer getan hat. Es reicht nicht, die Segmente zu reservieren. Man muss auch prüfen, ob sie tatsächlich beschrieben wurden, bevor man den nächsten daran arbeiten lässt.
Zwischen dem Launch am 27. August und dem Bekanntwerden am 8. September lagen zwölf Tage, in denen eine bekannt fehlerhafte Konfiguration produktiv lief. Das ist keine Nachlässigkeit, die nur Cloudflare betrifft, sondern die typische Dynamik bei Modell-Rollouts: Der Druck, ein neues Modell mit großem Kontextfenster anzubieten, ist hoch. Die Prüfung der Speichergrenzen ist unspektakuläre Arbeit, die niemand sieht, solange nichts passiert. Dass die Offline-Nahme innerhalb von 40 Minuten nach der Incidents-Erklärung erfolgte und die Wiederherstellung noch am selben Tag, spricht für ein eingespieltes Verfahren. Die Prüfung vor dem Start ersetzt das nicht. Und die besteht nicht darin, dass die Tests grün werden, sondern darin, dass man weiß, was die Tests tatsächlich gemessen haben.
Quelle: blog.cloudflare.com
