[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:
co-authored by
mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent
7852e50e4d
commit
9492362972
@@ -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
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user