flawopen.com/Teardowns/cve-2026-105752-vllm-harmony-cache-salt-omission
CVE-2026-105752: vLLM cache-salt omission
A vLLM GPT-OSS Responses tool continuation could omit the request's cache_salt, placing that continuation in an unsalted prefix-cache namespace. Upgrade to vLLM 0.30.0 or later and preserve tenant-scoped cache salts across every continuation.[1], [5]
Imagine each tenant's papers are stored in lockers marked with a private color tag. The first request gets the right tag, but when a tool hands the papers back for the next step, the tag is accidentally left off. A shared shelf can then reveal whether a guessed set of pages is already there by reporting a cache hit; it does not hand over the pages themselves.[1]
Core Concepts & Subsystem Terms
Prefix caching- Reuse of computed model-key/value blocks for matching prompt prefixes, reducing repeated inference work.[6]
cache_salt- A caller-supplied value mixed into the first prefix-block hash so requests with different salts do not share those cached blocks.[6]
Harmony continuation- The follow-up model input assembled after a GPT-OSS Harmony response, including after a tool call.[1]
Membership oracle- A signal that answers whether a guessed value or prefix is present, without returning the underlying content.[1]
CWE-524- Use of a cache that contains sensitive information; this is the primary weakness classification assigned in the vLLM advisory.[1]
Framework mappings
- CWE reference for the weakness described in this guide: CWE-524
Root Cause Analysis
The vLLM advisory describes the GPT-OSS Harmony path on POST /v1/responses. The initial engine input passed request.cache_salt to tokens_input, but after a built-in or MCP tool call the continuation rebuilt its input with tokens_input(token_ids) and omitted the salt. The helper only stores a salt when one is passed. On a shared deployment with automatic prefix caching, the continuation therefore used the unsalted cache namespace even though the original request was salted. The advisory lists versions before 0.30.0 as affected, confirms the issue on 0.25.1, and says the lower bound predates the confirmed test and is not established. The result is a cache-membership signal through cached_tokens_per_turn, not direct disclosure of arbitrary prompt text. The vLLM advisory also lists CWE-200 as an impact classification; CWE-524 is used here as the single primary weakness.[1], [3], [4]
Step-by-Step Attack Flow
A shared service meets the prerequisites
Requires affected vLLM, GPT-OSS Harmony Responses, prefix caching and a tool; the victim must set cache_salt and reach a continuation.[1]
The first engine input is salted
The initial tokens_input call includes request.cache_salt, placing the first prefix in the caller’s salted namespace.[1]
A tool continuation drops the salt
After a tool result, the continuation calls tokens_input(token_ids) without the original salt, so its prefix can enter the shared unsalted namespace.[1]
A cache count can disclose membership
A co-tenant who reconstructs the post-tool prefix can inspect cached_tokens_per_turn to infer membership, not retrieve arbitrary prompt text.[1]
Source Code: Flaw vs. Secure Implementation
The vulnerable excerpt isolates the documented omission: a continuation called tokens_input without the salt. In the v0.30.0 upstream refactor (PR #50195 and commit 6a2a2bb), the renderer carries the salt into Harmony rendering and passes it to tokens_input for the resulting input. These are focused excerpts from separate revisions, not a literal line-for-line diff or a drop-in patch. This closes the described salt-propagation gap; it does not make cache_salt a complete tenant-isolation boundary.[1], [3], [4], [7]
Source excerpt: vLLM Responses Harmony renderer (upstream excerpts; vulnerable and fixed paths are from separate revisions)
# Initial request path was salted, but the tool continuation was not.
engine_input = tokens_input(token_ids, cache_salt=request.cache_salt)
# After a tool result, the continuation rebuilt the input like this:
engine_input = tokens_input(token_ids)
# Preserve the request salt in the Harmony rendering context.
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,
)
# The renderer supplies the salt whenever it creates engine input.
engine_input = tokens_input(prompt["prompt_token_ids"], cache_salt=cache_salt)
Engineering & System Hardening Checklist
- ✓Upgrade affected vLLM deployments to 0.30.0 or later; verify the running image and package version rather than relying only on a deployment manifest.[1], [5]
- ✓For every tenant-isolated request, generate an unpredictable high-entropy cache_salt at the trusted gateway and attach it to every request and continuation; do not accept another tenant's chosen salt.[6], [1]
- ✓Add a regression test that asserts the same tenant salt reaches every Harmony engine input after built-in and MCP tool continuations, and that different tenant salts produce distinct cache namespaces.[7]
- ✓If strict tenant separation is required, isolate tenants at the process or deployment boundary; vLLM documents cache_salt as a mitigation for cache sharing, not a complete isolation boundary.[6]
- ✓Review whether GPT-OSS Responses, tool continuations, and automatic prefix caching are enabled in each shared deployment, and limit cross-tenant access to cache-usage metrics such as cached_tokens_per_turn.[1]
- ✓Treat any suspected cache-membership inference as exposure of a guessed prefix signal; do not report it as arbitrary prompt disclosure without separate evidence.[1]