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.
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-IPheader. a forged value of127.0.0.1can 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 fromget_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_localhostis 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-IPloopback 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.

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.

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.

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
apichat 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
- GPUStack, GHSA-gj9m-f8ph-pmhr (published July 31, 2026)
- CVE-2026-102895 record (GitHub CNA, published November 4, 2026; updated November 9, 2026 with CISA ADP enrichment)
- CVE-2026-102894 record (GitHub CNA, published November 4, 2026)
- GPUStack fix commit 53f42a6 (May 19, 2026) and releases v2.2.0 (June 18, 2026) and v2.2.2 (July 24, 2026)
- CISA Known Exploited Vulnerabilities catalog, catalogVersion 2026.11.09, checked November 9, 2026
- NVD CVE-2026-102895 (status Received, GitHub score only, when checked November 9, 2026)
- SANS ISC, “Scans for Sshole Vulnerability (CVE-2026-123158)” (Johannes Ullrich, November 8, 2026)
