Executive Summary
Cloudflare has confirmed that 155 responses from the GLM 5.3 model on Workers AI carried text from other customers’ requests between 27 August and 9 September. Two faults stacked. The prefill KV cache pool held 681,280 tokens while the model was advertised at 1,048,576, and SGLang admitted a request its own scheduler had already rejected. No other hosted model was affected and Cloudflare found no evidence of deliberate exploitation. The fixes shipped in SGLang v0.5.20, v0.5.21 and v0.5.22.
The mechanism is the part worth keeping. SGLang derives the pool size from whatever GPU memory remains after the weights load, so it is not a value anyone sets. It moves when quantization, tensor parallel width or CUDA graph sizing changes. Every operator running disaggregated serving on SGLang carries that exposure, and most long-context tests check status codes rather than whether the answer made sense. Patch the engine, then check the pool against the window from the running process.
On 9 September Cloudflare declared a security incident on a model that had been live for two weeks. A researcher reported it the night before. Product Security rebuilt it from two accounts in 25 minutes, and Cloudflare had the model offline within 40 minutes of declaring the incident.
The evidence was an answer that could not have come from the prompt. A customer sent a very long request to GLM 5.3 on Workers AI and got back a clean HTTP 200, a coherent reply, and text that belonged to somebody else’s session.
The pool size is derived, not assigned
SGLang keeps its KV cache in a pool of fixed-size pages carved from GPU memory at startup. The pool is whatever is left after the weights load. The context window is a separate number held somewhere else, and nothing inside the engine forces the two to agree.
On the GLM 5.3 prefill nodes the pool came to 681,280 tokens. The model shipped with a 1,048,576 token window, so the pool covered 65 percent of the advertised context. Cloudflare’s incident report says the size is printed in the startup log and exposed as a metric, and the team knew about the mismatch.
They expected an error for any prompt that did not fit. The benchmark returned 200 responses with no error frames, so they concluded the engine was coping. The test never checked whether the words made sense.

A rejected request still counted as alive
The second fault sat in the engine. When a prompt did not fit, the scheduler rejected it and swapped in a one-token placeholder so it could drain through the batch machinery. The rejection was recorded in a field called to_finish. The admission queues asked whether a request was finished, and a rejected request answered no.
So prefill ran a trivial forward pass on the placeholder, pushed one page of cache, and told decode the transfer was complete. Decode had reserved eight pages and read all eight. One page came from this request. The other seven still held what earlier customers left in them.
On a single node the placeholder ran and the caller got the error it should have. Splitting prefill from decode broke the assumption that a rejection still reaches whoever sent the prompt. Cloudflare says the bug was filed as issue 30233 on 6 July, months before they hit it.
The prompts that leaked carry source code
Every affected request ran longer than 681,280 tokens. That is not a chat window. It is a repository, its test output and a long agent transcript. GLM 5.3 is built for agentic coding, so the window sat where the most sensitive material inside a company travels.
Cloudflare says the timing and volume looked like ordinary long-context use, not someone working the bug. That is a relief and a warning in one sentence. Nobody had to attack this.
The configuration fix gave the pool room. It now holds about 2.04 million tokens, twice the context length, so a valid request never reaches the rejection path. The engine fix retires a rejected request at the admission queue before any cache transfer and makes decode check the tokens it received against the number it reserved. That work is upstream as pull request 38704. Cloudflare recommends v0.5.22 or later.
The incident should bother an operator less than the arithmetic does. Cloudflare calls it a KV cache exposure, and the cause is a number that drifts. Because the pool is carved from leftover memory, tuning quantization, tensor parallel width or CUDA graph sizes quietly resizes it. A model upgrade that nothing else notices can put the pool back under the window. The rejection path is patched. The arithmetic is still yours to check.
Cloudflare moves that cache over RDMA through Mooncake because that is how modern serving buys its throughput. Every operator who follows them there inherits the same ownership question.
Three questions for your own team. What is the KV pool size on your prefill nodes, read from the running process? Does your long-context test check the answer or only the status code? And when a request is rejected, who tells the caller?
Related reading. Cloudflare’s sandboxes read the last tenant’s disk covers another isolation failure there. Disaggregated inference added a latency tax explains why the split exists.
Get the next one before it is old news
Independent analysis of cloud-native infrastructure, Kubernetes and data center economics. No vendor spin.
