flawopen.com/Teardowns/cve-2026-105752-vllm-harmony-cache-salt-omission
CVE-2026-105752:vLLM 遗漏 cache_salt
vLLM Responses 中的 GPT-OSS 工具续接可能遗漏 cache_salt,并使用未加盐的前缀缓存命名空间。请升级到 vLLM 0.30.0 或更高版本,并在每次续接中保留租户专属盐值。[1], [5]
想象每位客户的文件都放在贴有私密颜色标签的储物柜中。第一个请求带有正确标签,但工具把文件交回下一步时遗漏了标签。共享货架可通过缓存命中提示猜测的页面是否已存在,但不会交出页面内容。[1]
核心概念与专有名词
Prefix caching- 为相同前缀重复使用模型键值块。[6]
cache_salt- 混入首块哈希、用于隔离不同盐值缓存的值。[6]
Harmony continuation- Harmony 响应后的下一模型输入,包括工具调用之后。[1]
Membership oracle- 提示猜测前缀是否存在、但不泄露其内容的信号。[1]
CWE-524- 使用包含敏感信息的缓存;公告的主要分类。[1]
框架映射
- 本指南所述弱点的 CWE 参考: CWE-524
根本原因剖析 (Root Cause)
vLLM 公告描述 POST /v1/responses 上的 GPT-OSS Harmony 路径。初始引擎输入将 request.cache_salt 传给 tokens_input;内置或 MCP 工具调用后,续接却使用不带盐值的 tokens_input(token_ids)。helper 仅在收到盐值时保存它。因此,共享部署启用自动前缀缓存时,续接会使用未加盐的命名空间。公告列出 0.30.0 之前的版本,确认 0.25.1 存在此行为,并称更早的下限尚未确定。结果是通过 cached_tokens_per_turn 暴露缓存成员关系信号,而非直接泄露任意提示文本。公告还列出 CWE-200 作为影响;本文仅将 CWE-524 作为主要弱点。[1], [3], [4]
攻击执行流程分解
共享服务满足前置条件
需要受影响版本、GPT-OSS Harmony Responses、前缀缓存和工具;受害请求需设置 cache_salt 并进入续接。[1]
首个引擎输入带有盐值
首次调用 tokens_input 会传入 request.cache_salt,使前缀进入调用方加盐的命名空间。[1]
工具续接丢失盐值
工具返回后,续接调用不带原盐值的 tokens_input(token_ids),前缀可能进入共享的未加盐空间。[1]
缓存计数可能泄露成员关系
同租户用户若重建该前缀,可查看 cached_tokens_per_turn 推断其是否存在,但不能获取任意提示文本。[1]
源代码对比:漏洞与安全实现
vLLM 公告描述 POST /v1/responses 上的 GPT-OSS Harmony 路径。初始引擎输入将 request.cache_salt 传给 tokens_input;内置或 MCP 工具调用后,续接却使用不带盐值的 tokens_input(token_ids)。helper 仅在收到盐值时保存它。因此,共享部署启用自动前缀缓存时,续接会使用未加盐的命名空间。公告列出 0.30.0 之前的版本,确认 0.25.1 存在此行为,并称更早的下限尚未确定。结果是通过 cached_tokens_per_turn 暴露缓存成员关系信号,而非直接泄露任意提示文本。公告还列出 CWE-200 作为影响;本文仅将 CWE-524 作为主要弱点。 Upstream excerpts, not a literal line-by-line diff.[1], [3], [4], [7]
源码摘录: vLLM Responses Harmony 渲染器(不同版本的代码片段)
# 初始请求路径使用了 salt,但工具调用后的续接路径没有。
engine_input = tokens_input(token_ids, cache_salt=request.cache_salt)
# 工具返回结果后,续接路径会这样重新构造输入:
engine_input = tokens_input(token_ids)
# 在 Harmony 渲染上下文中保留请求的 salt。
cache_salt = cast(str | None, engine_input.get("cache_salt"))
engine_input = self.online_renderer.render_responses_harmony_messages(
context.messages,
cache_salt=cache_salt,
tok_params=tok_params,
)
# 渲染器每次创建引擎输入时都会提供 salt。
engine_input = tokens_input(prompt["prompt_token_ids"], cache_salt=cache_salt)