{"id":"GHSA-gj9m-f8ph-pmhr","summary":"GPUStack: Authentication bypass via trusted loopback source (reverse-proxy TCP peer or X-GPUStack-Real-IP header) grants unauthenticated admin","details":"### Impact\n\nIn 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 derived by `client_ip_getter`, which can be made to report `127.0.0.1` without any credentials in two ways:\n\n1. **Same-host reverse proxy (TCP peer).** In the canonical self-hosted shape —\n   GPUStack behind a same-host reverse proxy with `proxy_pass\n   http://127.0.0.1:\u003cport\u003e` — every remote request arrives at GPUStack with TCP\n   peer `127.0.0.1`, so the loopback auto-admin branch fires. No header or\n   credential is required. Verified end-to-end against HEAD `6bee5883`.\n2. **`X-GPUStack-Real-IP` header.** A forged `X-GPUStack-Real-IP: 127.0.0.1`\n   header can cause the request source to be treated as loopback.\n\nEither path yields **full, unauthenticated administrative access** over the network: enumerate and modify users, models, workers, and configuration; mint persistent admin API keys (scope `[\"*\"]`, valid from any source IP thereafter); read worker credentials and cloud/provider secrets. Combined with admin-only functionality such as `POST /v2/inference-backends/from yaml`, this leads to code execution on worker hosts.\n\nThis vulnerability has been observed exploited in the wild against a v2.1.1 deployment (attacker-created custom backends running reverse shells).\n\n### Root cause\n\nTrusting the network source of a request (socket peer, or a client-controllable header) as an authentication signal. When GPUStack is fronted by a same-host reverse proxy, the socket peer is always loopback, so the \"localhost is trusted\" assumption no longer holds.\n\n### Patches\n\nFixed in `gpustack` `2.2.0`: the loopback auto-admin path was removed and client-IP / forwarding-header handling was hardened. Upgrade to `2.2.0` or later.\n\n### Workarounds\n\n- Set `force_auth_localhost=true` to require authentication even for loopback\n  requests.\n- Ensure the reverse proxy / gateway cannot cause GPUStack to treat external\n  traffic as loopback, and strip any client-supplied `X-GPUStack-Real-IP`\n  header at the edge.\n- Keep the API off untrusted networks (VPN / edge authentication).\n\n### Remediation (in the fix)\n\nDo not derive authentication from the request's network source. Remove implicit loopback-based admin, and only accept a real client-IP header from an authenticated/trusted gateway (never from arbitrary clients).\n\n### Credit\n\nIndependently reported by Nikko Enggaliano; Wei Yunzhou; and Rui Yang & Haoyu Wang (Johns Hopkins University). Active in the-wild exploitation was reported by Javier Sixto (ulex.fr).","aliases":["CVE-2026-102895"],"modified":"2026-11-09T22:30:04.096651997Z","published":"2026-11-09T22:15:27Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-11-09T22:15:27Z","nvd_published_at":"2026-11-04T22:17:44Z","cwe_ids":["CWE-287","CWE-290","CWE-346"],"severity":"CRITICAL"},"references":[{"type":"WEB","url":"https://github.com/gpustack/gpustack/security/advisories/GHSA-gj9m-f8ph-pmhr"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-102895"},{"type":"WEB","url":"https://github.com/gpustack/gpustack/pull/5346"},{"type":"WEB","url":"https://github.com/gpustack/gpustack/commit/53f42a6a553542974396d16a4ff6b12bcbedb48d"},{"type":"PACKAGE","url":"https://github.com/gpustack/gpustack"},{"type":"WEB","url":"https://github.com/gpustack/gpustack/releases/tag/v2.2.0"},{"type":"WEB","url":"https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-102895"}],"affected":[{"package":{"name":"gpustack","ecosystem":"PyPI","purl":"pkg:pypi/gpustack"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.2.0"}]}],"versions":["0.0.0","0.1.0","0.1.0rc1","0.1.0rc2","0.1.1","0.1.1rc1","0.1.2","0.2.0","0.2.1","0.3.0","0.3.1","0.3.2","0.3.2rc1","0.4.0","0.4.0rc2","0.4.1","0.4.1rc1","0.5.0","0.5.0rc1","0.5.0rc2","0.5.1","0.5.1rc1","0.6.0","0.6.0rc1","0.6.0rc2","0.6.1","0.6.1rc1","0.6.2","0.6.2rc1","0.6.2rc2","0.7.0","0.7.0rc1","0.7.0rc2","0.7.1","0.7.1rc1","0.7.1rc2","2.0.0","2.0.0rc1","2.0.0rc2","2.0.1","2.0.1rc2","2.0.2","2.0.2rc1","2.0.2rc2","2.0.3","2.0.3rc1","2.1.0","2.1.0rc1","2.1.0rc2","2.1.1","2.1.1rc1","2.1.2","2.1.2rc2","2.2.0rc1","2.2.0rc2","2.2.0rc3","2.2.0rc4"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/11/GHSA-gj9m-f8ph-pmhr/GHSA-gj9m-f8ph-pmhr.json"}}],"schema_version":"1.9.2","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}]}