FlawOpen

flawopen.com/Teardowns/cve-2026-105752-vllm-harmony-cache-salt-omission

● CVE-2026-105752 · CVSS 3.1 · Low
FlawOpen Security Research

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]

💡 Plain English Explainer (ELI5)

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

Step 1

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]

Step 2

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]

Step 3

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]

Step 4

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]

Step 5

The renderer keeps salt on every turn

The merged renderer refactor carries cache_salt through Harmony rendering and passes it to each new engine input; upgrade to 0.30.0+.[1], [3], [7]

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)

UNPATCHED FLAW
# 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)
HARDENED SECURE PATCH
# 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

Sources