Automated Threat Modeling

Brief Summary: GPUStack CVE-2026-102895 Loopback Authentication Bypass Granting Unauthenticated Admin Access

A brief summary of CVE-2026-102895, a critical authentication bypass in GPUStack before 2.2.0 that let remote unauthenticated requests gain administrator access through loopback trust. Includes patch information from the fixing commit.

CVE Analysis

8 min read

Gaurav Sarraf
Gaurav Sarraf

2026-11-04

Brief Summary: GPUStack CVE-2026-102895 Loopback Authentication Bypass Granting Unauthenticated Admin Access
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A reverse proxy sitting on the same host as your AI serving control plane is one of the most ordinary deployment patterns imaginable, and in GPUStack before 2.2.0 it was enough to hand a remote attacker the keys to the whole cluster. CVE-2026-102895 turned an apparent loopback origin into an automatic administrator identity. The vendor confirmed it was exploited in the wild against a 2.1.1 deployment, where attacker-created backends ran reverse shells.

GPUStack is an open-source GPU cluster manager for AI model serving and GPU instance provisioning, licensed under Apache 2.0. It orchestrates inference engines including vLLM, SGLang and TensorRT LLM, and runs across on-premises servers, Kubernetes clusters and cloud providers. Because it puts serving configuration and infrastructure provisioning under one control plane, a compromise of its authentication layer exposes both model serving and the underlying worker and cloud credentials.

Technical Information

The vulnerable function is get_current_user in gpustack/api/auth.py. Before version 2.2.0, after the server failed to resolve a user from a session or API token, it had a fallback branch: if client_ip_getter reported 127.0.0.1 and force_auth_localhost was off (the default), the server logged the caller in as the first user with is_admin=True. The decision rested on apparent network origin rather than on possession of a valid credential.

The advisory states the design flaw plainly. The code trusted where a request came from instead of who sent it. That assumption breaks the moment the immediate connection peer and the original requester are not the same party, which is exactly the situation a reverse proxy creates.

The removed branch, visible in the fixing commit, is concise:

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)

Two Remote Entry Paths

The vendor documents two independent ways a remote request reaches this branch:

  • Reverse proxy on the same host. A proxy configured with proxy_pass http://127.0.0.1:<port> forwards every external request to GPUStack over a loopback TCP connection. GPUStack sees the proxy's loopback peer and applies the automatic administrator branch. No forged header or credential is required. Stripping a header alone does not fix this path.
  • Forged real IP header. Where GPUStack accepts a client-controlled real IP value, a request carrying X-GPUStack-Real-IP: 127.0.0.1 makes the source appear trusted and triggers the same branch.

Both mechanisms reach the same privilege decision, which is why the vendor's permanent remedy removes the branch itself rather than trying to make loopback detection more precise.

Attack Flow

Based on the advisory, exploitation proceeds without any valid credential:

  1. The attacker sends a request to a GPUStack deployment that either sits behind a same-host reverse proxy or accepts a client-supplied real IP header.
  2. GPUStack's source assessment reports 127.0.0.1, either from the proxy's loopback TCP peer or from the forged X-GPUStack-Real-IP value.
  3. With force_auth_localhost at its default of false, get_current_user resolves the request to the first administrator user.
  4. The now-administrator request can enumerate and modify users, models, workers and configuration, and read worker credentials and cloud or provider secrets.
  5. The attacker creates a persistent administrator API key with scope ["*"]. The key is valid from any source IP afterward, so the attacker keeps access once the source-based shortcut is no longer needed.
  6. Administrator functionality, specifically POST /v2/inference-backends/from-yaml, leads to code execution on worker hosts. In the observed 2.1.1 incident, attacker-created custom backends ran reverse shells.

NVD records the weakness under CWE-287 (Improper Authentication), CWE-290 (Authentication Bypass by Spoofing) and CWE-346 (Origin Validation Error), with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, assigned by the GitHub CNA.

Patch Information

The fix is GPUStack 2.2.0 (pip package gpustack). Advisory GHSA-gj9m-f8ph-pmhr lists < 2.2.0 as vulnerable and 2.2.0 as patched. The core change is commit 53f42a6, titled "fix: remove localhost auto-admin bypass", which arrived via PR #5346, merged on 2026-05-19. The v2.2.0 release came out on 2026-06-18. Its release notes do not mention this fix, so track it through the advisory and the commit.

Stop Treating the Network Source as Proof of Identity

The maintainers could have tried to make loopback detection more accurate. Instead they removed the branch entirely:

@@ -120,9 +120,6 @@ async def get_current_user( if token is not None: user, api_key = await get_user_from_api_token(session, token) - 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) if user: if not user.is_active: raise UnauthorizedException(message="User account is deactivated")

After the patch, only a real credential such as a session or an API token can set user. A request without credentials raises UnauthorizedException, whether it comes from 127.0.0.1, ::1, a reverse proxy, or a remote address. Because the IP check no longer exists, neither a proxy's loopback TCP peer nor a forged X-GPUStack-Real-IP: 127.0.0.1 header grants anything.

force_auth_localhost Is Deprecated and Does Nothing

With the bypass gone, the opt-out flag has no purpose. The commit keeps it only so existing configs and CLI calls still parse:

  • In gpustack/config/config.py, the field changes from force_auth_localhost: bool = False to Optional[bool] = None, with a comment noting that localhost callers must always authenticate now.
  • In gpustack/cmd/start.py, the --force-auth-localhost help text marks it a deprecated no-op. If the flag is set, the server logs at startup: "force_auth_localhost is deprecated and ignored: localhost callers are always authenticated."
  • The CLI reference (docs/cli-reference/start.md) lists the default as (unset) and drops force_auth_localhost: false from the sample config.

No setting in 2.2.0 restores the old behavior. Any automation that relied on local, credential-free admin access, such as scripts calling http://127.0.0.1 on the server host, now has to send a real API key.

Regression Tests

The commit adds a parameterized test, test_get_current_user_requires_credentials, to tests/api/test_auth.py, covering four cases:

  1. A genuine local request (127.0.0.1, Host: 127.0.0.1:30080)
  2. A remote attacker behind a reverse proxy (TCP peer 127.0.0.1 with X-Forwarded-For: 8.8.8.8)
  3. IPv6 loopback (::1)
  4. An external IP (10.0.0.1)

Every case must raise UnauthorizedException. The test also asserts that User.first_by_field and UserService.get_by_username are never awaited, so no database lookup can happen when credentials are missing. That guards against a future change reviving an implicit-admin shortcut. The existing X-API-Key tests were updated to drop force_auth_localhost from their mocked config.

Header Handling

The advisory states that 2.2.0 also hardened client-IP and forwarding-header handling, and that a real-client-IP header should only be accepted from an authenticated or trusted gateway. The fetched commit does not touch client_ip_getter or X-GPUStack-Real-IP, so that change is not shown here. For this CVE, removing the loopback check is what closes the authentication bypass, because the reported client IP no longer affects authentication.

Affected Systems and Versions

All GPUStack versions before 2.2.0 are vulnerable. The advisory GHSA-gj9m-f8ph-pmhr lists < 2.2.0 as vulnerable and 2.2.0 as patched, and NVD records 2.2.0 as the fixed version. Exploitation was observed against a 2.1.1 deployment. The vulnerable configuration is the default, with force_auth_localhost set to false. Deployments are reachable through either a same-host reverse proxy forwarding to 127.0.0.1 or any configuration that accepts a client-supplied X-GPUStack-Real-IP header.

Vendor Security History

A separate VulnCheck advisory, CVE-2026-58658, describes unprotected worker endpoints /serveLogs and /debug in GPUStack 2.2.1 and earlier. It reports access to inference logs containing prompts and completions, changes to logging and worker configuration, and access to memory profiling data. VulnCheck identifies a fixing commit (4e20551) but does not name a fixed release in that advisory. This issue is separate from CVE-2026-102895, with a different surface and a different version range. Taken together, the two findings show that resolving the central API bypass does not by itself establish that every worker interface is secure. That is a limited conclusion from two documented findings, not a comprehensive rating of GPUStack's security maturity. GPUStack publishes a security policy requesting private vulnerability reports through GitHub, with [email protected] as an alternative contact.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization