Target: gpustack/gpustack @ commit 6bee5883 (HEAD on 2026-05-14)
| Field | Value |
|---|---|
| Severity | Critical |
| CVSS v3.1 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CVSS Score | 9.8 |
| CWE | CWE-290 (Authentication Bypass by Spoofing), CWE-284 (Improper Access Control) |
When force_auth_localhost is False (default) and gateway_mode resolves to disabled (default on bare metal), gpustack.api.auth.get_current_user auto-promotes any HTTP request whose TCP peer IP is 127.0.0.1/::1 to the first admin user.
In any deployment that puts a reverse proxy in front of gpustack on the same host — the canonical self-hosted shape (nginx, Caddy, Traefik, Docker with same-host upstream, Kubernetes sidecar) every TCP connection arriving at gpustack comes from the proxy at 127.0.0.1. The bypass therefore triggers for every unauthenticated remote request, returning full admin and allowing an attacker to mint a persistent admin API key (scope=["*"]) usable from any source IP thereafter.
gpustack/api/auth.py (auto-admin branch, ~lines 122–125 of HEAD 6bee5883):
if user is None and client_ip_getter(request=request) == "127.0.0.1":
if not server_config.force_auth_localhost:
user = await User.first_by_field(session, "is_admin", True)
client_ip_getter (~lines 80–89) uses Starlette's request.client.host (the TCP peer IP). It only consults the X-GPUStack-Real-IP header when gateway_mode != disabled, which is not the default.
Default config (gpustack/config/config.py):
force_auth_localhost: bool = Falsegateway_mode: str = "auto" → resolves to disabled on bare-metal.