---
title: "One untagged tuple is one namespace, not two"
date: 2026-10-06
series: 25
summary: "In multi-tenant serving, the prefix cache is a shared database and the block hash is its only access control. vLLM v0.31.0 fixed a key collision that proves the point."
voice: architect
tags: [prefix-cache-key-collision, kv-cache-tenant-isolation, cache-salt, lora-serving, release-notes-security-section]
image: "/notes/vllm-prefix-cache-collision/image.png"
imageAlt: null
linkedin: "urn:li:share:7513126224538439680"
sources:
  - title: "Tagged extra keys: LoRA name vs cache_salt collision fix — vLLM PR #51899"
    url: https://github.com/vllm-project/vllm/pull/51899
    date: 2026-09-30
  - title: "LoRA path added to block hash (re-pointed adapter names) — vLLM PR #59335"
    url: https://github.com/vllm-project/vllm/pull/59335
    date: 2026-10-01
  - title: "v0.31.0 release notes, Security section"
    url: https://github.com/vllm-project/vllm/releases/tag/v0.31.0
    date: 2026-10-05
  - title: "Original report — vLLM issue #44701"
    url: https://github.com/vllm-project/vllm/issues/44701
    date: 2026-06-06
dateApprox: false
---

In multi-tenant serving, the prefix cache is a shared database and the block hash is its only access control. vLLM v0.31.0 fixed a key collision that proves the point.

The bug, from the merged PR: block hashes mixed LoRA adapter names, cache_salt values, multimodal ids and prompt-embeds digests into a single untagged tuple. A LoRA request for adapter foo and a base-model request with cache_salt "foo" produced the same first-block hash for the same prompt tokens. Either request could be served KV computed with the other's weights. A caller could pick a salt that landed inside an adapter's namespace on purpose.

The fix is the textbook one: tag every key by its source, ("lora", name) versus ("cache_salt", salt), so equal values from different fields cannot hash the same. A follow-up PR adds the adapter path to the key, because re-pointing a name at a different adapter kept serving the old KV until eviction.

Upgrade cost: one cold-cache cycle, every block with LoRA or salt keys misses once. KV events keep their old shapes, so external KV-aware routers keep decoding. An in-place adapter reload from the same path is still undetected, you get a warning, publish under a new path instead.

The trap: when two request fields can write the same bytes into one untagged key, you built one namespace, not two. cache_salt read like a tenancy boundary while it was spoofable.

The issue sat four months with one comment; read the Security section of your engine's release notes before the Highlights.
