C’è un tipo di incidente informatico che dovrebbe preoccupare più di un servizio che va giù: quello in cui il servizio continua a funzionare, risponde con aria convinta e nel frattempo attraversa un confine che non avrebbe mai dovuto superare. Nel caso di GLM-5.3 su Cloudflare Workers AI, quel confine separava le richieste di clienti diversi. La risposta arrivava. Il problema è che una parte della memoria usata per produrla apparteneva a qualcun altro.
Il postmortem pubblicato da Cloudflare il 26 ottobre 2026 descrive un’esposizione avvenuta fra il 27 agosto e il 9 settembre. Secondo l’indagine dell’azienda, 155 risposte del modello hanno contenuto dati provenienti da altre richieste, tutte in presenza di prompt superiori a 681.280 token. È il numero delle risposte interessate, non il numero dei clienti coinvolti; e non è una misura indipendente dell’intero impatto.
La tentazione è archiviarlo come l’ennesimo bug dell’AI. Sarebbe un modo elegante per non capire niente. Qui non c’entrano un prompt ingegnoso, un modello che inventa fatti o qualche misteriosa volontà della macchina. C’entrano capacità di memoria, ownership, messaggi tra processi e un percorso di errore che ha continuato a comportarsi come un successo. Insomma, software.
Un milione di token dichiarati, ma il pool ne conteneva meno
GLM-5.3 era configurato per una finestra di contesto da 1.048.576 token. Il pool della KV cache sulle GPU dedicate al prefill, però, poteva contenere soltanto 681.280 token. La differenza non era un dettaglio cosmetico: una richiesta poteva essere formalmente valida rispetto al contesto pubblicizzato e, nello stesso tempo, non entrare nella memoria disponibile per elaborarla.
Cloudflare riconosce di conoscere il disallineamento prima del lancio. La sua aspettativa era che il motore di inferenza rifiutasse correttamente le richieste troppo grandi. I test sui contesti lunghi restituivano risposte riuscite e quindi il problema non era stato interpretato come una condizione di rischio. Ma un test che verifica soltanto se qualcosa risponde sta misurando disponibilità, non correttezza. E, soprattutto, non sta misurando isolamento.
La KV cache conserva le rappresentazioni di chiavi e valori usate dal meccanismo di attenzione durante l’inferenza. È un’ottimizzazione fondamentale: senza riutilizzare quello stato, ogni nuovo token costringerebbe a ripetere molto lavoro. In un servizio multi-tenant, però, quelle pagine di memoria vengono allocate, liberate e riutilizzate da richieste diverse. Non devono necessariamente essere azzerate a ogni passaggio, purché il sistema garantisca che nessuna richiesta legga pagine che non le appartengono.
Il prefill ha detto “finito”. Il decode gli ha creduto.
Workers AI utilizzava SGLang in modalità prefill/decode disaggregata: un gruppo di GPU elabora il prompt e prepara la KV cache, un altro genera i token di risposta. Lo stato passa fra i due attraverso trasferimenti di memoria remoti. È un’architettura che permette di specializzare e dimensionare separatamente due fasi con carichi molto diversi. Ma introduce un contratto che non può essere lasciato all’ottimismo: quando il decode riceve il segnale di trasferimento completato, deve poter assumere che le pagine riservate contengano davvero i dati della richiesta corrente.
Il bug rompeva proprio quel contratto. Quando il prompt non entrava nel pool, SGLang marcava internamente la richiesta come rifiutata e sostituiva il contenuto con un segnaposto di un solo token. Nel percorso disaggregato, però, la richiesta proseguiva comunque verso il trasferimento. Il prefill preparava lo stato del segnaposto, segnalava il completamento e il decode continuava usando lo spazio che aveva riservato per il prompt originale. Le pagine non riscritte potevano conservare stato di richieste precedenti.
Il risultato era particolarmente insidioso: il decode non riceveva semplicemente un buffer vuoto o un errore evidente. Poteva leggere informazioni residue e generare testo che includeva contenuti di un altro tenant. Il client vedeva HTTP 200 dove avrebbe dovuto ricevere un rifiuto. Un successo del protocollo diventava la confezione di un fallimento di sicurezza.
La memoria condivisa non è il problema. Il problema nasce quando un componente considera completato un lavoro che un altro componente non ha mai eseguito davvero.
Il proprietario della memoria non può essere un’ipotesi
Nella programmazione concorrente conosciamo da decenni questa famiglia di problemi. Una risorsa viene liberata, un altro attore la riutilizza, ma qualcuno conserva ancora un riferimento o un’operazione asincrona non è terminata. In un’applicazione tradizionale possiamo ottenere corruzione dei dati, comportamenti intermittenti o crash. In un servizio di inferenza condiviso, la stessa classe di errore può diventare esposizione di informazioni fra clienti.
È utile distinguere due meccanismi descritti nella disclosure coordinata. L’incidente specifico di GLM-5.3 nasceva dal percorso di rifiuto che lasciava il decode leggere pagine non inizializzate per quella richiesta. Un problema correlato di lifetime, discusso dal team SGLang, riguarda invece scritture RDMA ancora in volo dopo l’abort: se le pagine vengono riassegnate troppo presto, un trasferimento tardivo può sovrascrivere la cache del tenant successivo. Non sono due descrizioni intercambiabili delle 155 risposte; sono due modi in cui lo stesso principio architetturale può fallire.
Nel primo caso manca una garanzia di completamento valido. Nel secondo manca una garanzia di fine utilizzo. Il risultato concettuale è simile: la memoria è stata dichiarata disponibile, pronta o appartenente a qualcuno senza che tutte le operazioni coinvolte avessero rispettato quel confine. Il problema del percorso di rifiuto era già documentato nel repository SGLang dal 6 luglio 2026. Non basta che un bug sia visibile in un issue tracker: serve che la sua gravità venga compresa e che il percorso di produzione lo renda irraggiungibile o sicuro.
Correggere il parametro non basta, se resta sbagliato il contratto
Cloudflare ha riallineato la capacità effettiva della cache, portando il pool di prefill a circa 2,04 milioni di token, superiore alla finestra configurata. Ha anche corretto il motore affinché le richieste già destinate al rifiuto vengano fermate prima di allocazione e trasferimento, senza far arrivare al decode un falso segnale di completamento. Il modello è stato rimesso a disposizione il 9 settembre dopo i test di riproduzione e verifiche con marcatori canary.
La parte importante è proprio la doppia correzione. Aumentare il pool impedisce oggi di raggiungere quel percorso con una richiesta valida. Ma una nuova quantizzazione, un cambiamento nel parallelismo, un modello più grande o una diversa distribuzione della memoria GPU potrebbero riproporre il disallineamento. Correggere soltanto la configurazione significherebbe affidare la sicurezza al fatto che nessuno modifichi mai più un parametro nel modo sbagliato. Non è una strategia: è una speranza con un file YAML.
Le correzioni upstream di SGLang hanno rafforzato anche la gestione degli abort e il rilascio differito delle pagine KV ancora coinvolte in trasferimenti asincroni. Il team raccomanda di aggiornare i deployment disaggregati a versioni che includano tali protezioni; la disclosure del 26 ottobre distingue le versioni e i backend Mooncake, NIXL e MORI, e Cloudflare suggerisce SGLang v0.5.22 o successiva. Un numero di versione da solo, naturalmente, non sostituisce la verifica della configurazione effettiva.
Il test giusto non è “risponde?”
Il punto operativo per chi costruisce o integra servizi AI è più interessante del nome del modello coinvolto. I test dovrebbero esercitare i confini, non soltanto il percorso felice. Se un endpoint dichiara un contesto massimo, occorre misurare la capacità realmente disponibile. Se una richiesta viene rifiutata durante il prefill, occorre verificare che non arrivi mai al decode come richiesta riuscita. Se un trasferimento asincrono viene interrotto, bisogna dimostrare che le pagine non siano riutilizzate mentre esistono scritture pendenti.
- Capacità e configurazione: confrontare a runtime la finestra di contesto con il pool KV effettivo su ogni nodo prefill e decode, non con un valore presunto dal file di configurazione.
- Test negativi: inviare richieste che superano intenzionalmente la capacità e verificare status, messaggi d’errore e assenza di trasferimenti successivi.
- Isolamento: usare richieste concorrenti con marcatori sintetici distinti e controllare che una risposta non contenga frammenti del contesto di un altro tenant.
- Lifetime asincrono: simulare abort, disconnessioni, timeout e fault durante i trasferimenti, verificando che le pagine siano rilasciate soltanto dopo il completamento sicuro.
- Osservabilità: registrare rejection, mismatch di capacità, checksum e rilascio differito senza trasformare i log in un nuovo contenitore di dati sensibili.
Qui c’è una differenza sottile rispetto a un normale controllo di uptime. Un monitor che riceve 200 può continuare a essere verde mentre il sistema restituisce informazioni che non dovrebbe conoscere. Non è una critica astratta ai codici HTTP: in un altro caso, parlando di HTTP trailers e gRPC, il problema era già distinguere l’esito del trasporto da quello dell’operazione. Qui si aggiunge una terza dimensione, più scomoda: la provenienza dei dati.
Multi-tenant non significa soltanto dividere il carico
L’isolamento nei sistemi condivisi viene spesso raccontato come una questione di quote, prestazioni e noisy neighbor. Sono aspetti reali: l’evoluzione dell’architettura multi-tenant di Habitat mostra bene quanto sia difficile scegliere il confine corretto fra efficienza e autonomia dei carichi. Ma c’è un livello più fondamentale: nessuna ottimizzazione può permettere che i dati di un cliente entrino nella risposta di un altro.
Secondo Cloudflare, nell’indagine sull’incidente non sono emerse evidenze di sfruttamento deliberato e nessun altro modello Workers AI è risultato coinvolto. È una conclusione del fornitore, da attribuire come tale. Non significa che ogni deployment SGLang fosse sicuro, né che tutte le varianti di rischio legate alla cache siano scomparse. Significa che l’incidente documentato ha un perimetro preciso e che la sua spiegazione tecnica è abbastanza dettagliata da essere utile anche fuori da quel perimetro.
Chi sviluppa software dovrebbe riconoscere una lezione familiare: la sicurezza non nasce da un componente chiamato “security”, ma dalla correttezza dei contratti fra componenti. Una coda che conferma troppo presto, una transazione considerata conclusa prima del commit, un lock liberato mentre qualcuno lo usa ancora: cambiano le tecnologie, non cambia la responsabilità. Nel mondo dell’AI, dove una risposta plausibile può nascondere il problema molto meglio di uno stack trace, questa responsabilità diventa ancora meno negoziabile.
Il vero progresso non è far girare modelli sempre più grandi in meno memoria. È riuscire a dimostrare, anche quando una richiesta fallisce, che la memoria riutilizzata appartiene soltanto a chi ha diritto di leggerla. Tutto il resto, benchmark compresi, viene dopo.
Domande frequenti
Che cos'è la KV cache in un modello AI?
È la memoria che conserva le rappresentazioni di chiavi e valori usate dall’attenzione durante la generazione. Riduce il lavoro ripetuto, ma in un servizio condiviso richiede una gestione rigorosa delle pagine assegnate alle diverse richieste.
Quanti utenti sono stati coinvolti nell'incidente GLM-5.3?
Cloudflare ha identificato 155 risposte che contenevano dati di richieste di altri clienti tra il 27 agosto e il 9 settembre 2026. Il dato riguarda le risposte, non il numero di utenti o clienti coinvolti.
Perché GLM-5.3 poteva leggere dati di altre richieste?
La finestra configurata era di 1.048.576 token, ma il pool KV del prefill ne conteneva 681.280. Un bug nel percorso di rifiuto di SGLang lasciava proseguire la richiesta verso il decode, che poteva leggere pagine cache non riscritte per quella richiesta.
Il bug ha colpito tutti i modelli su Cloudflare Workers AI?
No. Secondo l’indagine pubblicata da Cloudflare, l’incidente documentato riguardava GLM-5.3 e nessun altro modello Workers AI è risultato affetto da quel percorso specifico. La conclusione si riferisce all’ambiente analizzato dal fornitore.
Quali verifiche sono utili per un deployment SGLang multi-tenant?
Verificare la capacità KV reale rispetto alla finestra di contesto, aggiornare il motore con le correzioni di sicurezza, testare richieste rifiutate e abort asincroni, e controllare con marcatori sintetici che le risposte non mescolino dati di tenant diversi.
