@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 39c67d714e
Fork 0

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)