Cloudflare GLM-5.3 泄露拆解:155 个响应混入他人 KV Cache

一句话结论:Cloudflare 在 Workers AI 上部署的 GLM-5.3 出现跨请求 KV Cache 泄露,13 天内 155 个响应混入了其他用户的数据,根因是预填充 KV 池 681280 token 装不下 1048576 token 的上下文窗口,叠加 SGLang 分离式预填充解码模式把被拒绝的请求当作正常请求继续执行。

2026 年 10 月 26 日,Cloudflare 联合 SGLang 维护方 RadixArk 与报告人 Gadi Evron 做了一次协调披露。这不是模型本身的安全缺陷,而是一次推理基础设施的租户隔离失效,暴露点在模型之下。Cloudflare 自己的判断是,Workers AI 上没有第二个模型受影响。

下面按披露报告的原始数字,把泄露是怎么发生的、20 小时里做了什么、以及自建推理的团队应该检查哪几项,逐条拆开。

事件轮廓:13 天暴露期,155 个响应

受影响的模型是 Cloudflare 托管在 Workers AI 上的智谱 GLM-5.3,模型标识为 zai-org/glm-5.3,上线日是 2026 年 8 月 27 日。暴露从上线当天就开始,直到 9 月 9 日模型转为私有才结束,前后 13 天。

确认受影响的响应总数是 155 个,全部对应长度超过 681280 token 的提示词。这个数字是排查后收敛的结果:Cloudflare 排除了自己和研究员的复现流量,剩下的请求在时间分布和请求量上都与正常的长上下文使用一致,团队没有找到蓄意利用的证据。

泄漏的内容形式很具体。返回给客户端的响应带着 HTTP 200 而不是错误码,提示词 token 计数与长提示词对得上,回复表面上读起来是连贯的,响应速度还比正常预填充 8 页应该有的更快。唯一的问题是,这段回复的一部分内容来自同一模型上更早的其他请求。

常规监控看不出这类问题。状态码正常、性能正常,只有输出内容本身携带了不该出现的东西。Cloudflare 在报告里承认,他们的长期上下文测试当时只检查了 HTTP 状态码、流式响应体里有没有错误帧以及推理性能,没有检查输出在长上下文下是否正确连贯。

项目 数值
受影响模型 GLM-5.3(Cloudflare Workers AI 托管)
暴露窗口 2026-08-27 至 2026-09-09,共 13 天
确认受影响响应 155 个
触发条件 提示词长度超过 681280 token
声明上下文窗口 1048576 token
修复后预填充 KV 池 约 204 万 token
其他模型受影响 无
蓄意利用证据 未发现

第一个根因:KV 池装不下声明的上下文

Cloudflare 把 GLM-5.3 部署成 1048576 token 的上下文窗口,但预填充 GPU 上的 KV Cache 池只放得下 681280 token。两者的差距不是配置疏忽那么轻,是团队在上线前就知道的事实。

预期是引擎会对装不下的提示词直接报错。长期上下文基准测试也确实返回了成功响应,于是团队判断引擎在优雅地处理这个差距。

问题在于 SGLang 的 KV 池大小不是一个显式配置项。引擎在启动时根据加载模型权重后剩下的 GPU 显存自动算出池容量,而对外声明的上下文长度是另一个独立配置。两者之间没有任何机制强制对齐。量化方式、并行度、CUDA Graph 大小、显存占用比例中的任何一项改动都会改变池容量,而服务声明的上下文长度不会跟着变。

这一项本身不会造成数据泄漏。它的作用是把合法请求推进 SGLang 的超长拒绝路径,而那条路径的逻辑本身是错的。

第二个根因:被拒绝的请求被当成还在跑

SGLang 的调度器遇到装不下的提示词时,走的是 Req.set_finish_with_abort 这条拒绝路径。这个函数做两件事,记录一个要返回给客户端的错误,然后把提示词替换成一个单 token 的占位符,让请求通过正常的批次清理流程走完。在单节点部署下这样是对的,占位符跑完,客户端拿到错误。

问题出在 Cloudflare 用的分离式预填充解码模式上。预填充和解码两侧的准入队列检查的是请求是否 finished,而不是请求是否被 rejected。被拒绝的错误被记在 to_finish 里而不是 finished_reason 里,于是 req.finished() 仍然返回 False。这个请求在整条流水线看来,还是一个活跃请求。

报告用了一个八页的例子讲清楚后果。路由器把原始提示词长度告诉解码侧,解码侧为这个请求预留 8 个 KV 页。预填充侧被错误地接纳,对那个单 token 占位符做了一次极简前向计算,然后只传输了 1 个页的 KV,并发出传输完成信号。解码侧接受这次交接,开始生成时注意力却读了全部 8 个预留页。只有 P0 是本次请求真正写入的,P1 到 P7 保留着更早请求的提示词和部分生成内容,模型把这团混合当成当前上下文继续往下生成。

所以客户端拿到的是 HTTP 200 而不是 HTTP 400,回复读起来连贯,速度还偏快。整个流程里没有一个环节报过错。

环节 正常预期 实际发生
提示词长度 装入上下文窗口 超过 KV 池容量,被调度器标记拒绝
占位符 中止并返回客户端错误 被替换为 1 个 token 的占位符继续执行
预填充产出 覆盖全部预留页 只生成并传输 1 页
解码读取 只读本次写入的页 读取全部 8 个预留页,含 7 个陈旧页
客户端状态码 HTTP 400 HTTP 200

20 小时的处置时间线

所有时间都是协调披露报告里的 UTC 时间。这段处置流程本身值得逐格看,它决定了一个漏洞在真实生产环境里的止损速度。

时间 动作
08-27 GLM-5.3 在 Workers AI 上线,预填充 KV 池小于上下文窗口,暴露从此刻存在
09-08 23:11 通过 HackerOne 收到 Gadi Evron 提交的报告
09-09 08:00 开始验证报告
09-09 13:22 宣布安全事件,产品安全团队用两个独立账号在 25 分钟内复现泄露
09-09 14:00 GLM-5.3 转为私有,请求返回 HTTP 400,状态页同步更新
09-09 14:35 根因定位为预填充池小于上下文长度,池更大的金丝雀节点确认不受影响
09-09 14:52 全部预填充节点切换到修正配置
09-09 19:04 用修正配置加引擎补丁重建完整部署,并用研究员的复现脚本验证
09-09 19:40 模型恢复公开
09-10 23:30 与 SGLang 维护方会面,敲定上游修复方案
09-11 至 09-19 额外加固措施推送到全部生产 SGLang 部署
09-16 至 10-12 上游合并准入修复、逐传输校验和、终止确认保留,并默认开启延迟 KV 释放
10-02 SGLang v0.5.21 发布
10-19 SGLang v0.5.22 发布
10-26 与研究员和 RadixArk 联合发布协调披露

从收到报告到恢复公开一共 20 小时 29 分,其中宣布事件到恢复公开 6 小时 18 分,复现只用 25 分钟,下线用了 38 分钟,根因定位 73 分钟,全量配置修正 90 分钟。

修复的两层:把池放大,把拒绝路径改安全

第一层是配置修复。Cloudflare 把 GLM-5.3 的配置调到与 GLM-5.2 一致,释放出的显存让预填充 KV 池能装约 204 万 token,大约是 1048576 token 上下文长度的两倍。合法请求只要装得进上下文窗口,就一定装得进 KV 池,超长拒绝路径对合法请求不再可达。仅这一项就能阻止漏洞被触发。

第二层是引擎修复,因为只放大池子不牢靠。量化、并行度、CUDA Graph 大小、未来的模型版本都会改变池容量,预填充调度器也可能因为别的原因拒绝请求。所以补丁让携带待定拒绝状态的请求在分离式准入队列处直接退出,带着原始错误返回,而且必须发生在 bootstrap、内存分配和 KV 传输三步之前。Cloudflare 的上游提案编号是 38704,同时要求解码侧校验实际传输的 token 数与预留数是否一致。

恢复公开之前,团队用两套东西验证:研究员提供的复现脚本,以及 Cloudflare 自建的金丝雀标记测试。另外 GLM-5.3 是唯一受影响的模型,因为 Workers AI 上其它分离式部署的预填充和解码 KV 池都大于各自的上下文长度,请求根本够不到拒绝路径。引擎修复与加固措施后来被推到 Cloudflare 用 SGLang 提供的每一个模型上,新部署必须通过相应检查项。

SGLang 版本与相关补丁

这个缺陷早于本次事件就存在。2026 年 7 月 6 日另一位用户把它作为 issue 30233 报给了 SGLang。Cloudflare 自 v0.5.10 起就在向维护方反馈同类问题,这次安全事件加速了原本就在推进的合作。

下面这张表是梳理给运维团队看的版本对照,最低目标版本是 v0.5.22。

版本 关键补丁
v0.5.20 36988 在被中止的预填充结果到达解码侧之前将其退出;首次阻断本事件
v0.5.21 38935 在准入阶段更早拒绝请求;39500 增加逐传输校验和开关;30899 由 Cloudflare 编写的页生命周期修复
v0.5.22 41128 增加延迟释放指标;43562 修复主机内存恢复失败时的并行一致性;Cloudflare 建议的最低版本

Cloudflare 还补充了四条修复措施。延迟 KV 释放扩展到持有预填充侧的源页面,并覆盖全部失败类型,而不只是中止场景。Mooncake 传输批次改为有界,避免单次超时留下无界的在途写入。负载均衡器的断连行为改成允许请求至少跑到产出第一个 token,减少不必要的跨节点中止。以及新增部署检查项,每次改动分离式部署都要核对池大小与上下文长度、两个角色的延迟释放状态、传输批次上限与释放超时的关系、以及补丁是否齐备。

长期方案:让读取本身成为一道校验

这类问题的通用定义是,一个页面被释放、复用或读取时,还有别的东西持有它。Cloudflare 区分了两种常见成因。同一节点内的页生命周期竞争,decode 节点上 token 生成、主机内存卸载、KV 传输接收端、中止与错误路径可能同时持有同一个页面,其中一个把它释放了,新请求就会拿到一个仍被读写中的页面。跨节点竞争则发生在预填充还在用 RDMA 写 KV 时请求被中止,已经发出的写入无法召回,延迟到达的写入会落进下一个请求的缓存里,产出损坏的输出或者串号的数据,而且不产生任何错误。

本次事件与上述竞争不同。被拒绝的请求每次都必然走错交接,不是概率问题,但结果一样,解码侧读到了当前请求从未写过的页面。

Cloudflare 的长期方向是页面级标签。给每个物理 KV 页打一个标签,页面每次被重新分配时标签就变,每个请求记录它期望的标签,解码侧在读取缓存前先校验页面与请求的归属关系,校验不过就让当前请求失败,而不是返回别人的数据。这个原型在 GLM 5.2 上测试,吞吐和尾部延迟的额外开销都低于 1%,但因为可靠性问题在测试后不久被停用。它绑定特定注意力后端和模型架构,还不能作为通用修复。

另一条路线是形式化验证,把 RDMA 的 KV 传输协议建模型并证明其安全性。报告把这次事件归类为分布式系统里的一致性失效,两个节点对系统状态持有不一致的视图。Cloudflare 计划用 Mooncake 对生产中的 SGLang 分布式 KV 缓存系统建模,追踪并修掉任何偏离目标安全属性的地方。

自建推理团队该检查什么

把这次事故翻译成运维动作,Cloudflare 给出了五条建议,其中每一条都可以立刻用在自己的部署上。

第一条,在每一个预填充和解码节点上分别测量 KV 池容量,并和上下文长度比较。每次改动权重、量化、并行度或显存占用比例之后重新测。池更小就把上下文长度降下来,不要依赖代理层强制限制,也不要假设引擎一定会拒绝装不下的请求。

第二条,升级到包含 36988 与 38935 的版本,也就是 v0.5.22 或更高,让超长请求无论池容量如何都会在交接前被拒绝。

第三条,在预填充和解码两个角色上都启用延迟 KV 释放,同时限制 Mooncake 的传输批次大小。

第四条,模型支持时打开损坏检测,也就是 –disaggregation-enable-kv-checksum 与 –kv-canary。这两个开关尚未覆盖所有模型架构,要去日志里确认请求确实在被校验。

第五条,不要让代理制造不必要的中止。负载均衡器应该允许请求至少运行到产出第一个 token,否则会触发需要跨节点协调的中止。

这套检查清单对已经在跑自建推理的团队价值更直接。站内此前记录过 AKV 缓存工程如何压低智能体成本,那篇讲的是怎么把缓存用得更省,这次事故讲的是缓存用得太省时会漏掉什么。而 EdgeRock-4B 的固定 KV 方案和 DeepSeek V4.1 Flash 的 KV Cache 压缩,都是在页的粒度上动手,正是这类问题最可能出现的层面。至于 GLM-5.3 这个模型本身,站内另有 它上线 Amazon Bedrock 的商务分成那篇。

小结

这次事故的价值在于它把一个抽象的租户隔离问题变成了一组可测量的数字。上下文窗口 1048576 token 与预填充池 681280 token 的差距是第一个变量,单 token 占位符和 finished 语义的错位是第二个变量,8 个预留页里只有 1 页被写入是放大器。三者叠在一起,155 个响应带上了别人的内容,而客户端收到的始终是 HTTP 200。

配置层的修复两天内就完成了,引擎层的补丁从 10 月 2 日的 v0.5.21 到 10 月 19 日的 v0.5.22 陆续落地。剩下的难题是页面级标签与形式化验证,这两条路都还停在原型阶段。

对绝大多数团队,最低成本的动作是把 SGLang 升到 v0.5.22,并在每个节点上量一次 KV 池和上下文长度的差值。

常见问题

这次泄露的是模型权重还是用户数据?

是用户数据。暴露的是同一模型上更早请求的提示词内容和部分生成结果,出现在更长请求的响应里。模型权重本身没有泄露,155 个响应全部对应长度超过 681280 token 的提示词,短请求不受影响。

升级到 v0.5.22 就一定安全了吗?

只能消除这条具体的触发路径。v0.5.22 让超长请求在交接前被拒绝,同时带来逐传输校验和与延迟释放的改进。但页生命周期竞争和跨节点竞争这两类问题,报告明确说仍在加固中,页面级标签原型因为可靠性问题还没上线。池容量与上下文长度的比对依然要自己做。

只有用了分离式预填充解码的部署才会中招吗?

这次的组合条件确实需要分离式模式加上池小于上下文长度。单节点部署的拒绝路径是正常的。但 Cloudflare 说 Workers AI 上其它分离式部署只是因为池更大才没出事,这说明池容量与上下文长度的关系是通用检查项,不是分离式部署的专属问题。

相关阅读