GLM‑5.3 Workers AI 模型 KV 快取跨請求暴露事件與 SGLang 修復
Cross-request KV cache exposure in the GLM-5.3 Workers AI model, and our remediation with SGLang
Cloudflare 發布了 GLM‑5.3 Workers AI 模型 KV 快取跨請求暴露事件的詳細報告,並說明瞭 SGLang 修復措施與後續安全加固。
- 暴露範圍:155 答案受影響,僅限於 681,280 以上 token 的長提示。
此報告詳細說明瞭 GLM‑5.3 Workers AI 模型 KV 快取跨請求暴露的技術根源與修復流程,對安全工程師評估類似分散式推理系統風險具有參考價值。
2026年9月8日,我們收到來自Knostic的安全研究員Gadi Evron的通知,指出自2026年8月27日起影響GLM-5.3 Workers AI模型(@cf/zai-org/glm-5.3)的租戶隔離問題。我們已修復此問題,並於9月9日恢復該模型。已確認Workers AI上託管的其他模型未受到影響。
在8月27日至9月9日期間,少量使用GLM 5.3 Workers AI模型的客戶收到的回應包含了其他客戶在其請求之前不久對同一模型發出的請求中的資料。共受影響155則回應,皆針對長度超過681,280個 token 的提示。
排除我們自己的測試和研究員的測試後,我們識別出的請求在時間與量上與一般長上下文使用相符,並非有人反覆利用此漏洞。我們未發現任何蓄意濫用的證據。
今天,我們與我們使用的開源推理引擎 SGLang 的一篇貼文一起發布此報告,作為協調披露。該貼文說明瞭導致此問題的漏洞、我們的回應時間表,以及隨後在正式 SGLang 部署上進行的強化措施。我們亦詳述了與 SGLang 上游維護者持續合作,以解決此及類似漏洞的努力。
發生了什麼
GLM 是來自 Z.ai 的文字生成模型,專為代理式編碼工作流程設計;它是 Workers AI 上可用的眾多 models 之一。Gadi Evron 於9月8日透過我們的 HackerOne 程式提交報告,稱在 Workers AI 上向 @cf/zai-org/glm-5.3 傳送極長提示時,有時會返回屬於其他使用者會話的文字。Cloudflare 第二天早上重現了此問題,並在宣佈事故後 40 分鐘內將模型下線。我們隨後部署了修正的配置和已修補的推理引擎,並於同日恢復服務。
此關於跨請求 KV 快取暴露的具體報告,是兩個重疊問題的結果。第一個是 Cloudflare 使用的開源推理引擎 SGLang 的錯誤配置。我們以 1,048,576 token 的上下文視窗部署 GLM 5.3,但我們的 prefill GPU 上的 KV 快取池僅能容納 681,280 token。我們在啟動前已知此不匹配。預期引擎在無法處理任何提示時會返回錯誤,而我們的長上下文測試仍成功返回回應,因而未認為這是問題。
第二個問題是 SGLang 在分離式 prefill / decode 模式下處理超長請求的錯誤。未能適配池的提示會被內部標記為拒絕,並以一個 token 的佔位符取代;然而,prefill 並未中止請求並返回客戶端錯誤,而是為佔位符提示生成 KV 快取並轉移至 decode 節點。decode 節點收到「transfer complete」訊息後,假設 prefill 用此請求的完整提示覆蓋了先前請求的 KV 快取,實際上它只保留了大部分先前狀態。decode 節點隨後在仍保留早期請求資料的陳舊快取頁上產生回應。結果,長度超過 681,280 token 的提示回應可能包含其他使用者 GLM 5.3 會話的資料。
即時修復
在 停用模型 後,我們採取了以下措施來修正此漏洞,並在恢復公開存取前完成。
為修正設定,我們讓池子保留完整上下文。 我們將 GLM 5.3 的設定調整為與 GLM 5.2 相同,釋放足夠 GPU 記憶體,使預填 KV 池現在可容納約 2.04 百萬個 tokens,約為 1,048,576 tokens 上下文長度的兩倍。符合上下文視窗的 prompt 現在始終能放進池中,因此對於有效請求,拒絕路徑永不會被觸發。僅此即可防止該錯誤被觸發。
為修正引擎,我們也將拒絕路徑做了安全處理。 僅依賴池大小很脆弱:任何量化、張量並行、CUDA 圖尺寸或未來模型版本的變更都可能改變池大小,容易被忽略。此外,還可能存在其他情況,預填排程器會拒絕請求並觸發同樣的錯誤。為防止此情況,我們還修補了引擎,使攜帶待拒絕的請求在去聚合授權佇列時即被退休,保留其原始錯誤,且在任何引導、分配或 KV 傳輸發生前完成。我們已在上游提出此修正,參考 #38704,並讓解碼節點驗證傳輸的 tokens 數量與其預留數量相符。該修正已在 SGLang main 的 #36988 中,於 9 月 6 日合併處理相關失敗路徑,但尚未發布。維護者亦合併了 #38935,該修正根據我們的方法更早拒絕這些請求。
我們在恢復模型前,先對兩項更改進行了驗證,使用研究者的重現指令碼以及我們自己的金絲雀標記測試。
背景:我們如何提供 GLM 5.3
Workers AI 以 SGLang 在去聚合配置下執行 GLM 5.3:一組預填 GPU 處理 prompt 並為其產生模型的 KV 快取,然後透過 Mooncake 以遠端直接記憶體存取 (RDMA) 將該快取傳遞給一組解碼 GPU 生成回應。我們曾在關於 高效能 LLM 和 更小、更快、更安全的模型 的文章中討論過此架構。
GPU 記憶體中的 KV 快取是一個共享的固定大小頁面池。每個請求在被允許時分配頁面,完成時釋放。頁面目前不會在請求之間被清零;下一個獲取頁面的請求直接覆寫。此方法在推論引擎中很常見,且為達到最佳效能所必需,但也意味著推論引擎負責追蹤這些記憶體頁面的所有權。KV 快取頁面所有權與生命週期追蹤必須處理的不僅是節點內部的共享所有權(KV 快取解除安裝與 token 生成都以不同生命週期存取記憶體),還有與遠端預填節點技術上共享的所有權(該節點同時持有對該頁面的可變參照)。
此外,該池的大小並非設定值。SGLang 在啟動時根據載入模型權重後剩餘的 GPU 記憶體來設定其大小。模型宣稱的上下文長度則是另一個設定值。兩者並未被強制一致,我們的工作是確認它們是否相符。
問題一:設定錯誤
在我們的 GLM 5.3 prefill 節點使用我們的設定時,KV pool 佔用 681,280 個 tokens,而模型的 context 長度為 1,048,576 tokens。SGLang 在啟動時會在日誌中列印所選的 KV pool 大小,並以指標的形式公開。雖然我們知道這個尺寸不匹配,但在預發布測試期間,我們觀察到成功的 HTTP 狀態碼和無錯誤框架的串流內容。於是我們假設引擎能透過串流或其他機制平滑處理 KV cache pool 與請求的 context 視窗差異。
這是我們測試中的一個關鍵缺口:我們僅驗證了長 context 時的 HTTP 回應碼和引擎效能,並未驗證模型輸出與連貫性。現在我們直接從正在執行的引擎檢查 KV pool 大小與 context 長度是否匹配,在任何分散式部署變更釋出前,而不是從回應碼推斷。
第二個問題:SGLang bug
在 v0.5.21 中,SGLang 團隊已解決此 bug,修正了處理長於 KV pool 大小的 prompt 的方式。以下是之前的情況。
當收到的 prompt 長度超過 KV pool 可容納時,SGLang 的排程器會拒絕它。拒絕路徑 (Req.set_finish_with_abort) 會記錄要回傳給客戶端的錯誤,並將 prompt 交換成一個單 token 的佔位符,讓請求能透過正常的批次機制處理,而不分配實際記憶體。在單節點部署中這樣運作:佔位符執行,批次完成,客戶端收到錯誤。
在我們運作的分散式模式下,prefill 與 decode 的接納佇列都會檢查請求是否 finished,而非是否 rejected。被拒絕的請求會報告自己為活躍。於是:
- Bootstrap. 路由器告訴 decode 節點 prompt 長度(圖中為 8 個頁面)。Decode 為此請求保留了 8 個 KV 頁面。
- Prefill admission. Prefill 接納佇列會接納任何
req.finished()為 False 的請求。被拒絕的請求將其拒絕訊息存於to_finish而非finished_reason,因此finished()仍回傳 False。被拒絕的請求被接納。 - Prefill forward pass. 拒絕已將 prompt 取代為單 token 佔位符。Prefill 對那個 token 執行了簡單的前向傳播。
- KV transfer. Prefill 將一頁 KV 傳送至 decode,並標示傳輸已完成。
- Decode admission. Decode 的接納佇列也執行相同的
finished()檢查,因此接受了交接。 - Decode attention. Decode 開始生成,注意力讀取了所有 8 個保留頁面。只有頁面 P0 被此請求寫入。頁面 P1 至 P7 仍保留先前請求留下的內容:其他使用者的 prompt 及部分生成。
- Response. 客戶端收到 HTTP 200 而非 HTTP 400。
prompt_tokens匹配長 prompt,回覆保持連貫。其到達速度比 8 頁 prefill 所需時間還快,且包含其他使用者 KV 頁面的內容。
所有超過門檻的請求都會這樣行為。這個錯誤已由另一位使用者於 2026 年 7 月 6 日以 issue #30233 向 SGLang 報告,Cloudflare 在調查此事件時注意到。自 v0.5.10 起,我們已開始在 SGLang 上標記此類問題,這次安全事件加速了 Cloudflare 已與 SGLang 團隊合作開始的工作。此問題的修補已於 SGLang v0.5.20 發布,隨後在 v0.5.21 與 v0.5.22 進行了後續更新,並已提供給所有依賴 SGLang 的其他運營者使用。我們強烈建議所有運營者將 SGLang 依賴更新至 v0.5.22 或更高版本。欲瞭解更多關於特定 PR 的細節,請繼續閱讀。
GLM 5.3 是唯一受此錯誤影響的模型。Workers AI 上的其他所有分散部署在預填充與解碼階段的 KV 池都大於其上下文長度,因此沒有任何請求能進入拒絕路徑。我們已將以下所述的引擎修復和強化措施套用於所有使用 SGLang 的模型,並且我們的部署檢查要求每一次新部署都必須包含這些措施。
時間線
所有時間皆為 UTC。
時間 | 事件 |
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 | 根本原因確認為預填充池小於上下文長度。使用較大池的 Canary 預填充節點確認不易受影響。 |
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 合併了入門修復、每次傳輸校驗碼、abort-ACK 保留,並預設啟用延遲 KV 釋放。 |
2026 年 10 月 2 日 | SGLang v0.5.21 發布。 |
2026 年 10 月 19 日 | SGLang v0.5.22 發布。 |
2026 年 10 月 26 日 | 與研究人員及 RadixArk 協調發布。 |
持續強化工作
此錯誤屬於 Cloudflare 已經持續數月努力解決的 KV 快取記憶體安全性錯誤型別。每種情況下,某個頁面在被釋放、重複使用或讀取時,仍有其他程式佔有其許可權。我們觀察到兩個常見原因。
第一個是單一節點內的頁面生命週期。解碼節點上的幾個任務可能同時持有相同頁面:令牌生成、向主機記憶體解除安裝、KV 傳輸接收器,以及中止與錯誤路徑。如果其中一個在另一個仍在使用時釋放頁面,新的請求可能會在頁面仍被讀寫時就被分配到該頁面。我們之前對解碼解除安裝的修復(#24226, #30899)以及語法錯誤時的雙重釋放(#30937)屬於此類。
第二個是跨節點的競態。當請求在 prefill 仍在透過 RDMA 將其 KV 快取寫入解碼時被中止,已經傳送的寫入無法被撤銷。如果解碼立即釋放那些頁面,這些頁面可能會被分配給另一個請求,而遲到的寫入則落在那個請求的 KV 快取中。Cloudflare 透過建議的修復(#31357, #38961)和重現指令碼(#32564)為修正此競態做出了貢獻。在兩種情況下,結果都是輸出損毀,或一位使用者的資料出現在另一位使用者的回應中,且沒有產生錯誤。此次事件中的錯誤不同:被拒絕的請求每次都會經過交接,而非因時間失誤。但結果相同:解碼讀取了該請求從未寫入的頁面。上述情況未涵蓋此次事件中 prefill 拒絕請求、解碼未得知此事的失敗。
自事件發生以來,我們已經:
- 將延遲釋放擴充套件至保留 prefill 的來源頁面,同時覆蓋所有失敗型別,而不僅限於中止。我們以 #38961 為提案上游,後來被 #40645, #41402 以及 #41404 取代。
- 部署了受限的 Mooncake 傳輸批次(#32758,來自 SGLang 社群),因此單一傳輸呼叫不會超過釋放超時。
- 調整了負載平衡器,使客戶端斷線不再在傳輸途中中止請求。它會在 prefill 完成前保持 prefill 連線,並在解碼收到第一個 token 前保持解碼連線,然後同時關閉兩個連線。
- 在每一次分散式部署變更中加入檢查:KV 池大小對照上下文長度、兩個角色皆使用延遲釋放、傳輸批次邊界落在釋放超時內,以及已套用所有必要的修補程式。
上述所有變更已釋出至我們的分散式生產部署。
與 SGLang 團隊合作
我們於事件發生後第二天,即 9 月 10 日,在 RadixArk 與 SGLang 維護者會面。我們同意 RadixArk 將調查我們報告的競態條件,上游整合完整性檢查概念,並為此類錯誤建立重現測試。
以下已經在 SGLang main 上落地,以解決此事件及相關的 KV 快取競態條件,除了一個之外,全部包含於 v0.5.22。執行分散式 SGLang 的使用者應更新至 v0.5.22 或根據需要回移這些修補程式。
類別 | 拉取請求 | 發布 | 功能說明 |
事件 | v0.5.20 | 在結果到達解碼前終止被中止的 prefill 請求。此舉可阻止本次事件中的暴露。 | |
事件 | v0.5.21 | 在入門時拒絕這些請求,早於任何啟動、分配或 KV 傳輸。採用 Cloudflare 草案修補程式 #38704 的方法。 | |
競態 | v0.5.21, v0.5.22 |
| |
Race | v0.5.20, v0.5.21 | 僅在支援的後端啟用延遲 KV 釋放,並將其設為預設開啟。 | |
Race | v0.5.21 | 修正了排空確認失效的情況,確保釋放在確認後執行,而非等待超時。 | |
Race | v0.5.21 | 在每次傳輸失敗時保持頁面,而不僅限於解碼啟動的中止。 | |
Race | v0.5.21 | 修正了中止通知路徑中的回歸。 | |
Race | v0.5.22 | 失敗的 Mooncake 傳送不再將其寫入報告為已排空。 | |
Race | v0.5.22 | 為每個請求標記中止訊息,避免重用 ID 消耗過期確認。新增 MORI 支援。 | |
Race | v0.5.20 | 在 Mooncake、NIXL 與 MORI 之間共享預填至解碼失敗通知。 | |
Race | v0.5.20 | 可選擇限制 Mooncake 傳輸批次大小,避免單次超時呼叫留下無限寫入。 | |
Lifetime | v0.5.21 | 解碼在撤回請求並釋放頁面前,等待進行中的解除安裝到主機記憶體。由 Cloudflare 撰寫。 | |
Lifetime | v0.5.22 | 在主機記憶體快取還原失敗時,保持張量並行等級一致。 | |
Observability | v0.5.21 | 新增 | |
Observability | v0.5.20, v0.5.21 | 修正 SGLang KV canary,該功能可偵測並記錄潛在損毀。 | |
Observability | v0.5.22 | 延遲釋放的度量:計數、持續時間,以及每次保持是否因排空確認或超時結束。 |
These follow-ups are still works in progress:
- #39729 對
--disaggregation-enable-kv-checksum的額外清理。已合併,待發布。 - Zero KV pages on release, draft proposal: #46435
- Extend KV checksum validation from the prefill-to-decode handoff to every decode step (see Integrity by construction, below)
- Broader KV Canary and KV Checksum model and feature support
What we recommend to other operators
If you run SGLang in disaggregated prefill/decode mode, we recommend the following:
- 測量您的 KV 池並將其與上下文長度比較,在每個預填與解碼節點上,從引擎啟動日誌開始,於每次權重、量化、並行度或記憶體比例變更後。若池較小,降低上下文長度以適配。不要依賴代理強制限制,也不要假設引擎會拒絕不符合的請求。這是我們未執行的檢查。
- 升級至包含 #36988 與 #38935(v0.5.22 或更高版)的構建,以便在交接前即拒絕超長請求,無論池大小。這是引擎端修正,也是當您在第一步的檢查錯誤時的後備方案。
- 在兩個角色上啟用延遲 KV 釋放(在 #41023 後預設啟用),並限制 Mooncake 傳輸批次,避免單次超時呼叫留下無限寫入尾部。
- 啟用模型支援的損毀偵測。
--disaggregation-enable-kv-checksum與--kv-canary尚未涵蓋所有模型架構。請檢查日誌以確認請求已被驗證。我們正與 SGLang 團隊合作,將兩項檢查擴充套件至更多架構,包括 GLM 5.3、GLM 5.3 Flash 與 DeepSeek V4。 - 確保您的代理伺服器不會產生不必要的中止。設定您的負載平衡器允許請求在第一個令牌之前繼續,以避免觸發需要跨節點協調的中止。
長期而言
從構造上確保完整性
配置檢查與生命週期修正減少頁面被濫用的方式。它們並不能完全避免濫用,這次事件提醒我們下一個錯誤可能呈現我們未曾見過的形式。持久的答案是讓引擎在讀取時偵測到請求即將存取非自己擁有的頁面,並直接失敗該請求,而不是返回他人資料。
我們在今年夏天開發了這個原型,並於八月描述了它於八月。每個實體 KV 頁面都有一個標籤,當頁面重新分配時會改變;每個請求會記錄其預期的標籤,並在解碼讀取快取前檢查對映。在我們的測試中,這對吞吐量和尾部延遲的成本低於 1%。我們於八月中旬在 GLM 5.2 上測試,之後因可靠性問題很快停用。
該原型還與特定的注意力後端和模型架構相關聯,因而無法作為通用修復。我們正與 SGLang 團隊合作,將此功能構建為可跨所有模型使用的生產版本,而非維護 Cloudflare 專屬補丁。在#39500 中的每次傳輸校驗碼是第一步:它驗證了從預填充到解碼的交接。頁面級標籤更進一步,因為它們會檢查每一次讀取,包括傳輸後很久的讀取。
數學不可行性確保完整性
RDMA 直接將資料從一個節點的記憶體複製到另一個節點。兩臺機器必須就資料去向、大小以及完成時間達成共識;任何不匹配都可能導致接收端讀取錯誤資料。此錯誤是分散式系統中一致性失敗的例子,兩臺機器對該系統狀態持有不一致的觀點。
在 Cloudflare 其他部門,我們已投入努力,利用形式驗證方法證明分散式系統的安全性。Topaz 讓我們的權威 DNS 伺服器能進行無衝突的 IP 選擇。對Meerkat’s QuePaxa 實作的模型顯示了一些一致性錯誤,後續已被修正。
我們現在將在形式方法上的經驗導向 KV 快取傳輸引擎。這將從建模並證明基於 RDMA 的 KV 快取傳輸協定的安全性開始。目標是使用 Mooncake 模擬我們的 SGLang 產線分散式 KV 快取系統,然後追蹤並修正任何偏離我們想要保證的安全屬性的差異。目的是盡可能證明此類錯誤不會再次發生。實際上,我們預期證明工具能告訴我們的範圍有限,但證明其餘系統可讓我們更密切審查與監控這些缺口。
協調披露
我們正將此報告作為協調披露的一部分發布。感謝研究員 Gadi Evron,他透過我們的 HackerOne 方案報告此問題,提供了清晰可重現的指令碼,並給予我們時間修復並與 SGLang 維護者協調發布前的事宜。我們也感謝 RadixArk 的 SGLang 團隊,他們將此視為自己的問題,同時以緊急方式完成上游修復。
閱讀更多於 SGLang 團隊的貼文.
如果您在 Workers AI 中發現安全問題,請於 hackerone.com/cloudflare.