Blootstelling van KV-cache tussen verzoeken in het GLM-5.3 Workers AI-model en onze herstelmaatregelen met SGLang

Op 8 september 2026 werden we door beveiligingsonderzoeker Gadi Evron van Knostic gewaarschuwd voor een probleem met de isolatie tussen tenants dat het GLM-5.3 Workers AI-model (@cf/zai-org/glm-5.3) beïnvloedde, beginnend op 27 augustus 2026. We hebben het probleem verholpen en het model op 9 september hersteld. We hebben bevestigd dat geen enkel ander model op Workers AI is beïnvloed.

Tussen 27 augustus en 9 september ontving een klein aantal klanten die het GLM 5.3 Workers AI-model gebruikten antwoorden die gegevens bevatten uit verzoeken van andere klanten die kort daarvoor aan hetzelfde model waren gesteld. In totaal waren 155 antwoorden beïnvloed, allemaal bij prompts langer dan 681.280 tokens.

Na het uitsluiten van onze eigen tests en die van de onderzoeker, zijn de geïdentificeerde verzoeken in timing en volume consistent met gewoon gebruik van lange contexten, en niet met iemand die de bug herhaaldelijk exploiteerde. We hebben geen bewijs gevonden van opzettelijke exploitatie.

Dit rapport wordt gepubliceerd als onderdeel van een gecoördineerde openbaarmaking, samen met een bericht van SGLang, de open-source inference engine die we gebruiken. In dit bericht beschrijven we de bug die dit probleem veroorzaakte, onze tijdlijn van respons en de daaropvolgende versteviging van onze productie-implementaties van SGLang. Ook gaan we dieper in op onze voortdurende inspanningen met de upstream-onderhouders van SGLang om dit en soortgelijke bugs aan te pakken.

Wat is er gebeurd?

GLM is een tekstgeneratiemodel van Z.ai dat is gebouwd voor agentic coding-workflows; het is een van de vele modellen die beschikbaar zijn via Workers AI. Gadi Evron diende op 8 september een rapport in via ons HackerOne-programma, waarin hij stelde dat zeer lange prompts die naar @cf/zai-org/glm-5.3 op Workers AI werden gestuurd, soms tekst teruggaven die bij sessies van andere gebruikers hoorde. Cloudflare reproduceerde het probleem de volgende ochtend en haalde het model binnen 40 minuten na het uitroepen van een incident offline. Vervolgens hebben we een gecorrigeerde configuratie en een gepatchte inference engine geïmplementeerd en de dienst diezelfde dag hersteld.

Deze specifieke blootstelling van de KV-cache tussen verzoeken was het resultaat van twee overlappende problemen.

Ten eerste was er een foutieve configuratie van de open-source inference engine, SGLang, die door Cloudflare wordt gebruikt. We implementeerden GLM 5.3 met een contextvenster van 1.048.576 tokens, maar de KV-cache pool op onze prefill-GPU's had slechts ruimte voor 681.280 tokens. We waren ons bewust van dit verschil vóór de lancering. We verwachtten dat de engine een foutmelding zou geven voor elke prompt die hij niet kon verwerken, en onze tests met lange contexten gaven succesvolle antwoorden, dus we dachten niet dat dit een probleem was.

Ten tweede was er een bug in hoe SGLang omging met de resulterende te lange verzoeken in de gedisaggregeerde prefill/decode-modus. Een prompt die niet in de pool paste, werd intern gemarkeerd als afgewezen en vervangen door een placeholder van één token. In plaats van het verzoek af te breken en een client-fout terug te geven, genereerde de prefill de KV-cache voor de placeholder-prompt en transferreerde deze naar de decode-node. De decode-node ontving een "transfer complete"-bericht en nam aan dat de prefill de KV-cache van vorige verzoeken had overschreven met de volledige prompt voor dit verzoek, terwijl in werkelijkheid het grootste deel van de vorige staat intact bleef. De decode-node genereerde vervolgens een antwoord terwijl hij putte uit verouderde cache-pagina's die nog gegevens van eerdere verzoeken bevatten. Hierdoor konden antwoorden op prompts langer dan 681.280 tokens gegevens bevatten uit andere GLM 5.3-sessies van gebruikers.

Onmiddellijke herstelmaatregelen

Na het uitschakelen van het model hebben we de volgende acties ondernomen om deze kwetsbaarheid te corrigeren voordat we de publieke toegang herstelden:

  1. Correctie van de configuratie: We hebben de pool zo ingesteld dat deze de volledige context bevat. We hebben de configuratie van GLM 5.3 aangepast aan die van GLM 5.2, wat genoeg GPU-geheugen vrijmaakte zodat de prefill KV-pool nu ongeveer 2,04 miljoen tokens bevat, wat ongeveer twee keer de contextlengte van 1.048.576 tokens is. Een prompt die in het contextvenster past, past nu altijd in de pool, waardoor het afwijzingspad bij een geldig verzoek nooit wordt bereikt. Dit alleen voorkomt al dat de bug wordt geactiveerd.
  1. Correctie van de engine: We hebben het afwijzingspad veilig gemaakt. Alleen vertrouwen op de poolgrootte is fragiel: elke wijziging in kwantisering, tensor-parallellisme, CUDA-graph-groottes of een toekomstige modelversie verandert de poolgrootte op een manier die gemakkelijk over het hoofd wordt gezien. Om dit te voorkomen, hebben we de engine gepatcht zodat een verzoek met een hangende afwijzing wordt beëindigd in de disaggregatie-toelatingswachtrij, met de oorspronkelijke foutmelding, voordat er bootstrap, allocatie of KV-transfer plaatsvindt. We hebben dit upstream voorgesteld als #38704, waarbij de decode-node ook verifieert of het aantal overgedragen tokens overeenkomt met het gereserveerde aantal.

We hebben beide wijzigingen gevalideerd met het reproductiescript van de onderzoeker en onze eigen canary-marker-tests voordat we het model herstelden.

Achtergrond: hoe we GLM 5.3 aanbieden

Workers AI draait GLM 5.3 met SGLang in een gedisaggregeerde configuratie: een pool van prefill-GPU's verwerkt de prompt en produceert de KV-cache van het model, en draagt die cache vervolgens via Remote Direct Memory Access (RDMA), via Mooncake, over aan een pool van decode-GPU's die het antwoord genereren.

De KV-cache in het GPU-geheugen is een gedeelde pool van pagina's met een vaste grootte. Elk verzoek krijgt pagina's toegewezen bij toelating en geeft deze vrij wanneer het is voltooid. Pagina's worden momenteel niet genuld tussen verzoeken; het volgende verzoek dat een pagina krijgt, overschrijft deze simpelweg. Deze aanpak is gebruikelijk bij inference engines om de beste prestaties te behalen, maar het betekent dat de inference engine verantwoordelijk is voor het bijhouden van het eigendom van deze geheugenpagina's.

Bovendien is de grootte van deze pool geen vaste configuratiewaarde. SGLang bepaalt deze bij opstarten op basis van het resterende GPU-geheugen na het laden van de modelgewichten. De geadverteerde contextlengte van het model is een aparte configuratiewaarde. Er is niets dat dwingt dat deze twee overeenstemmen; dat is onze taak om te controleren.

Probleem 1: Foutieve configuratie

Op onze GLM 5.3 prefill-nodes was de pool 681.280 tokens, terwijl de contextlengte van het model 1.048.576 tokens is. De geselecteerde KV-poolgrootte wordt door SGLang in logs tijdens het opstarten geprint en als metriek weergegeven. We waren ons bewust van dit verschil, maar tijdens het testen voorafgaand aan de lancering zagen we succesvolle HTTP-statuscodes en een streaming body zonder foutframes tijdens benchmarking. We namen aan dat de engine in staat was om het verschil tussen de KV-cache pool en het gevraagde contextvenster via streaming of een ander mechanisme elegant af te handelen.

Dit was een kritiek gat in onze tests: we valideerden alleen HTTP-responsecodes en engine-prestaties bij lange contexten, zonder de output en coherentie van het model te valideren. We controleren nu de KV-poolgrootte direct tegen de contextlengte vanuit de draaiende engine, voordat elke wijziging in de gedisaggregeerde implementatie wordt doorgevoerd.

Probleem 2: Bug in SGLang

In v0.5.21 heeft het SGLang-team deze bug in de afhandeling van prompts die langer zijn dan de KV-poolgrootte aangepakt. Dit is wat er gebeurde:

Wanneer er een prompt arriveerde die langer was dan de KV-pool kon bevatten, wees de scheduler van SGLang deze af. Het afwijzingspad (Req.setfinishwith_abort) legde de fout vast die aan de client teruggestuurd moest worden en verving de prompt door een placeholder van één token, zodat het verzoek via de normale batch-machinerie kon lopen zonder echt geheugen te alloceren. In een single-node implementatie werkte dit: de placeholder draaide, de batch werd voltooid en de client ontving een fout.

In de gedisaggregeerde modus, zoals wij deze gebruiken, controleerden de toelatingswachtrijen op zowel prefill als decode of een verzoek voltooid was, niet of het was afgewezen. Een afgewezen verzoek rapporteerde zichzelf als "live". Dit leidde tot het volgende proces:

  1. Bootstrap: De router vertelde de decode-node de promptlengte (bijv. 8 pagina's). Decode reserveerde 8 KV-pagina's voor het verzoek.
  2. Prefill toelating: De prefill-toelatingswachtrij liet elk verzoek toe waarbij req.finished() False was. Een afgewezen verzoek sloeg zijn afwijzing op in tofinish, niet in finishedreason, dus finished() gaf nog steeds False terug. Het afgewezen verzoek werd toegelaten.
  3. Prefill forward pass: De afwijzing had de prompt al vervangen door een placeholder van één token. Prefill voerde een triviale forward pass uit op dat ene token.
  4. KV transfer: Prefill stuurde één pagina KV naar decode en gaf aan dat de transfer voltooid was.
  5. Decode toelating: De toelatingswachtrij van Decode had dezelfde finished()-check en accepteerde de overdracht.
  6. Decode attention: Decode begon met genereren en attention las alle 8 gereserveerde pagina's. Alleen pagina P0 was door dit verzoek beschreven. Pagina's P1 tot P7 bevatten nog wat eerdere verzoeken hadden achtergelaten: prompts en gedeeltelijke generaties van andere gebruikers.
  7. Respons: De client kreeg HTTP 200 in plaats van HTTP 400. prompt_tokens kwam overeen met de lange prompt en het antwoord was coherent. Het arriveerde sneller dan een 8-pagina prefill zou moeten duren, en het bevatte inhoud uit KV-pagina's van andere gebruikers.

Elk verzoek boven de drempelwaarde gedroeg zich op deze manier. Deze bug was al gemeld bij SGLang door een andere gebruiker als issue #30233 op 6 juli 2026. De patch voor dit probleem is uitgebracht in SGLang v0.5.20, met aanvullingen in v0.5.21 en v0.5.22. We raden alle operators dringend aan om SGLang-dependencies te updaten naar v0.5.22 of later.

GLM 5.3 was het enige model dat door deze bug werd beïnvloed. Elke andere gedisaggregeerde implementatie op Workers AI heeft KV-pools die groter zijn dan de contextlengte op zowel prefill als decode.

Tijdlijn

Alle tijden in UTC.

DatumGebeurtenis
27 aug 2026GLM 5.3 gelanceerd op Workers AI met een prefill KV-pool kleiner dan het contextvenster. De kwetsbaarheid was vanaf de lancering aanwezig.
8 sep 2026 - 23:11Rapport ontvangen via HackerOne.
9 sep 2026 - 08:00Cloudflare begint met het verifiëren van het HackerOne-rapport.
9 sep 2026 - 13:22Beveiligingsincident uitgeroepen. Product Security reproduceerde het lek binnen 25 minuten met twee onafhankelijke accounts.
9 sep 2026 - 14:00GLM 5.3 werd op privé gezet. Verzoeken geven HTTP 400 terug. Statuspagina bijgewerkt.
9 sep 2026 - 14:35Grondoorzaak geïdentificeerd als de prefill-pool die kleiner is dan de contextlengte.
9 sep 2026 - 14:52Alle prefill-nodes overgezet naar de gecorrigeerde configuratie.
9 sep 2026 - 19:04Volledige implementatie herbouwd met gecorrigeerde configuratie plus een door Cloudflare geschreven engine-patch.
9 sep 2026 - 19:40Model hersteld naar publiek.
10 sep 2026 - 23:30Overleg met SGLang-onderhouders (RadixArk CTO en kernengineers) om upstream-fixes af te spreken.
11-19 sep 2026Aanvullende verstevigingen uitgerold naar alle productie-SGLang-implementaties.
16 sep - 12 okt 2026Upstream SGLang voegt de admission-fix, per-transfer checksums en abort-ACK preservatie samen.
2 okt 2026SGLang v0.5.21 uitgebracht.
19 okt 2026SGLang v0.5.22 uitgebracht.
26 okt 2026Gecoördineerde publicatie met de onderzoeker en RadixArk.

Voortdurende verbeteringen

Deze bug behoort tot een klasse van KV-cache geheugenveiligheidsbugs waar Cloudflare al enkele maanden aan werkt. We zien twee veelvoorkomende oorzaken:

  1. Pagina-levensduur binnen een enkele node: Meerdere taken op een decode-node kunnen tegelijkertijd dezelfde pagina vasthouden (token-generatie, offload naar host-geheugen, KV-transfer ontvanger, en abort/error-paden). Als één taak de pagina vrijgeeft terwijl een andere deze nog gebruikt, kan een nieuw verzoek die pagina krijgen terwijl deze nog wordt gelezen of geschreven.
  2. Race-condities tussen nodes: Wanneer een verzoek wordt afgebroken terwijl prefill nog zijn KV-cache naar decode schrijft over RDMA, kunnen de reeds verzonden writes niet worden teruggeroepen. Als decode die pagina's direct vrijgeeft, kunnen ze aan een ander verzoek worden gegeven, waardoor de late writes in de KV-cache van dat verzoek terechtkomen.

Sinds het incident hebben we het volgende gedaan:

  • Uitgebreide uitgestelde vrijgave (deferred release): Deze houdt nu ook de bronpagina's van prefill vast en dekt elk type fout, niet alleen aborts.
  • Begrensde Mooncake transfer batches: Hierdoor kan geen enkele transfer-call langer duren dan de release-timeout.
  • Aanpassing loadbalancer: Een client-disconnect verbreekt nu niet meer een verzoek midden in de transfer.
  • Controle bij implementatie: Elke wijziging in de gedisaggregeerde implementatie wordt nu gecontroleerd op KV-poolgrootte t.o.v. contextlengte, deferred release op beide rollen, en toegepaste patches.

Samenwerking met het SGLang-team

We zijn op 10 september overlegd met de SGLang-onderhouders bij RadixArk. De volgende verbeteringen zijn doorgevoerd in SGLang (meestal in v0.5.22) om dit incident en gerelateerde race-condities aan te pakken:

CategoriePull RequestReleaseFunctie
Incident#36988v0.5.20Beëindigt afgebroken prefill-verzoeken voordat het resultaat decode bereikt.
Incident#38935v0.5.21Weist verzoeken af bij toelating, vóór bootstrap, allocatie of KV-transfer.
Race#39461, #41612v0.5.21, v0.5.22sgl-router annuleert beide helften van een verzoek bij client-disconnect of prefill-fout.
Race#37454, #41023v0.5.20, v0.5.21Schakelt deferred KV release standaard in op ondersteunende backends.
Race#40645, #41402v0.5.21Fixes voor gevallen waarin de drain-bevestiging verloren ging.
Race#41404v0.5.21Houdt pagina's vast bij elke transfer-fout, niet alleen bij decode-initiated aborts.
Race#43719v0.5.22Een mislukte Mooncake-send rapporteert zijn writes niet langer als "drained".
Race#37077v0.5.22Tagged abort-berichten per verzoek om misbruik van hergebruikte ID's te voorkomen.
Race#36612v0.5.20Deelt prefill-to-decode foutmeldingen via Mooncake, NIXL en MORI.
Race#32758v0.5.20Optionele limiet op Mooncake transfer batch size.
Lifetime#30899v0.5.21Decode wacht op lopende offload naar host-geheugen vóór vrijgave pagina's.
Lifetime#43562v0.5.22Houdt tensor-parallel ranks in overeenstemming bij falen host-memory restore.
Observability#39500v0.5.21Voegt checksum-verificatie toe voor de overdracht van KV naar decode.
Observability#38596, #40642v0.5.20, v0.5.21Fixes voor SGLang's KV-canary ter detectie van corruptie.
Observability#41128v0.5.22Metrieken voor deferred release (aantal, duur, type beëindiging).

Aanbevelingen voor andere operators

Voor organisaties die SGLang in gedisaggregeerde prefill/decode-modus draaien, adviseren we het volgende:

  1. Meet uw KV-pool en vergelijk deze met uw contextlengte op elke prefill- en decode-node via de opstartlogs. Als de pool kleiner is, verlaag dan de contextlengte. Vertrouw niet op een proxy om deze limiet af te dwingen.
  2. Update naar een build die #36988 en #38935 bevat (v0.5.22 of later), zodat een te lang verzoek wordt afgewezen vóór de overdracht.
  3. Gebruik deferred KV release op beide rollen en begrens uw Mooncake transfer batches.
  4. Schakel corruptiedetectie in waar het model dit ondersteunt (--disaggregation-enable-kv-checksum en --kv-canary).
  5. Zorg dat uw proxy geen onnodige aborts genereert. Configureer uw loadbalancer zo dat verzoeken kunnen doorgaan tot het eerste token.

Lange termijn

Integriteit door constructie

Configuratiecontroles verminderen het aantal fouten, maar maken misbruik niet onmogelijk. De duurzame oplossing is dat de engine bij het lezen detecteert dat een verzoek toegang probeert te krijgen tot een pagina die het niet bezit, en dat specifieke verzoek faalt in plaats van data van anderen terug te geven. We werken met het SGLang-team aan een productieversie van pagina-level tags die elke leesactie controleren.

Integriteit door mathematische onmogelijkheid

RDMA kopieert data direct van het geheugen van de ene node naar de andere. Elke mismatch kan ervoor zorgen dat de ontvanger de verkeerde data leest. We passen nu onze ervaring met formele verificatiemethoden (zoals gebruikt in Topaz) toe op KV-cache transfer engines. Het doel is om het gedistribueerde KV-cache systeem van SGLang met Mooncake te modelleren en te bewijzen dat bugs zoals deze mathematisch onmogelijk zijn.

Gecoördineerde openbaarmaking

We publiceren dit rapport als onderdeel van een gecoördineerde openbaarmaking. We willen onderzoeker Gadi Evron bedanken voor het melden van dit probleem via ons HackerOne-programma met een helder, reproduceerbaar script. Ook danken we het SGLang-team bij RadixArk voor hun snelle samenwerking bij het implementeren van de fixes.