[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>
This commit is contained in:
Juan Pérez de Algaba
2026-06-11 10:05:34 +00:00
committed by GitHub
co-authored by mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent 7852e50e4d
commit 9492362972
4 changed files with 91 additions and 5 deletions
@@ -0,0 +1,81 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Tests that error messages in Anthropic and speech-to-text entrypoints
are sanitized to prevent memory address leakage.
Verifies the fix for the incomplete CVE-2026-22778 remediation where
PIL repr addresses leaked via the Anthropic API router and the
speech-to-text WebSocket paths.
"""
import pytest
from vllm.entrypoints.serve.utils.api_utils import sanitize_message
class TestSanitizeMessageCoversLeakPatterns:
"""Ensure sanitize_message strips addresses from realistic exceptions."""
@pytest.mark.parametrize(
("raw", "expected"),
[
(
"cannot identify image file <_io.BytesIO object at 0x7a95e299e750>",
"cannot identify image file <_io.BytesIO object>",
),
(
"cannot identify image file <_io.BytesIO object at 0x7f3c1a2b4d90>",
"cannot identify image file <_io.BytesIO object>",
),
(
"<PIL.PngImagePlugin.PngImageFile image mode=RGB "
"size=8x8 at 0x7f3c1a2b4d90>",
"<PIL.PngImagePlugin.PngImageFile image mode=RGB size=8x8>",
),
(
"Error processing <_io.BytesIO object at 0xdeadbeef>: invalid header",
"Error processing <_io.BytesIO object>: invalid header",
),
],
ids=[
"bytesio-standard",
"bytesio-different-addr",
"pil-image-repr",
"mid-string-repr",
],
)
def test_address_stripped(self, raw: str, expected: str):
assert sanitize_message(raw) == expected
def test_safe_message_unchanged(self):
msg = "Invalid request: missing 'messages' field"
assert sanitize_message(msg) == msg
def test_multiple_addresses_stripped(self):
raw = "<obj at 0xaaa> and <obj at 0xbbb>"
result = sanitize_message(raw)
assert "0x" not in result
class TestAffectedModulesUseSanitize:
"""Verify that affected modules call sanitize_message (source-level)."""
@pytest.mark.parametrize(
"module",
[
"vllm.entrypoints.anthropic.api_router",
"vllm.entrypoints.anthropic.serving",
"vllm.entrypoints.speech_to_text.realtime.connection",
],
)
def test_module_calls_sanitize_message(self, module: str):
import importlib.util
from pathlib import Path
spec = importlib.util.find_spec(module)
assert spec is not None and spec.origin is not None, (
f"Cannot locate module {module}"
)
source = Path(spec.origin).read_text()
assert "sanitize_message" in source, f"{module} does not call sanitize_message"
assert "import" in source and "sanitize_message" in source
+3 -2
View File
@@ -19,6 +19,7 @@ from vllm.entrypoints.anthropic.serving import AnthropicServingMessages
from vllm.entrypoints.openai.engine.protocol import ErrorResponse
from vllm.entrypoints.serve.utils.api_utils import (
load_aware_call,
sanitize_message,
validate_json_request,
with_cancellation,
)
@@ -75,7 +76,7 @@ async def create_messages(request: AnthropicMessagesRequest, raw_request: Reques
content=AnthropicErrorResponse(
error=AnthropicError(
type="internal_error",
message=str(e),
message=sanitize_message(str(e)),
)
).model_dump(),
)
@@ -121,7 +122,7 @@ async def count_tokens(request: AnthropicCountTokensRequest, raw_request: Reques
content=AnthropicErrorResponse(
error=AnthropicError(
type="internal_error",
message=str(e),
message=sanitize_message(str(e)),
)
).model_dump(),
)
+4 -1
View File
@@ -44,6 +44,7 @@ from vllm.entrypoints.openai.engine.protocol import (
StreamOptions,
)
from vllm.entrypoints.openai.models.serving import OpenAIServingModels
from vllm.entrypoints.serve.utils.api_utils import sanitize_message
from vllm.entrypoints.serve.utils.request_logger import RequestLogger
if TYPE_CHECKING:
@@ -846,7 +847,9 @@ class AnthropicServingMessages(OpenAIServingChat):
logger.exception("Error in message stream converter.")
error_response = AnthropicStreamEvent(
type="error",
error=AnthropicError(type="internal_error", message=str(e)),
error=AnthropicError(
type="internal_error", message=sanitize_message(str(e))
),
)
data = error_response.model_dump_json(exclude_unset=True)
yield wrap_data_with_event(data, "error")
@@ -14,6 +14,7 @@ from starlette.websockets import WebSocketDisconnect
from vllm import envs
from vllm.entrypoints.openai.engine.protocol import ErrorResponse, UsageInfo
from vllm.entrypoints.serve.utils.api_utils import sanitize_message
from vllm.exceptions import VLLMValidationError
from vllm.logger import init_logger
@@ -72,7 +73,7 @@ class RealtimeConnection:
await self.send_error("Invalid JSON", "invalid_json")
except Exception as e:
logger.exception("Error handling event: %s", e)
await self.send_error(str(e), "processing_error")
await self.send_error(sanitize_message(str(e)), "processing_error")
except WebSocketDisconnect:
logger.debug("WebSocket disconnected: %s", self.connection_id)
self._is_connected = False
@@ -262,7 +263,7 @@ class RealtimeConnection:
except Exception as e:
logger.exception("Error in generation: %s", e)
await self.send_error(str(e), "processing_error")
await self.send_error(sanitize_message(str(e)), "processing_error")
async def send(
self, event: SessionCreated | TranscriptionDelta | TranscriptionDone