[Bugfix][Frontend] Constrain Anthropic cache_salt to non-empty (#50764)

Signed-off-by: Omkar Shewale <omkarshewale2001@gmail.com> Signed-off-by: Andreas Karatzas <Andreas.Karatzas@amd.com> Co-authored-by: Andreas Karatzas <akaratza@amd.com> Co-authored-by: Andreas Karatzas <Andreas.Karatzas@amd.com>

Omkar Shewale committed Aug 3, 2026 at 00:33 UTC f5bb701fa270f5c801f1572e1478b56f292d8dfc
2 files changed +49
tests/entrypoints/anthropic/test_anthropic_messages_conversion.py
+48
@@ -13,10 +13,16 @@ Also covers cache usage computation in ``_build_anthropic_usage``.
13 """
14
15 import json
16 +from argparse import Namespace
17 +from http import HTTPStatus
18 from unittest.mock import MagicMock
19
20 import pytest
21 +from fastapi import FastAPI
22 +from fastapi.exceptions import RequestValidationError
23 +from fastapi.testclient import TestClient
24
25 +from vllm.entrypoints.anthropic.api_router import attach_router
26 from vllm.entrypoints.anthropic.protocol import (
27 AnthropicMessagesRequest,
28 )
@@ -38,6 +44,7 @@ from vllm.entrypoints.openai.engine.protocol import (
44 PromptTokenUsageInfo,
45 UsageInfo,
46 )
47 +from vllm.entrypoints.serve.utils.server_utils import validation_exception_handler
48
49 _convert = AnthropicServingMessages._convert_anthropic_to_openai_request
50 _img_url = AnthropicServingMessages._convert_image_source_to_url
@@ -1408,3 +1415,44 @@ class TestCacheSalt:
1415 request = _make_request([{"role": "user", "content": "Hello"}])
1416 result = _convert(request)
1417 assert result.cache_salt is None
1418 +
1419 + @staticmethod
1420 + def _make_api_app():
1421 + app = FastAPI()
1422 + attach_router(app)
1423 + app.state.args = Namespace(log_error_stack=False)
1424 + app.exception_handler(RequestValidationError)(validation_exception_handler)
1425 +
1426 + handler = MagicMock(spec=AnthropicServingMessages)
1427 + handler.create_messages.side_effect = AssertionError(
1428 + "invalid requests must not reach the serving handler"
1429 + )
1430 + app.state.anthropic_serving_messages = handler
1431 + return app, handler
1432 +
1433 + def test_cache_salt_openapi_requires_non_empty_string(self):
1434 + app, _ = self._make_api_app()
1435 + field_schema = app.openapi()["components"]["schemas"][
1436 + "AnthropicMessagesRequest"
1437 + ]["properties"]["cache_salt"]
1438 + string_schema = next(
1439 + option for option in field_schema["anyOf"] if option.get("type") == "string"
1440 + )
1441 +
1442 + assert string_schema["minLength"] == 1
1443 +
1444 + def test_empty_cache_salt_returns_bad_request(self):
1445 + app, handler = self._make_api_app()
1446 + with TestClient(app, raise_server_exceptions=False) as client:
1447 + response = client.post(
1448 + "/v1/messages",
1449 + json={
1450 + "model": "test-model",
1451 + "max_tokens": 1,
1452 + "messages": [{"role": "user", "content": "Hello"}],
1453 + "cache_salt": "",
1454 + },
1455 + )
1456 +
1457 + assert response.status_code == HTTPStatus.BAD_REQUEST
1458 + handler.create_messages.assert_not_awaited()
vllm/entrypoints/anthropic/protocol.py
+1
@@ -135,6 +135,7 @@ class AnthropicMessagesRequest(BaseModel):
135 # vLLM-specific fields that are not in Anthropic spec
136 cache_salt: str | None = Field(
137 default=None,
138 + min_length=1,
139 description=(
140 "If specified, the prefix cache will be salted with the provided "
141 "string to prevent an attacker to guess prompts in multi-user "