@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 9492362972
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

[Security] Apply sanitize_message to Anthropic and STT error paths (#45119)

Signed-off-by: jperezde <jperezde@redhat.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

Juan Pérez de Algaba committed Jun 11, 2026 at 12:05 UTC 94923629729381d7f7c9efde72071a2441f7fd82
4 files changed +91 -5
tests/entrypoints/serve/utils/test_error_sanitization.py new
+81
@@ -0,0 +1,81 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""Tests that error messages in Anthropic and speech-to-text entrypoints
4 +are sanitized to prevent memory address leakage.
5 +
6 +Verifies the fix for the incomplete CVE-2026-22778 remediation where
7 +PIL repr addresses leaked via the Anthropic API router and the
8 +speech-to-text WebSocket paths.
9 +"""
10 +
11 +import pytest
12 +
13 +from vllm.entrypoints.serve.utils.api_utils import sanitize_message
14 +
15 +
16 +class TestSanitizeMessageCoversLeakPatterns:
17 + """Ensure sanitize_message strips addresses from realistic exceptions."""
18 +
19 + @pytest.mark.parametrize(
20 + ("raw", "expected"),
21 + [
22 + (
23 + "cannot identify image file <_io.BytesIO object at 0x7a95e299e750>",
24 + "cannot identify image file <_io.BytesIO object>",
25 + ),
26 + (
27 + "cannot identify image file <_io.BytesIO object at 0x7f3c1a2b4d90>",
28 + "cannot identify image file <_io.BytesIO object>",
29 + ),
30 + (
31 + "<PIL.PngImagePlugin.PngImageFile image mode=RGB "
32 + "size=8x8 at 0x7f3c1a2b4d90>",
33 + "<PIL.PngImagePlugin.PngImageFile image mode=RGB size=8x8>",
34 + ),
35 + (
36 + "Error processing <_io.BytesIO object at 0xdeadbeef>: invalid header",
37 + "Error processing <_io.BytesIO object>: invalid header",
38 + ),
39 + ],
40 + ids=[
41 + "bytesio-standard",
42 + "bytesio-different-addr",
43 + "pil-image-repr",
44 + "mid-string-repr",
45 + ],
46 + )
47 + def test_address_stripped(self, raw: str, expected: str):
48 + assert sanitize_message(raw) == expected
49 +
50 + def test_safe_message_unchanged(self):
51 + msg = "Invalid request: missing 'messages' field"
52 + assert sanitize_message(msg) == msg
53 +
54 + def test_multiple_addresses_stripped(self):
55 + raw = "<obj at 0xaaa> and <obj at 0xbbb>"
56 + result = sanitize_message(raw)
57 + assert "0x" not in result
58 +
59 +
60 +class TestAffectedModulesUseSanitize:
61 + """Verify that affected modules call sanitize_message (source-level)."""
62 +
63 + @pytest.mark.parametrize(
64 + "module",
65 + [
66 + "vllm.entrypoints.anthropic.api_router",
67 + "vllm.entrypoints.anthropic.serving",
68 + "vllm.entrypoints.speech_to_text.realtime.connection",
69 + ],
70 + )
71 + def test_module_calls_sanitize_message(self, module: str):
72 + import importlib.util
73 + from pathlib import Path
74 +
75 + spec = importlib.util.find_spec(module)
76 + assert spec is not None and spec.origin is not None, (
77 + f"Cannot locate module {module}"
78 + )
79 + source = Path(spec.origin).read_text()
80 + assert "sanitize_message" in source, f"{module} does not call sanitize_message"
81 + assert "import" in source and "sanitize_message" in source
vllm/entrypoints/anthropic/api_router.py
+3 -2
@@ -19,6 +19,7 @@ from vllm.entrypoints.anthropic.serving import AnthropicServingMessages
19 from vllm.entrypoints.openai.engine.protocol import ErrorResponse
20 from vllm.entrypoints.serve.utils.api_utils import (
21 load_aware_call,
22 + sanitize_message,
23 validate_json_request,
24 with_cancellation,
25 )
@@ -75,7 +76,7 @@ async def create_messages(request: AnthropicMessagesRequest, raw_request: Reques
76 content=AnthropicErrorResponse(
77 error=AnthropicError(
78 type="internal_error",
78 - message=str(e),
79 + message=sanitize_message(str(e)),
80 )
81 ).model_dump(),
82 )
@@ -121,7 +122,7 @@ async def count_tokens(request: AnthropicCountTokensRequest, raw_request: Reques
122 content=AnthropicErrorResponse(
123 error=AnthropicError(
124 type="internal_error",
124 - message=str(e),
125 + message=sanitize_message(str(e)),
126 )
127 ).model_dump(),
128 )
vllm/entrypoints/anthropic/serving.py
+4 -1
@@ -44,6 +44,7 @@ from vllm.entrypoints.openai.engine.protocol import (
44 StreamOptions,
45 )
46 from vllm.entrypoints.openai.models.serving import OpenAIServingModels
47 +from vllm.entrypoints.serve.utils.api_utils import sanitize_message
48 from vllm.entrypoints.serve.utils.request_logger import RequestLogger
49
50 if TYPE_CHECKING:
@@ -846,7 +847,9 @@ class AnthropicServingMessages(OpenAIServingChat):
847 logger.exception("Error in message stream converter.")
848 error_response = AnthropicStreamEvent(
849 type="error",
849 - error=AnthropicError(type="internal_error", message=str(e)),
850 + error=AnthropicError(
851 + type="internal_error", message=sanitize_message(str(e))
852 + ),
853 )
854 data = error_response.model_dump_json(exclude_unset=True)
855 yield wrap_data_with_event(data, "error")
vllm/entrypoints/speech_to_text/realtime/connection.py
+3 -2
@@ -14,6 +14,7 @@ from starlette.websockets import WebSocketDisconnect
14
15 from vllm import envs
16 from vllm.entrypoints.openai.engine.protocol import ErrorResponse, UsageInfo
17 +from vllm.entrypoints.serve.utils.api_utils import sanitize_message
18 from vllm.exceptions import VLLMValidationError
19 from vllm.logger import init_logger
20
@@ -72,7 +73,7 @@ class RealtimeConnection:
73 await self.send_error("Invalid JSON", "invalid_json")
74 except Exception as e:
75 logger.exception("Error handling event: %s", e)
75 - await self.send_error(str(e), "processing_error")
76 + await self.send_error(sanitize_message(str(e)), "processing_error")
77 except WebSocketDisconnect:
78 logger.debug("WebSocket disconnected: %s", self.connection_id)
79 self._is_connected = False
@@ -262,7 +263,7 @@ class RealtimeConnection:
263
264 except Exception as e:
265 logger.exception("Error in generation: %s", e)
265 - await self.send_error(str(e), "processing_error")
266 + await self.send_error(sanitize_message(str(e)), "processing_error")
267
268 async def send(
269 self, event: SessionCreated | TranscriptionDelta | TranscriptionDone