main
py 137 lines 4.95 KB
Raw
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.exception_handling.utils import sanitize_message
14
15
16 def test_sanitize_message():
17 assert (
18 sanitize_message("<_io.BytesIO object at 0x7a95e299e750>")
19 == "<_io.BytesIO object>"
20 )
21 assert (
22 sanitize_message("<PIL.Image.Image image mode=RGB size=2x2 at 0x7D52EAF07BF0>")
23 == "<PIL.Image.Image image mode=RGB size=2x2>"
24 )
25
26
27 class TestSanitizeMessageFilePaths:
28 """sanitize_message should also strip file paths and traceback
29 frames, not just memory addresses - see #31683."""
30
31 def test_strips_traceback_style_frame(self):
32 msg = (
33 "1 validation error:\n"
34 " {'type': 'list_type', 'loc': ('body', 'messages')}\n"
35 '\n File "/usr/local/lib/python3.12/dist-packages/vllm/'
36 'entrypoints/serve/utils/api_utils.py", line 40, '
37 "in create_chat_completion\n"
38 " POST /v1/chat/completions"
39 )
40 result = sanitize_message(msg)
41 assert "/usr/local/" not in result
42 assert "api_utils.py" not in result
43 assert "list_type" in result
44
45 def test_strips_arbitrary_absolute_path(self):
46 result = sanitize_message("Error in /home/user/project/vllm/server.py")
47 assert "/home/user" not in result
48
49 def test_strips_single_parent_container_path(self):
50 """Regression: /app/server.py and /workspace/server.py (common in
51 container deployments) were missed by the original {2,} quantifier."""
52 assert "/app/" not in sanitize_message("Error in /app/server.py")
53 assert "/workspace/" not in sanitize_message("Error in /workspace/server.py")
54
55 def test_preserves_api_endpoint_paths(self):
56 msg = "POST /v1/chat/completions failed"
57 assert "/v1/chat/completions" in sanitize_message(msg)
58
59 def test_preserves_short_field_references(self):
60 msg = "Invalid value for field 'body.messages'"
61 assert sanitize_message(msg) == msg
62
63 def test_strips_both_address_and_path(self):
64 msg = (
65 "<Request at 0x7f123> failed at "
66 "/usr/local/lib/python3.12/dist-packages/vllm/server.py"
67 )
68 result = sanitize_message(msg)
69 assert "0x" not in result
70 assert "/usr/local/" not in result
71
72
73 class TestSanitizeMessageCoversLeakPatterns:
74 """Ensure sanitize_message strips addresses from realistic exceptions."""
75
76 @pytest.mark.parametrize(
77 ("raw", "expected"),
78 [
79 (
80 "cannot identify image file <_io.BytesIO object at 0x7a95e299e750>",
81 "cannot identify image file <_io.BytesIO object>",
82 ),
83 (
84 "cannot identify image file <_io.BytesIO object at 0x7f3c1a2b4d90>",
85 "cannot identify image file <_io.BytesIO object>",
86 ),
87 (
88 "<PIL.PngImagePlugin.PngImageFile image mode=RGB "
89 "size=8x8 at 0x7f3c1a2b4d90>",
90 "<PIL.PngImagePlugin.PngImageFile image mode=RGB size=8x8>",
91 ),
92 (
93 "Error processing <_io.BytesIO object at 0xdeadbeef>: invalid header",
94 "Error processing <_io.BytesIO object>: invalid header",
95 ),
96 ],
97 ids=[
98 "bytesio-standard",
99 "bytesio-different-addr",
100 "pil-image-repr",
101 "mid-string-repr",
102 ],
103 )
104 def test_address_stripped(self, raw: str, expected: str):
105 assert sanitize_message(raw) == expected
106
107 def test_safe_message_unchanged(self):
108 msg = "Invalid request: missing 'messages' field"
109 assert sanitize_message(msg) == msg
110
111 def test_multiple_addresses_stripped(self):
112 raw = "<obj at 0xaaa> and <obj at 0xbbb>"
113 result = sanitize_message(raw)
114 assert "0x" not in result
115
116
117 class TestAffectedModulesUseSanitize:
118 """Verify that affected modules call sanitize_message (source-level)."""
119
120 @pytest.mark.parametrize(
121 "module",
122 [
123 "vllm.entrypoints.anthropic.serving",
124 "vllm.entrypoints.speech_to_text.realtime.connection",
125 ],
126 )
127 def test_module_calls_sanitize_message(self, module: str):
128 import importlib.util
129 from pathlib import Path
130
131 spec = importlib.util.find_spec(module)
132 assert spec is not None and spec.origin is not None, (
133 f"Cannot locate module {module}"
134 )
135 source = Path(spec.origin).read_text()
136 assert "sanitize_message" in source, f"{module} does not call sanitize_message"
137 assert "import" in source and "sanitize_message" in source