Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,38 @@ def test_auto_discovered_protected_routes_require_auth(task_routes):
)


CONTROL_PLANE_PATHS = (
"/abort_requests",
"/detokenize",
"/is_scaling_elastic_ep",
"/scale_elastic_ep",
"/start_profile",
"/stop_profile",
"/tokenize",
)


def test_control_plane_routes_require_auth():
"""Stateful control-plane endpoints are mounted at the top level
(outside the /v1, /v2, /inference, /cohere prefixes) but must still
be authenticated when an API key is configured."""
mock_routes = [(path, ["POST"]) for path in CONTROL_PLANE_PATHS]
app = _create_app_with_mock_routes(mock_routes)
client = TestClient(app)

for path in CONTROL_PLANE_PATHS:
assert path.startswith(GUARDED_PREFIX), f"{path} must be guarded"

resp = client.post(path)
assert resp.status_code == 401, f"POST {path} should reject missing token"

resp = client.post(path, headers={"Authorization": "Bearer wrong"})
assert resp.status_code == 401, f"POST {path} should reject invalid token"

resp = client.post(path, headers={"Authorization": "Bearer valid-token"})
assert resp.status_code == 200, f"POST {path} should accept valid token"


def test_auto_discovered_unprotected_routes_no_auth(task_routes):
"""For every auto-discovered route that does NOT start with a guarded
prefix, verify that no authentication is required."""
Expand Down
21 changes: 20 additions & 1 deletion vllm/entrypoints/serve/middleware/authenticate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,26 @@
from starlette.responses import JSONResponse
from starlette.types import ASGIApp, Receive, Scope, Send

GUARDED_PREFIX = ("/v1", "/v2", "/inference", "/cohere")
# Paths that require authentication when an API key is configured.
# Besides the OpenAI-compatible API prefixes, this also covers stateful
# control-plane endpoints that are mounted outside those prefixes
# (/tokenize, /scale_elastic_ep, /abort_requests, /start_profile, ...).
# They mutate or introspect engine state, so they must not be reachable
# unauthenticated while the rest of the API is behind --api-key.
# Note: "/tokenize" also covers the /tokenizer_info endpoint.
GUARDED_PREFIX = (
"/v1",
"/v2",
"/inference",
"/cohere",
"/abort_requests",
"/detokenize",
"/is_scaling_elastic_ep",
"/scale_elastic_ep",
"/start_profile",
"/stop_profile",
"/tokenize",
)


class AuthenticationMiddleware:
Expand Down
Loading