Cloudflare Workers AI 中的跨请求 KV 缓存泄露:GLM-5.3、SGLang 以及 v0.5.22 中的修复
在 2026 年 8 月 27 日至 9 月 9 日期间,少数使用 GLM-5.3 Workers AI 模型 (@cf/zai-org/glm-5.3) 的客户收到的响应中包含了其他客户对同一模型请求的数据。其原因是两个重叠的问题:Cloudflare 的配置不匹配(模型宣称的 1,048,576 token 上下文窗口与仅能容纳 681,280 token 的预填充 KV 缓存池),以及一个 SGLang 漏洞,该漏洞导致超长请求在解耦的预填充到解码(prefill-to-decode)切换过程中被放行,而不是被拒绝。总计有 155 条响应受到影响,所有受影响的提示词长度均超过了 681,280 token。Cloudflare 于 2026 年 9 月 9 日修复了该问题并恢复了模型,并确认 Workers AI 上托管的其他模型均未受到影响。
事件经过
Cloudflare 于 2026 年 9 月 8 日收到 Knostic 的安全研究员 Gadi Evron 的警报,他通过 Cloudflare 的 HackerOne 项目报告称,发送到 Workers AI 上 @cf/zai-org/glm-5.3 的超长提示词有时会返回属于其他用户会话的文本。Cloudflare 在次日上午重现了该问题,宣布发生安全事件,并在宣布后 40 分钟内将该模型下线。随后,公司部署了修正后的配置和打过补丁的推理引擎,并于当天恢复了服务。
Cloudflare 表示,在排除其自身的测试和研究人员的测试后,受影响请求的时间和数量与普通的超长上下文使用情况一致,而非重复利用,且未发现任何蓄意利用的证据。
GLM 是 Z.ai 开发的一种用于代理编码工作流的文本生成模型,也是 Workers AI 提供的模型之一。该泄露问题自 2026 年 8 月 27 日该模型在 Workers AI 上线时便已存在。
问题一:KV 池小于上下文窗口
Cloudflare 在部署 GLM 5.3 时配置了 1,048,576 token 的上下文窗口,但其预填充 GPU 上的 KV 缓存池仅有 681,280 token 的空间。Cloudflare 在发布前就意识到了这种不匹配。
SGLang 在启动时会根据加载模型权重后剩余的 GPU 内存来调整 KV 缓存池的大小。池大小不是一个配置值,而模型宣称的上下文长度是另一个独立的配置值。没有任何机制强制两者保持一致,Cloudflare 的文章指出,检查这种一致性是运维人员的工作。所选的 KV 池大小由 SGLang 在启动日志中打印,并作为指标公开。
Cloudflare 的发布前测试在基准测试期间观察到了成功的 HTTP 状态码和没有错误帧的流式响应体,因此公司认为引擎能够妥善处理 KV 缓存池与请求上下文窗口之间的差异。文章将此确定为一个关键的测试漏洞:Cloudflare 验证了长上下文下的 HTTP 响应码和引擎性能,但没有验证模型输出和连贯性。
问题二:解耦模式下的 SGLang 拒绝路径
在解耦的预填充/解码模式下,超过 KV 池容量的提示词在内部被标记为拒绝并替换为单 token 占位符,但请求并未中止。相反,预填充节点为占位符提示词生成了 KV 缓存并将其传输到解码节点。解码节点收到“传输完成”消息,并假设预填充节点已用该请求的完整提示词覆盖了之前请求的 KV 缓存,而实际上大部分之前的状态保持完好。随后,解码节点在读取仍保留有早期请求数据的陈旧缓存页面的同时生成了响应。
在单节点部署中,拒绝路径工作正常:占位符运行,批处理完成,客户端收到错误。在解耦模式下,预填充和解码节点的准入队列检查的是请求是否“完成”,而不是是否“被拒绝”。被拒绝的请求将其拒绝状态存储在 to_finish 中,而不是 finished_reason 中,因此 finished() 仍然返回 False,请求报告自身处于活跃状态。
Cloudflare 描述的序列如下:
- 引导。 路由器告知解码节点提示词长度(Cloudflare 图示中为 8 页),解码节点预留了 8 个 KV 页面。
- 预填充准入。 预填充准入队列准入了任何
req.finished()为 False 的请求,因此被拒绝的请求被准入。 - 预填充前向传递。 拒绝机制已将提示词替换为单 token 占位符,因此预填充节点对该 token 运行了简单的向前传递。
- KV 传输。 预填充节点向解码节点发送了一页 KV 并发出传输完成信号。
- 解码准入。 解码节点的准入队列使用了相同的
finished()检查并接受了切换。 - 解码注意力。 解码节点开始生成,注意力机制读取了所有 8 个预留页面。只有页面 P0 是由当前请求写入的;页面 P1 到 P7 仍然保留着早期请求留下的内容。
- 响应。 客户端收到了 HTTP 200 而不是 HTTP 400。
prompt_tokens与长提示词匹配,回复连贯,但其到达速度比 8 页预填充应有的速度更快,且包含了来自其他用户 KV 页面的内容。
每个超过阈值的请求都表现出这种行为。该漏洞曾由另一位用户在 2026 年 7 月 6 日作为问题 #30233 报告给 SGLang,Cloudflare 在调查此事件时注意到了这一点。Cloudflare 表示自 v0.5.10 以来,它一直在向 SGLang 标记此类问题。
GLM 5.3 是唯一受影响的模型。Workers AI 上的所有其他解耦部署在预填充和解码节点上的 KV 池都大于其上下文长度,因此没有任何请求能够触及该拒绝路径。
补救措施:修复配置并修复引擎
Cloudflare 在恢复公共访问之前应用了两个独立的修复程序。
使池容量容纳完整上下文。 Cloudflare 调整了其 GLM 5.3 配置以匹配其 GLM 5.2 配置,释放了足够的 GPU 内存,使预填充 KV 池现在可容纳约 204 万个 token,大约是 1,048,576 token 上下文长度的两倍。现在,符合上下文窗口的提示词总是能放入池中,因此有效请求永远不会触及拒绝路径。
使拒绝路径安全。 Cloudflare 认为仅依赖池大小是脆弱的,因为量化、张量并行、CUDA 图大小或未来模型版本的更改会以容易被忽略的方式改变池大小,而且其他情况也可能让预填充调度程序拒绝请求并触发相同的错误。Cloudflare 对引擎进行了修补,使得带有待处理拒绝状态的请求在解耦准入队列处即被退回,并返回其原始错误,在任何引导、分配或 KV 传输发生之前。它将此作为 #38704 提交给上游,该补丁还要求解码节点验证传输的 token 数量是否与预留的数量匹配。
Cloudflare 在恢复模型之前,根据研究人员的重现脚本及其自身的金丝雀标记测试验证了这两项更改。
事件时间线
所有时间均为 UTC,由 Cloudflare 发布。
| 时间 | 事件 |
|---|---|
| 2026 年 8 月 27 日 | GLM 5.3 在 Workers AI 上发布,其预填充 KV 池小于其上下文窗口。泄露自发布时即存在。 |
| 2026 年 9 月 8 日 – 23:11 | 通过 HackerOne 收到报告。 |
| 2026 年 9 月 9 日 – 08:00 | Cloudflare 开始验证 HackerOne 报告。 |
| 2026 年 9 月 9 日 – 13:22 | 宣布安全事件。产品安全团队在 25 分钟内使用两个独立账户重现了泄露。 |
| 2026 年 9 月 9 日 – 14:00 | GLM 5.3 设置为私有。请求返回 HTTP 400。状态页面已更新。 |
| 2026 年 9 月 9 日 – 14:35 | 根本原因确定为预填充池小于上下文长度。金丝雀预填充节点(池更大)确认不受影响。 |
| 2026 年 9 月 9 日 – 14:52 | 所有预填充节点滚动更新至修正后的配置。 |
| 2026 年 9 月 9 日 – 19:04 | 使用修正后的配置加上 Cloudflare 编写的引擎补丁重建了完整部署。根据研究人员的重现脚本进行了验证。 |
| 2026 年 9 月 9 日 – 19:40 | 模型恢复公共访问。 |
| 2026 年 9 月 10 日 – 23:30 | 与 SGLang 维护者(RadixArk CTO 和核心工程师)会面,就上游修复达成一致。 |
| 2026 年 9 月 11–19 日 | 向所有生产环境 SGLang 部署推送了额外的加固措施。 |
| 2026 年 9 月 16–10 月 12 日 | 上游 SGLang 合并了准入修复、逐传输校验和、中止-ACK 保留,并将延迟 KV 释放设为默认开启。 |
| 2026 年 10 月 2 日 | SGLang v0.5.21 发布。 |
| 2026 年 10 月 19 日 | SGLang v0.5.22 发布。 |
| 2026 年 10 月 26 日 | 与研究人员和 RadixArk 协调发布。 |
上游 SGLang 修复
针对此问题的补丁已在 SGLang v0.5.20 中发布,并在 v0.5.21 和 v0.5.22 中进行了后续跟进。Cloudflare 建议所有运营商将 SGLang 依赖项更新至 v0.5.22 或更高版本。以下更改已合并至 SGLang main 分支;除一项外,其余均包含在 v0.5.22 中。
| 类别 | Pull request | 发布版本 | 功能描述 |
|---|---|---|---|
| 事件 | #36988 | v0.5.20 | 在结果到达解码节点之前退回已中止的预填充请求。这阻止了本次事件中的泄露。 |
| 事件 | #38935 | v0.5.21 | 在准入阶段拒绝这些请求,在任何引导、分配或 KV 传输之前。遵循 Cloudflare 草案补丁 #38704 中的方法。 |
| 竞态 | #39461, #41612 | v0.5.21, v0.5.22 | sgl-router 在客户端断开连接或预填充失败时取消请求的两半。 |
| 竞态 | #37454, #41023 | v0.5.20, v0.5.21 | 仅在支持的后端启用延迟 KV 释放,随后将其设为默认开启。 |
| 竞态 | #40645, #41402 | v0.5.21 | 修复了排水确认丢失的情况,以便在确认时进行释放,而不是等待超时。 |
| 竞态 | #41404 | v0.5.21 | 在每次传输失败时保留页面,而不仅仅是解码发起的终止。 |
| 竞态 | #41618 | v0.5.21 | 修复了中止通知路径中的回归问题。 |
| 竞态 | #43719 | v0.5.22 | 失败的 Mooncake 发送不再将其写入报告为已排水。 |
| 竞态 | #37077 | v0.5.22 | 按请求标记中止消息,以便重用的 ID 不会消耗陈旧的确认。添加了 MORI 支持。 |
| 竞态 | #36612 | v0.5.20 | 在 Mooncake、NIXL 和 MORI 之间共享预填充到解码的失败通知。 |
| 竞态 | #32758 | v0.5.20 | Mooncake 传输批处理大小的可选上限,防止单个超时调用留下无限数量的飞行写入。 |
| 生命周期 | #30899 | v0.5.21 | 解码节点在撤回请求并释放页面之前等待正在进行的主机内存卸载。由 Cloudflare 编写。 |
| 生命周期 | #43562 | v0.5.22 | 在主机内存缓存恢复失败时保持张量并行等级的一致性。 |
| 可观测性 | #39500 | v0.5.21 | 添加 --disaggregation-enable-kv-checksum:解码节点在生成前验证传输的 KV 校验和。清理工作在 #39729 中,已合并等待发布。 |
| 可观测性 | #38596, #40642 | v0.5.20, v0.5.21 | 修复 SGLang 的 KV 金丝雀,用于检测和记录潜在损坏。 |
| 可观测性 | #41128 | v0.5.22 | 延迟释放的指标:计数、持续时间,以及每次保留是因排水确认还是超时结束。 |
发布时尚在进行中的工作:#39729(--disaggregation-enable-kv-checksum 的清理,已合并等待发布)、释放时清零 KV 页面(草案提案 #46435)、将 KV 校验和验证从预填充到解码的切换扩展到每个解码步骤,以及更广泛的 KV 金丝雀和 KV 校验和模型及功能支持。
Cloudflare 部署的额外加固措施
Cloudflare 将此漏洞归类为它已研究数月的 KV 缓存内存安全漏洞家族之一。在每种情况下,页面在仍有其他方对其有要求时被释放、重用或读取。Cloudflare 确定了两个常见原因。
第一个是单节点内的页面生命周期。解码节点上的多个任务可以同时持有同一个页面:token 生成、卸载到主机内存、KV 传输接收器以及中止和错误路径。如果一个任务在另一个任务仍在使用时释放了页面,则新请求可能会在页面仍被读取或写入时被分配该页面。早期针对解码卸载 (#24226, #30899) 和语法错误时的双重释放 (#30937) 的修复属于此类。
第二个是跨节点的竞态。当请求在预填充节点通过 RDMA 将 KV 缓存写入解码节点时被中止,已发送的写入无法撤回。如果解码节点立即释放这些页面,这些页面可能会被分配给另一个请求,而延迟的写入会落入该请求的 KV 缓存中。Cloudflare 为此竞态贡献了建议的修复 (#31357, #38961) 和重现脚本 (#32564)。Cloudflare 指出,本次事件中的漏洞有所不同:被拒绝的请求每次都通过了切换,而不是因为时序意外,但其结果与解码节点读取请求从未写入的页面相同。
事件发生后,Cloudflare 采取了以下措施:
- 将延迟释放扩展到同时保留预填充节点的源页面,并覆盖所有失败类型,而不仅仅是中止。作为 #38961 提交给上游,后被 #40645、#41402 和 #41404 取代。
- 部署了有界的 Mooncake 传输批处理 (#32758,来自 SGLang 社区),因此没有任何单个传输调用可以超过释放超时。
- 更改了负载均衡器,使客户端断开连接不再在传输中途中止请求。它会保持预填充连接直到预填充完成,并保持解码连接直到第一个 token 生成,然后关闭两者。
- 在每次解耦部署更改中添加了检查:针对上下文长度测量的 KV 池大小、两个角色上的延迟释放、释放超时内的传输批处理边界,以及应用所有必需的补丁。
Cloudflare 对其他 SGLang 运营商的建议
Cloudflare 对任何在解耦预填充/解码模式下运行 SGLang 的人的建议是:
- 测量您的 KV 池并将其与上下文长度进行比较,在每个预填充和解码节点上,从引擎的启动日志中,在每次更改权重、量化、并行度或内存比例后进行。如果池较小,请降低上下文长度以适应。不要依赖代理来强制执行限制,也不要假设引擎会拒绝不符合要求的请求。
- 升级到包含 #36988 和 #38935 (v0.5.22 或更高版本) 的版本,以便超长请求在切换前被拒绝,无论池大小如何。
- 在两个角色上运行延迟 KV 释放(#41023 后为默认),并限制 Mooncake 传输批处理,以便单个超时调用不会留下无限的写入尾部。
- 在模型支持的情况下开启损坏检测。
--disaggregation-enable-kv-checksum和--kv-canary尚未覆盖所有模型架构。检查日志以确认请求正在被验证。Cloudflare 正在与 SGLang 团队合作,将这两项检查扩展到更多架构,包括 GLM 5.3、GLM 5.3 Flash 和 DeepSeek V4。 - 确保您的代理不会生成不必要的中止。 配置负载均衡器以允许请求继续进行直到第一个 token,这样它就不会触发需要跨节点协调的中止。
长期展望:构造完整性与形式化验证
Cloudflare 认为,配置检查和生命周期修复减少了页面被误用的方式,但并未使误用变得不可能。在其框架中,持久的答案是让引擎在读取时检测到请求即将读取其不拥有的页面,并使该单个请求失败,而不是返回其他人的数据。
Cloudflare 在夏天构建了一个原型,并在 2026 年 8 月进行了描述。每个物理 KV 页面都带有一个在页面重新分配时更改的标签,每个请求记录其期望的标签,并在解码读取缓存之前检查映射。在 Cloudflare 的测试中,它在吞吐量和尾部延迟方面的成本不到 1%。Cloudflare 在 8 月中旬在 GLM 5.2 上进行了测试,但由于可靠性问题不久后将其禁用。该原型还绑定到特定的注意力后端和模型架构,因此无法成为通用修复。Cloudflare 表示正在与 SGLang 团队合作,将生产版本构建到引擎中,使其适用于所有模型。#39500 中的逐传输校验和是第一步,因为它验证了从预填充到解码的切换;页面级标签更进一步,因为它们检查每次读取,包括传输后很久的读取。
Cloudflare 还将该漏洞描述为分布式系统中的一致性失败:RDMA 直接将数据从一个节点的内存复制到另一个节点,两台机器必须就数据去向、大小以及何时完成达成一致。Cloudflare 指向其现有的形式化验证工作——用于权威 DNS 中无冲突 IP 选择的 Topaz,以及 Meerkat 的 QuePaxa 实现模型(展示了后来修复的一致性漏洞)——并表示现在正将该经验应用于 KV 缓存传输引擎。既定计划是对基于 RDMA 的 KV 缓存传输协议进行建模和安全性证明,使用 Mooncake 对其生产环境 SGLang 分布式 KV 缓存系统进行建模,并跟踪和修复与它想要保证的安全属性的任何偏差。
Hacker News 讨论补充了什么
Hacker News 帖子中最常被重复的批评是标题将漏洞过度归因于模型。评论者 jacquesm 写道:
这与 GLM-5.3 真的没有任何关系。当你在用户之间共享模型且没有做好簿记工作时,就会发生这种情况,实际的模型并不重要。它表明用户会话之间没有足够的隔离,并且当内存永远不应该重叠时,内存可能会重叠。这在很大程度上是 SGLANG 和 Cloudflare 的错误,而不是 Z.AI 模型的错误,我不明白他们能对此做什么。Cloudflare 应该承担责任,而不是在标题中点名其他(我认为是无辜的)方。
armcat 提出了另一种表述:“共享 LLM 推理中的跨租户 KV 缓存泄露:分析与补救”,并补充说作为 GLM-5.3 用户,他们最初认为有理由担心,“结果发现根本没有”。sauravadhk 和 adg001 也提出了类似的建议,要求从标题中删除模型名称。
几位评论者关注 Cloudflare 自己披露的测试漏洞。ComputerGuru 写道:
“我们预期会出错但得到了成功的响应”难道不值得调查而不是直接跳过吗?
segmondy 提出了相关问题,即 Cloudflare 是否在仅有 681,280 token 空间的情况下宣称 1,048,576 token 的上下文窗口。
其他人称赞了披露本身。kami23 写道,他们“比大多数人更喜欢”根本原因分析,引用了责任承担、对测试为何未捕获漏洞的清晰解释,以及 Cloudflare 禁用模型服务的速度。
该帖子还将问题推广到本次事件之外。0xbadcafebee 指出,共享租户意味着 token 无法被神奇地隔离,并且“拥有高度敏感数据的企业确实应该使用非共享主机”。totetsu 询问是否有人提供单租户推理即服务。nitishr 写道,他们“惊讶于更多的无服务器提供商还没有发现(或公开披露)类似的问题”,而 thehamkercat 建议其他提供商“可能根本不知道/不在乎这种情况正在发生”。
两位评论者描述了他们认为在其他地方发生的类似跨租户泄露。xmcqdpt2 讲述了工作中 Claude 的一次响应,返回了“非常具体、不相关的信息,看起来非常像是来自公司其他人的信息”,并表示 Anthropic 告诉他们这是不可能的,AI 有时会犯错。reassess_blind 描述了发现某浏览器测试服务的 iOS 虚拟机在用户之间泄露剪贴板数据,包括另一个用户的凭据,并表示供应商拒绝了赏金。这些是评论者的未经证实的轶事,而非 Cloudflare 报告中的发现。
一位评论者 octoberfranklin 将此事件描述为一种情况,即“尽管 sglang 是用 Rust 编写的,但他们仍然因为未初始化内存的未定义行为而受到伤害”。Cloudflare 自己的报告对根本原因的描述不同:被拒绝的请求被准入,因为准入队列检查的是 finished() 而不是拒绝状态,因此解码节点读取了未被覆盖的陈旧 KV 页面。
协调披露
Cloudflare 将该报告作为协调披露的一部分发布,同时发布了来自 SGLang 团队的帖子。Cloudflare 感谢 Gadi Evron,他通过其 HackerOne 项目报告了该问题并提供了可重现的脚本,并感谢 RadixArk 的 SGLang 团队,Cloudflare 表示该团队将此事件视为自己的问题,并紧急合并了上游修复。Workers AI 中的安全问题可以在 hackerone.com/cloudflare 报告。
SUMMARY: 2026 年 8 月 27 日至 9 月 9 日期间,由于 KV 缓存池配置错误加上 SGLang 解耦预填充/解码漏洞,Cloudflare 的 GLM-5.3 Workers AI 模型产生的 155 条响应包含了其他客户的数据;Cloudflare 对模型进行了补救,上游 SGLang 在 v0.5.20 至 v0.5.22 版本中发布了修复程序。
TITLE: Cloudflare Workers AI 中的跨请求 KV 缓存泄露:GLM-5.3、SGLang 以及 v0.5.22 中的修复