Skip to content

GPUStack gave admin to anything that looked like localhost. behind a same-host proxy, everything does (CVE-2026-102895, on KEV)

CVE-2026-102895: GPUStack Loopback Admin Bypass Added to CISA KEV

GPUStack gave admin to anything that looked like localhost. behind a same-host proxy, everything does (CVE-2026-102895, on KEV)

somebody built a GPU cluster manager, put it behind nginx on the same box, and asked one question to decide who gets admin: “did this request come from localhost?” behind a same-host reverse proxy, every request comes from localhost. that is how CVE-2026-102895 works. CISA added it to the Known Exploited Vulnerabilities catalog this morning, November 9, with a federal due date of November 12. the patch has been out since June. please clap.

Lab-only / defensive use. this post covers version checks, reverse-proxy config review, and access-log hunting. the downloadable kit reads files you export yourself, sends no requests to any GPUStack server, and contains no exploit code. we are not linking proof-of-concept repos, and you should not go shopping for them either.
The short version. GPUStack before 2.2.0 promoted any request whose source looked like loopback to the built-in administrator when force_auth_localhost was false, which was the default. a same-host reverse proxy, or a client-supplied X-GPUStack-Real-IP: 127.0.0.1 header, made remote requests look like loopback. the GitHub advisory says exploitation was observed in the wild against a v2.1.1 deployment, with attacker-created custom backends running reverse shells. fixed in 2.2.0. a second bug in the same product, CVE-2026-102894 (SAML callback), is fixed in 2.2.2. upgrade to 2.2.2 or later and recreate the container.

what GPUStack is, for the three people who did not deploy it last quarter

per its own CVE description, GPUStack is “an open-source GPU cluster manager for AI model serving and GPU instance provisioning.” translation: it sits in front of your expensive GPUs, holds your worker credentials and cloud-provider secrets, and decides which models run where. it is exactly the kind of box every AI team stood up in a hurry, behind a reverse proxy, with a sticky note that says “we will harden it after the demo.”

the demo was in June. the hardening is now a CISA deadline.

the bug, exactly as the maintainers describe it

from the GitHub security advisory GHSA-gj9m-f8ph-pmhr: in GPUStack before v2.2.0, get_current_user in gpustack/api/auth.py “promotes any request whose source is considered loopback to the built-in administrator account when force_auth_localhost is False (the default).” the source is decided by client_ip_getter, which the advisory says can be made to report 127.0.0.1 without any credentials in two ways:

  • same-host reverse proxy. the “canonical self-hosted shape,” GPUStack behind a proxy on the same host with proxy_pass http://127.0.0.1:<port>. every remote request arrives with TCP peer 127.0.0.1, so the auto-admin branch fires. no header, no credential.
  • the X-GPUStack-Real-IP header. a forged value of 127.0.0.1 can cause the request to be treated as loopback.

the advisory’s own root-cause line is the whole lesson in one sentence: “Trusting the network source of a request (socket peer, or a client-controllable header) as an authentication signal.” we have been writing that sentence in pentest reports since before some of these GPUs were taped out.

what an unauthenticated admin gets

the advisory and CVE record list it plainly, and we are not going to embellish it:

  • enumerate and modify users, models, workers, and configuration
  • mint persistent admin API keys with scope ["*"], which the advisory says stay valid “from any source IP thereafter”
  • read worker credentials and cloud or provider secrets
  • combined with the admin-only POST /v2/inference-backends/from-yaml, code execution on worker hosts

read that API key bullet twice. patching closes the door. it does not take back the keys somebody already copied while the door was open. more on that in the hunt section.

exploited, per the people who wrote the code

the advisory states: “This vulnerability has been observed exploited in the wild against a v2.1.1 deployment (attacker-created custom backends running reverse shells).” the credit section says active in-the-wild exploitation was reported by Javier Sixto (ulex.fr). the advisory does not give a date for that observation, a victim count, or attacker infrastructure. so neither do we. no invented IP lists today.

CISA’s catalog entry, added November 9, 2026, describes the bug as allowing “unauthenticated administrative access, including user and model changes, persistent administrator API-key creation, disclosure of worker and cloud-provider secrets, and code execution through POST /v2/inference-backends/from-yaml.” Known ransomware use: Unknown. Due date for federal civilian agencies: November 12, 2026. the entry also flags forensic triage as required and points to BOD 26-04 guidance, which is CISA’s polite way of saying “patch it, then go look at what already happened.”

CISA’s ADP enrichment on the CVE record, timestamped the same morning, scores it Exploitation: active, Automatable: yes, Technical Impact: total.

scores, and who assigned them

  • CVSS 3.1 9.8 Critical, AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, assigned by GitHub as the CNA (that is the maintainer advisory score carried into the CVE record).
  • NVD listed the record as “Received” with only the GitHub score attached when we pulled it on November 9. no separate NVD assessment yet.
  • CWEs in the KEV entry: CWE-287 (improper authentication), CWE-290 (authentication bypass by spoofing), CWE-346 (origin validation error).

the timeline, which is the actual scandal

  • May 19, 2026: fix commit 53f42a6, titled “fix: remove localhost auto-admin bypass,” deletes the loopback branch from get_current_user.
  • June 18, 2026: GPUStack v2.2.0 released.
  • July 24, 2026: v2.2.2 released with two more security fixes, including the SAML callback issue now tracked as CVE-2026-102894.
  • July 31, 2026: GHSA-gj9m-f8ph-pmhr published.
  • November 4, 2026: CVE-2026-102895 published by GitHub’s CNA.
  • November 9, 2026: added to CISA KEV, due November 12.

so the fix existed for roughly five months before it had a CVE number, and the exploitation the advisory describes hit a 2.1.1 box, a release that predates 2.2.0. this is not a zero-day story. this is a “nobody upgrades the AI box because the AI box is a pet” story. vulnerability scanners that key on CVE IDs could not flag this until November 4. anyone who reads release notes could have patched in June. we know which group is larger.

the sibling: CVE-2026-102894, SAML that did not check SAML

same product, same week of CVE paperwork. per its CVE record, from 0.7.1 until 2.2.2 the /auth/saml/callback endpoint decoded SAML XML, used its NameID and attributes to resolve or provision a user, and minted a gpustack_session JWT without calling the signature and condition verification step. “Any network client could submit an identity for an existing user, including an administrator, or provision a new non-administrator account, even when SAML was not configured.” CVSS 3.1 9.1 from GitHub. CISA’s ADP enrichment on November 6 recorded Exploitation: none. it is not on KEV. it is still a reason your floor is 2.2.2, not 2.2.0.

the 2.2.2 release notes also mention a missing-authorization issue on several cluster and resource endpoints affecting 2.2.0 to 2.2.1. one more reason not to stop at 2.2.0.

are you affected

you are in scope for CVE-2026-102895 if all of these are true:

  • GPUStack server version is below 2.2.0
  • force_auth_localhost is false or unset (the default on those versions)
  • the API is reachable through a same-host reverse proxy that forwards to 127.0.0.1, or the server will accept a client-supplied X-GPUStack-Real-IP loopback value

in 2.2.0 the loopback branch is gone and the commit turns force_auth_localhost into a deprecated no-op that logs a warning. if your config still sets it after upgrading, that warning is fine. it is the sound of a bad idea leaving.

step 1: inventory check

feed the kit a CSV of your servers with version, the force_auth_localhost setting, whether a same-host proxy fronts it, and whether that proxy strips the header. it compares against both fixed floors and refuses to guess when the version is missing.

Kali terminal running gpustack-102895-audit.py inventory against a sample lab CSV of six GPUStack servers, flagging 2.1.1 and 2.0.3 for CVE-2026-102895 and CVE-2026-102894, flagging 2.2.0 for the SAML bug, marking 2.2.2 and 2.3.0 OK, and marking a row with no version as REVIEW.
Step 1: lab inventory, run offline. the 2.0.3 box has the workaround set and still gets flagged, because a workaround is not a version. the row with no version gets REVIEW, not a free pass.

step 2: proxy config and access-log hunt

the nginx mode looks for proxy_pass to 127.0.0.1 or localhost and checks whether the same file sets proxy_set_header X-GPUStack-Real-IP "";. in nginx, an empty value means the header is not passed upstream. note what that does and does not do: it closes the forged-header path. it does nothing about the TCP peer, which is still loopback. on a pre-2.2.0 server behind a same-host proxy, only the upgrade or force_auth_localhost=true closes that path. the script says so instead of printing a cheerful OK.

the access-log mode lists POST /v2/inference-backends/from-yaml, the admin-only endpoint the advisory names as the code execution route. legitimate admins use it too. a hit is a question, not a verdict.

Kali terminal running the nginx mode against two lab configs, marking a hardened config PART because the TCP peer is still loopback and warning on a config with loopback proxy_pass and no header strip, then running accesslog mode and listing two POST /v2/inference-backends/from-yaml requests from documentation address 203.0.113.77.
Step 2: the header strip is a partial fix and the tool says so. the two from-yaml hits come from a documentation address in a sample log, not live telemetry.

step 3: interim hardening, dry run first

if you cannot upgrade this afternoon, the advisory lists three workarounds: set force_auth_localhost=true, make sure the proxy cannot cause external traffic to look like loopback and strip client-supplied X-GPUStack-Real-IP at the edge, and keep the API off untrusted networks. the mitigation script prints what it would change and only edits with --apply, after taking backups.

Kali terminal running gpustack-102895-mitigate.sh in dry-run mode against a lab nginx site and GPUStack config, proposing a proxy_set_header X-GPUStack-Real-IP empty line and force_auth_localhost true, and warning that this does not revoke admin API keys already minted.
Step 3: dry run. the last line is the one that matters: hardening does not revoke keys or delete backends somebody already created.

what to look at after you patch

the advisory’s own impact list is your post-patch checklist. none of this is speculative:

  • admin API keys. list them. anything with full scope that nobody can explain gets revoked. the advisory says these keys remain valid from any source address after creation.
  • inference backends. list custom backends and compare against change tickets. the observed attacks created custom backends that ran reverse shells.
  • users and models. new admins, changed models, and anything you did not do.
  • worker and cloud-provider secrets. if the server was exposed and pre-2.2.0, rotate them. the advisory says an unauthenticated admin could read them. assume they were read.
  • worker hosts. code execution happened on workers in the reported case, not just the server. check their outbound connections and process trees.

and recreate the container after pulling the new image. pulling an image does not replace the process that is already running. yes, people forget this. yes, often.

also on today’s KEV list, because CISA does not do singles

the November 9 KEV update added three entries, all due November 12:

  • CVE-2026-102895, GPUStack, this post.
  • CVE-2026-116779, SignalWire FreeSWITCH, an inbound SIP MESSAGE that can select the api chat proto and run FreeSWITCH API commands. fixed in 1.11.3. we wrote the hunt and hardening walkthrough separately in today’s Red Team How-To.
  • CVE-2026-123158, OpenSSH “SSHole” on Linux with musl and no PAM. yesterday we wrote that it was not on KEV as of our November 8 check. as of November 9 it is. our SSHole breakdown and checkers still apply. SANS ISC’s Johannes Ullrich reported on November 8 that ISC honeypots saw the first all-“A” long-password attempts on November 5, with 232 that day and 713 on November 6, while noting the honeypots only keep the first 255 characters of a password.

the common thread: two of the three had vendor fixes for months before the CVE paperwork caught up. the attackers were not waiting for the paperwork.

download the Kali defensive kit

gpustack-102895-defensive-kit.zip contains gpustack-102895-audit.py (inventory, nginx, and access-log modes), gpustack-102895-mitigate.sh (dry run by default, backups before any edit), a README, and the synthetic lab files used in the screenshots. fixed versions are called out in every output: 2.2.0 for CVE-2026-102895, 2.2.2 for CVE-2026-102894. it makes no network connections and contains no exploit code. read it before you run it. that is advice for every script on the internet, including ours.

if your AI platform team owns GPU boxes that security has never seen in an inventory, that is the gap attackers just used. Book a slot with Dark Edge Consulting.

sources