GPUStack advisory says loopback trust gives unauthenticated admin; exploited, fixed in 2.2.0, on CISA KEV
GPUStack's own advisory says a request that looks like loopback becomes the built in administrator, and that this was exploited against a 2.1.1 deployment; 2.2.0 fixes it and CISA listed it on 9 November. The fix commit sat on the main branch 174 days before that listing (derived).
By Parminder Kumar Sharma · LinkedIn · · 17 min read
The fix was on the main branch 174 days before CISA listed the flaw (derived)
GPUStack, which the CVE record calls "an open-source GPU cluster manager for AI model serving and GPU instance provisioning", has a commit on its main branch titled "fix: remove localhost auto-admin bypass". The commit page dates it 19 May 2026 at 10:30 UTC. CISA added CVE-2026-102895 to its Known Exploited Vulnerabilities catalogue on 9 November 2026, with a due date of 12 November. That is 174 days after the commit (derived). Release 2.2.0, the version the GitHub advisory names as the fix, appeared on 18 June 2026, 30 days after the commit (derived), and 144 days before the KEV listing (derived).
What this does not establish. The pages do not say when exploitation began, who did it, or how many servers were reached. The advisory says the flaw "has been observed exploited in the wild against a v2.1.1 deployment", with "attacker-created custom backends running reverse shells". That is one deployment, no date, no actor. The commit also does not prove the 2.2.0 fix is complete: the advisory says the fix hardened header handling, and the one commit the CVE record links does not touch that code (see below). Nothing here says how many people run an affected version, or that every version before 2.2.0 is affected, because the advisory prints the range as "< 2.2.0" with no lower bound.
The advisory describes two ways for a remote request to look like it came from the server itself. The one that needs no header at all is a reverse proxy running on the same host as GPUStack. That matters because one workaround the advisory lists, stripping the named header at the proxy, does not close that path on its own. This briefing read the advisory, the CVE record, the CISA catalogue file, the 2.2.0 release page, the pull request page and the commit page from 06:45 UTC on Wednesday 11 November 2026. A KEV listing and a record can change within the hour, so every statement about them carries that time stamp.
One selling interest to state once: GPUStack is open source per its CVE record, and the advisory sits in the project's own repository, so the project is describing the weakness in, and the repair of, its own code. Several outside parties are credited in the advisory text, among them researchers at Johns Hopkins University, and a further party is credited with reporting the exploitation. This briefing names no individual.
What the advisory says happens: two ways to look like 127.0.0.1
In GPUStack before 2.2.0 the function get_current_user, in gpustack/api/auth.py, "promotes any request whose source is considered loopback to the built-in administrator account" when the setting force_auth_localhost is False, which was the default. The source address comes from a function called client_ip_getter. The advisory says that function can be made to report 127.0.0.1 without credentials in two ways.
Path A, the same host reverse proxy. The advisory describes what it calls the canonical self hosted shape, with GPUStack behind a reverse proxy on the same host that forwards to a loopback address. In that layout every remote request reaches GPUStack with a TCP peer of 127.0.0.1, so the loopback branch fires. The advisory's words: "No header or credential is required." It says it verified this end to end against a repository HEAD it identifies by a short hash, not by a release number.
Path B, a client supplied header. The advisory says a forged X-GPUStack-Real-IP header carrying a loopback value can cause the request source to be treated as loopback. The CVE record words it as a "client-supplied X-GPUStack-Real-IP loopback value". The advisory states the root cause as "Trusting the network source of a request (socket peer, or a client-controllable header) as an authentication signal." and adds that once a same host proxy is in front, the peer is always loopback, so the assumption that localhost is trusted no longer holds.
What an unauthenticated caller gets. The advisory lists: enumerate and modify users, models, workers and configuration; mint persistent admin API keys with a wildcard scope, valid from any source IP afterwards; read worker credentials and cloud or provider secrets; and, with the admin only route POST /v2/inference-backends/from-yaml in play, code execution on worker hosts. CISA's catalogue entry repeats the same list in its short description. The advisory does not say what the from-yaml route accepts or runs, and this briefing adds nothing beyond that: it prints no request and no payload.
Severity. The CVE record carries a CVSS 3.1 base score of 9.8 in the container of the CNA, GitHub_M, which is also the assigner. The advisory page shows the same score and vector, and labels it Critical. The CISA ADP container in the record adds an SSVC entry timestamped 5 November 2026: Exploitation active, Automatable yes, Technical Impact total. Neither document says who calculated the base score beyond that container; this briefing did not query the National Vulnerability Database.
The clock: a quiet fix, a late record, a KEV entry
The dates come from five different pages. The table sets each beside its source and keeps them in the order they happened. The one that does not sit comfortably is the advisory's: its page prints 31 July 2026 as the published date, which is 60 days before the CVE record's reserved date of 29 September 2026 (derived). The page does not say why, and it shows no edit history, so whether the exploitation sentence was in the 31 July text is not stated.
Dates as each page prints them, read 11 November 2026. Intervals in the last column are derived by this site.
| Event | Date as printed, and where | Derived interval |
|---|---|---|
| Fix commit on main, titled remove localhost auto-admin bypass | 19 May 2026, committed 10:30:10 UTC (commit page data) | Start |
| Release 2.2.0 | 18 Jun 2026, 15:54 UTC (release page) | 30 days after the commit |
| Advisory GHSA-gj9m-f8ph-pmhr | Published Jul 31, 2026 (advisory page) | 43 days after release 2.2.0 |
| CVE ID reserved | 2026-09-29 (CVE record) | 60 days after the advisory date |
| CVE record published | 2026-11-04 (CVE record) | 96 days after the advisory date |
| CISA ADP SSVC entry | 2026-11-05 (CVE record) | 1 day after the record was published |
| Added to CISA KEV | 2026-11-09 (KEV file, release 2026.11.10) | 174 days after the commit; 101 after the advisory date |
| KEV due date | 2026-11-12 (KEV file) | 3 days after the listing |
The release notes for 2.2.0 list new models, GPU instance management, Helm chart installation, database support and many other changes. Searching the page text for the pull request number, the words loopback, localhost, advisory and authentication bypass finds none of them (derived). A reader who relies on the release notes to decide whether to upgrade would not learn from that page that the release removes an unauthenticated admin path. The advisory is the place that says so.
CISA's catalogue entry says forensic triage is Yes and known ransomware use is Unknown, and its required action cites BOD 26-04 and the Forensics Triage Requirements. Its notes line says the vulnerability "affects an open-source component, third-party library, protocol, or proprietary implementation that could be used by different products". That wording is attributed here to CISA's catalogue notes. It appears in 18 of the 1,779 entries in release 2026.11.10, so it is a recurring note and not text written about GPUStack (derived). It names no product that embeds GPUStack, and no page this briefing read does.
What the fix changed, from the commit the CVE record links
The advisory says 2.2.0 fixed the flaw: "the loopback auto-admin path was removed and client-IP / forwarding-header handling was hardened". Its remediation line says to "only accept a real-client-IP header from an authenticated/trusted gateway (never from arbitrary clients)". The CVE record links the pull request, a commit and the release. The commit page shows five files changed, with 67 additions and 17 deletions: the documentation page for the start command, the authentication module, the start command, the configuration module and a test file.
In the authentication module the commit deletes three lines (shown under Technical details). Together they are the loopback branch: if no user was found and the client IP equals 127.0.0.1, and the flag is not true, use the first admin user. The setting force_auth_localhost is not removed: it becomes a deprecated option that does nothing, kept so existing configuration files still load, and the server logs a warning if it is set. The commit's test file adds cases that must raise an unauthorised error with no credentials, including a request with a TCP peer of 127.0.0.1 that carries a forwarded for header, a request from IPv6 loopback, and one from an outside address.
What the commit page does not show. The commit does not touch client_ip_getter and contains no reference to the X-GPUStack-Real-IP header (derived by searching the commit page). The advisory says header handling was hardened in 2.2.0. Either that change is in a different commit, or the advisory's wording is wider than this commit. The pull request has three commits, titled in order "fix: remove localhost auto-admin bypass", "fix: require worker auth on /config endpoint" and "feat: mint local JWT in reset-admin-password fallback". This briefing read the first commit's page and the pull request page, not the other two commits, so it cannot say whether the header change is in them. The pull request description reads "No description provided.", and its title calls the change a refactor.
What the commit does not do (derived from its diff). The three changed lines in the authentication module are the loopback branch, and the other four files hold documentation, option handling and tests. The diff contains no code that revokes or lists API keys, so nothing in it removes administrator keys created while the flaw was open, and the advisory says such keys are valid from any source IP. Upgrading removes the path in; whether it removes what an intruder left behind is a different question, and neither page answers it.
What the sources state, and what they do not
What the GitHub advisory, the CVE record and the CISA KEV file state and do not state, read 11 November 2026.
| Question | Stated | Not stated |
|---|---|---|
| Exploited? | Advisory and record: observed in the wild against a 2.1.1 deployment, custom backends running reverse shells. KEV listing. SSVC: Exploitation active | Actor, first date, number of victims, whether the same group is behind every case, any indicator |
| Ransomware? | KEV: known ransomware campaign use Unknown | Any ransomware link either way |
| Which versions? | Affected: < 2.2.0. Patched: 2.2.0 | A lower bound; whether any older line is unaffected; whether any release between 2.1.1 and 2.2.0 differs |
| Default install reachable? | The default of force_auth_localhost is False. A same host reverse proxy gives path A with no header | Whether a default install with no proxy and a remote bind is reachable, and how common the proxy layout is. Which proxies other than a same host proxy to a loopback address |
| What the header does | A forged X-GPUStack-Real-IP loopback value can make the source look local | Whether the header was honoured from any peer or only some, and what the 2.2.0 hardening changed in that code |
| Is the fix complete? | Advisory: auto admin path removed, header handling hardened. Commit: auto admin branch deleted | Which commit hardened header handling; any test of the header path; whether other loopback trust exists |
| Cleanup after exposure? | Admin API keys can be minted and last; secrets can be read | Whether the fix revokes keys, how to list keys created in the open window, a detection method, a log line to hunt |
| Other products? | KEV notes: the component could be used by different products | Any named product that embeds GPUStack |
| Exposure? | Nothing | Any count of reachable servers |
Labels that stand in for a control
Localhost. The word suggests the server's own machine, a place only the operator can reach. A reverse proxy on that machine makes every outside request local. The advisory calls this the canonical self hosted shape, so the safer looking layout, a proxy in front of the application, is the layout the flaw fires in. A name that promises locality is not a check on who is asking.
force_auth_localhost. The name reads as a safeguard. Per the advisory it was False by default, and it was the workaround, not the norm. After the fix it is a deprecated option that does nothing, so a configuration file that sets it true no longer changes anything. A reader who has the line in a file should read it as history, not as a control.
Strip the header at the edge. The advisory lists this workaround. It handles path B only. The advisory's own description of path A says no header or credential is required, so a proxy rule that removes X-GPUStack-Real-IP leaves a same host proxy forwarding to a loopback address exactly as before. The advisory pairs it with the first workaround, force_auth_localhost set true, and with keeping the API off untrusted networks.
Added to KEV. The listing is evidence of exploitation. It is not a description of it. The catalogue file carries no actor, count or date of first use, and its forensic triage field is CISA's flag, not a finding about any given server.
Where the pages differ
Differences between the pages read on 11 November 2026, left unresolved.
| Point | One page says | Another says |
|---|---|---|
| Advisory date | Advisory page: published Jul 31, 2026 | CVE record: ID reserved 2026-09-29, published 2026-11-04. The page does not explain a date before the reservation |
| Who reported what | Advisory text: independent reports from several outside parties, and a separate credit for reporting exploitation | Advisory Credits panel: two entries, both labelled Finder, and none for the exploitation report |
| What the fix did to headers | Advisory: client IP and forwarding header handling was hardened | The linked commit page: no change to client_ip_getter and no mention of X-GPUStack-Real-IP |
| How the change is named | Commit: a fix. Advisory: a fix for a critical flaw | Pull request title: a refactor, with no description. Release notes: no mention of it |
| What the KEV note implies | KEV: the component could be used by different products | No page lists one. The note is recurring catalogue wording (18 of 1,779 entries) |
No secondary source is cited. Several news sites will cover a KEV listing; this briefing took every figure from the primary pages and read no news coverage.
What to do, in order
Take this with you
The advisory's own fix and workarounds
- Upgrade to GPUStack 2.2.0 or later.
- Workaround: set force_auth_localhost=true to require authentication even for loopback requests.
- Workaround: make sure the reverse proxy or gateway cannot cause GPUStack to treat external traffic as loopback, and strip any client supplied X-GPUStack-Real-IP header at the edge.
- Workaround: keep the API off untrusted networks, with a VPN or edge authentication.
Take this with you
This site's suggestions (not the advisory's advice)
- Find every GPUStack server and record its version, including copies run for trials, in a lab, or inside another team's cluster. The KEV note says the component could be used by different products, so ask vendors whether any of their products include it.
- Upgrade the server to 2.2.0 or later. If that must wait, use the first workaround, not only the header rule, because the header rule does not cover a same host proxy.
- Find out whether the server port can be reached from outside the cluster network, directly or through a proxy that forwards to a loopback address. Treat a reachable server on a version before 2.2.0 as possibly accessed. CISA marks forensic triage Yes for this entry.
- Before and after the upgrade, list users and API keys. Revoke keys and accounts you cannot match to a person or a service, because the fix as read does not remove keys minted earlier.
- Rotate the worker credentials and the cloud and provider secrets the server holds, if the server was reachable on a version before 2.2.0.
- Review inference backends, custom backends in particular, and the worker hosts that ran them. The advisory says custom backends running reverse shells were the form the exploitation took; it prints no further indicator, so the review is of what you defined, not a list to match.
- Look at proxy and access logs for requests to the from-yaml route and to the admin API from outside the cluster network. The advisory does not name a log line, so this is this site's suggestion, not a detection rule.
Technical details
Everything below is copied from the advisory, the CVE record, the CISA catalogue file or the fix commit page. None of the pages prints an exploit request, a payload, an address, a hash or any other indicator of compromise, and this briefing adds none. The advisory prints the forged header in prose with a loopback value; this briefing quotes the header name and the workaround, not a ready made request.
Identifiers, weakness types, score and versions. A defender uses these to match scanner output and asset inventory, and to file the record.
CVE-2026-102895
GHSA-gj9m-f8ph-pmhr
CWE-287: Improper Authentication
CWE-290: Authentication Bypass by Spoofing
CWE-346: Origin Validation Error
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (base score 9.8, CRITICAL, in the CNA container)
Affected versions: < 2.2.0
Patched versions: 2.2.0
The proxy shape the advisory calls the exposed layout. It is the advisory's description of a same host reverse proxy forwarding to a loopback address. A defender searches proxy configuration for this shape on any host that also runs GPUStack, as a way to find servers where path A applies. It is not a test.
proxy_pass http://127.0.0.1:<port>
The route the advisory ties to code execution. It is admin only, and the unauthenticated caller reaches it through this flaw. A defender searches proxy and access logs for requests to it from outside the cluster network, and reviews the backends it created.
POST /v2/inference-backends/from-yaml
The advisory's workarounds, word for word. The first sets a configuration option, the second is a proxy rule, the third is network placement. After the upgrade the first option is ignored, per the commit.
Set force_auth_localhost=true to require authentication even for loopback requests.
Ensure the reverse proxy / gateway cannot cause GPUStack to treat external traffic as loopback, and strip any client-supplied X-GPUStack-Real-IP header at the edge.
Keep the API off untrusted networks (VPN / edge authentication).
The exploitation statement. One sentence in the advisory, repeated in the CVE record with different wording. It is the only description of the activity any page prints.
This vulnerability has been observed exploited in the wild against a v2.1.1 deployment (attacker-created custom backends running reverse shells).
The code the fix commit deleted. These three lines are the loopback branch in the authentication module, with the diff markers as the commit page shows them. A reviewer uses them to confirm that a build after the commit no longer contains the branch.
@@ -120,9 +120,6 @@ async def get_current_user(
- 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)
The warning the commit adds when the old option is set. After the fix, a configuration that still sets force_auth_localhost logs this message, which is a way to find servers whose files still carry it. The text is Python source as the diff shows it.
+ if cfg.force_auth_localhost is not None:
+ logger.warning(
+ "force_auth_localhost is deprecated and ignored: localhost callers "
+ "are always authenticated. This flag will be removed in a future "
+ "release."
+ )
CISA's catalogue fields and the SSVC entry. The catalogue fields carry the due date; the SSVC entry is CISA's coordinator view in the CVE record. Neither names an actor or a count.
vulnerabilityName: GPUStack Improper Authentication Vulnerability
dateAdded: 2026-11-09
dueDate: 2026-11-12
knownRansomwareCampaignUse: Unknown
forensicTriage: Yes
cwes: CWE-287, CWE-290, CWE-346
catalogVersion: 2026.11.10
Exploitation: active
Automatable: yes
Technical Impact: total
(role CISA Coordinator, timestamp 2026-11-05T00:00:00+00:00, version 2.0.3)
The question the pages leave open
The fix was public on the main branch 174 days before CISA listed the flaw, the release notes never mention it, and the fix as read does not remove administrator API keys that were minted while the path was open. If a GPUStack server was reachable through a same host proxy on any version before 2.2.0, what in your logs could show that no one used it?
Key facts
Sources
- PrimaryGitHub security advisory GHSA-gj9m-f8ph-pmhr on the gpustack/gpustack repository, page dated Jul 31, 2026: impact, root cause, patches, workarounds, credits, severity. Read in full on 11 November 2026. GPUStack is open source and the advisory is the project's ownGPUStack (GitHub)accessed 2026-11-11
- PrimaryCVE record CVE-2026-102895, CNA GitHub_M, published 2026-11-04, updated 2026-11-10: description, CVSS 3.1, CWEs, affected range, and the CISA ADP SSVC and KEV entries. Fetched and compared with the lead's saved copy: identicalCVE Program (MITRE CVE Services)accessed 2026-11-11
- PrimaryKnown Exploited Vulnerabilities catalogue, release 2026.11.10 (1,779 entries), entry for CVE-2026-102895: dates, forensic triage, notes, required action. Read as saved by the lead on 11 November 2026CISAaccessed 2026-11-11
- PrimaryRelease page for v2.2.0, released 18 Jun 2026 15:54 UTC: read in full; the notes do not mention the flawGPUStack (GitHub)accessed 2026-11-11
- PrimaryPull request 5346, refactor: remove localhost auto-admin bypass, merged 19 May 2026: three commit titles, no descriptionGPUStack (GitHub)accessed 2026-11-11
- PrimaryCommit 53f42a6, fix: remove localhost auto-admin bypass, 19 May 2026: five files changed; the deleted loopback branch and the deprecated optionGPUStack (GitHub)accessed 2026-11-11


