A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 3, 13:00 UTC
fix: add API key authorization to /v2 endpoints (#42594)
Signed-off-by: DustHunter <dusthunter@126.com> Signed-off-by: wang.yuqi <yuqi.wang@daocloud.io> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: wang.yuqi <yuqi.wang@daocloud.io>
DustHunter committed
May 16, 2026 at 09:29 UTC
39c67d714ef091df1533181bdc3df82dc9ac3e07
3 files changed
+55
-7
docs/usage/security.md
+4
-2
@@ -128,7 +128,7 @@ firewall configuration instructions.
128
129
### Overview
130
131
-The `--api-key` flag (or `VLLM_API_KEY` environment variable) provides authentication for vLLM's HTTP server, but **only for OpenAI-compatible API endpoints under the `/v1` path prefix**. Many other sensitive endpoints are exposed on the same HTTP server without any authentication enforcement.
131
+The `--api-key` flag (or `VLLM_API_KEY` environment variable) provides authentication for vLLM's HTTP server, but **only for OpenAI-compatible API endpoints under the `/v1` path prefix**, and other similar `/v2`, `/inference` path prefix**. Many other sensitive endpoints are exposed on the same HTTP server without any authentication enforcement.
132
133
**Important:** Do not rely exclusively on `--api-key` for securing access to vLLM. Additional security measures are required for production deployments.
134
@@ -154,6 +154,9 @@ When `--api-key` is configured, the following `/v1` endpoints require Bearer tok
154
- `/v1/rerank` - Reranking API
155
- `/v1/load_lora_adapter` - Load a LoRA adapter (can alter model behavior; only available when `--enable-lora` is set and `VLLM_ALLOW_RUNTIME_LORA_UPDATING=True`)
156
- `/v1/unload_lora_adapter` - Unload a LoRA adapter (can alter model behavior; only available when `--enable-lora` is set and `VLLM_ALLOW_RUNTIME_LORA_UPDATING=True`)
157
+- `/inference/v1/generate` - Generate completions
158
+- `/v2/embed` - Cohere Embed API
159
+- `/v2/rerank` - Cohere Rerank API
160
161
### Unprotected Endpoints (No API Key Required)
162
@@ -162,7 +165,6 @@ The following endpoints **do not require authentication** even when `--api-key`
165
**Inference endpoints:**
166
167
- `/invocations` - SageMaker-compatible endpoint (routes to the same inference functions as `/v1` endpoints)
165
-- `/inference/v1/generate` - Generate completions
168
- `/generative_scoring` - Generative scoring API
169
- `/pooling` - Pooling API
170
- `/classify` - Classification API
tests/entrypoints/serve/instrumentator/test_optional_middleware.py
+46
-3
@@ -86,13 +86,56 @@ async def test_passed_api_token(server: RemoteOpenAIServer):
86
indirect=True,
87
)
88
@pytest.mark.asyncio
89
-async def test_not_v1_api_token(server: RemoteOpenAIServer):
90
- # Authorization check is skipped for any paths that
91
- # don't start with /v1 (e.g. /v1/chat/completions).
89
+async def test_not_v1_or_v2_path_skips_auth(server: RemoteOpenAIServer):
90
+ # Authorization check is skipped for paths that
91
+ # don't start with /v1 or /v2 (e.g. /health, /metrics).
92
response = requests.get(server.url_for("health"))
93
assert response.status_code == HTTPStatus.OK
94
95
96
+# ---------------------------------------------------------------------------
97
+# /v2 path authentication tests
98
+# ---------------------------------------------------------------------------
99
+
100
+
101
+@pytest.mark.parametrize(
102
+ "server",
103
+ [["--api-key", "test"]],
104
+ indirect=True,
105
+)
106
+@pytest.mark.asyncio
107
+async def test_v2_endpoint_rejects_missing_api_token(server: RemoteOpenAIServer):
108
+ # /v2/embed should require authentication when --api-key is set.
109
+ body = {
110
+ "model": MODEL_NAME,
111
+ "texts": ["hello"],
112
+ "embedding_types": ["float"],
113
+ }
114
+ response = requests.post(server.url_for("/v2/embed"), json=body)
115
+ assert response.status_code == HTTPStatus.UNAUTHORIZED
116
+
117
+
118
+@pytest.mark.parametrize(
119
+ "server",
120
+ [["--api-key", "test"]],
121
+ indirect=True,
122
+)
123
+@pytest.mark.asyncio
124
+async def test_v2_endpoint_accepts_valid_api_token(server: RemoteOpenAIServer):
125
+ # /v2/embed should accept requests with a valid API key.
126
+ body = {
127
+ "model": MODEL_NAME,
128
+ "texts": ["hello"],
129
+ "embedding_types": ["float"],
130
+ }
131
+ response = requests.post(
132
+ server.url_for("/v2/embed"),
133
+ json=body,
134
+ headers={"Authorization": "Bearer test"},
135
+ )
136
+ assert response.status_code == HTTPStatus.OK
137
+
138
+
139
@pytest.mark.parametrize(
140
"server",
141
["--enable-request-id-headers"],
vllm/entrypoints/openai/server_utils.py
+5
-2
@@ -35,6 +35,9 @@ from vllm.v1.engine.exceptions import EngineDeadError, EngineGenerateError
35
logger = init_logger("vllm.entrypoints.openai.server_utils")
36
37
38
+GUARDED_PREFIX = ("/v1", "/v2", "/inference")
39
+
40
+
41
class AuthenticationMiddleware:
42
"""
43
Pure ASGI middleware that authenticates each request by checking
@@ -44,7 +47,7 @@ class AuthenticationMiddleware:
47
-----
48
There are two cases in which authentication is skipped:
49
1. The HTTP method is OPTIONS.
47
- 2. The request path doesn't start with /v1 (e.g. /health).
50
+ 2. The request path doesn't start with GUARDED_PREFIX (e.g. /health).
51
"""
52
53
def __init__(self, app: ASGIApp, tokens: list[str]) -> None:
@@ -80,7 +83,7 @@ class AuthenticationMiddleware:
83
url_path = URL(scope=scope).path.removeprefix(root_path)
84
headers = Headers(scope=scope)
85
# Type narrow to satisfy mypy.
83
- if url_path.startswith("/v1") and not self.verify_token(headers):
86
+ if url_path.startswith(GUARDED_PREFIX) and not self.verify_token(headers):
87
response = JSONResponse(content={"error": "Unauthorized"}, status_code=401)
88
return response(scope, receive, send)
89
return self.app(scope, receive, send)