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