CVE-2026-93355: Account Takeover in LiteLLM

LiteLLM Account takeover (2)
Share

One request carrying an unverified email claim grants immediate access as the victim and permanently mutates account

TL;DR

What: OX Research found a way to use a legitimately signed login token to authenticate as another existing LiteLLM user, including an admin. LiteLLM is an open-source gateway used by companies to manage access to OpenAI, Anthropic, and other LLM APIs. It holds API keys, tracks spend, and controls who can access what.


How: LiteLLM’s JWT authentication silently falls back to an unverified email claim when a direct identity match misses. This lets an attacker log in as any victim, including a proxy_admin, and permanently rebind that victim’s SSO account to the attacker’s own token with a single request. No victim interaction or prior credentials are required.

Vulnerability Details

  • CVE ID: CVE-2026-93355
  • CWE: CWE-290 (Authentication Bypass by Spoofing)
  • CVSS v3.1 Base Score: 8.8 (High)
  • CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Affected Products

Affected ProductAffected VersionsStatusVersionRegistry
LiteLLMConfirmed through 1.100.1 (Published 2026-09-10)UnpatchedAnyPyPI

Overview

LiteLLM authenticates JWT-bearing requests by first trying to match the token to an existing user by `user_id` or `sso_user_id`. When that lookup misses – the normal case for anyone’s very first request through a given identity claim – the proxy falls back to matching on the `email` claim carried in the token instead. That fallback trusts the claim outright: nothing in the code checks `email_verified`, and nothing confirms the presented identity actually owns the mailbox it claims. 

If the fallback finds an existing account with that email, it returns that account as the authenticated caller, and separately queues a background write that permanently overwrites the matched account’s `sso_user_id` with the attacker’s own token subject – so every subsequent request with the same forged token is now a direct, fast-path match, no fallback required.

No access to the victim’s real credentials, no interaction from the victim, and no elevated starting privilege were required – only a validly-signed JWT, from a trusted issuer, carrying the victim’s email address in the configured claim. That is a routine condition, not an exotic one: many IdP and self-service signup flows populate an `email` claim before – or entirely without – verifying it, precisely the case our proof of concept models.

In the proof of concept we built and disclosed, we created a victim `proxy_admin` account, then authenticated as an attacker-controlled JWT subject whose only connection to the victim was a matching, unverified email claim. The request succeeded with full admin context on the first call, and a follow-up query showed the victim account’s `sso_user_id` permanently rebound to the attacker’s subject – turning a one-time forged claim into standing access that survives independently of it.

This issue is not yet fixed. We reported it on May 18, 2026, followed up on July 27 with no response, and it remains present, unchanged, in the current release, LiteLLM 1.100.1.

About LiteLLM

LiteLLM is an open-source AI gateway – a Rust-core proxy with a Python SDK – that lets organizations call over 100 LLM APIs (OpenAI, Anthropic, Azure, Bedrock, Vertex AI, and more) through one unified, OpenAI-compatible interface, with centralized cost tracking, guardrails, load balancing, and logging. It has nearly 59,000 stars on GitHub and is widely deployed as the single chokepoint through which an organization’s AI usage flows.

Because LiteLLM sits in that position, the proxy itself holds what every team behind it would otherwise keep separate: the organization’s real upstream API keys for every configured LLM provider, every user’s spend and usage history, and the virtual keys and budgets that govern who can call what. An account on the proxy – especially a `proxy_admin` account – is not access to one more service; it is access to the credentials and controls for everything the organization does with AI through it. A vulnerability that lets an attacker become an existing account, admin included, without ever holding that account’s real credentials, compromises exactly the layer meant to keep those credentials safe.

Responsible Disclosure Timeline

  • May 18, 2026 – Vulnerability reported
  • Jul 27, 2026 – Follow-up requesting a status update; no response received
  • Sep 14, 2026 – No vendor response after ~120 days; proceeding with public disclosure

Technical Details – CVE-2026-93355

LiteLLM’s JWT identity resolution is a two-stage lookup. The first stage is a direct match on `user_id`/`sso_user_id`; on a miss, `get_user_object` calls the fallback, `_get_fuzzy_user_object`.

image

Two things happen here that matter. First, the email lookup (line 2309) is a plain case-insensitive equality match – there is no check anywhere in this function, or anywhere upstream of it, for whether that email is verified. Second, on a hit, line 2314-2320 doesn’t just authenticate the request – it schedules a background write that reassigns the matched row’s `sso_user_id` to the attacker’s own token subject. The rebind is fired-and-forgotten (`asyncio.create_task`, not awaited), so it lands regardless of whatever the rest of the request does. One forged claim, presented once, becomes a permanent identity change.

The `email` claim that reaches this function is never verified upstream either. `get_user_email` reads whatever value sits at the configured claim path and returns it as-is.

image

`email_verified` does not appear anywhere in `handle_jwt.py`. The one adjacent control that does exist is an optional email-domain allowlist, checked in `get_user_info`.

image

This is off by default (`is_enforced_email_domain()` requires an admin to configure `user_allowed_email_domain`), so on a stock deployment `valid_user_email` stays `None` and nothing downstream ever rejects the request on that basis.

Attack scenario

The proof of concept needs a victim account and one forged token:

  1. A `proxy_admin` account already exists in LiteLLM’s user table with `user_email=admin@target.com` – unremarkable; this describes any existing SSO user who has ever logged in.
  2. An attacker, holding some account at an IdP the proxy trusts, obtains a validly-signed JWT for their own subject, with an `email` claim set to `admin@target.com` and `email_verified=false` – a condition several common IdP flows will produce without any misconfiguration, since many providers issue tokens before, or without ever confirming, email ownership.
  3. The attacker sends that token as authentication on an ordinary API call, e.g. `POST /chat/completions`.
  4. `get_user_object` misses on `sso_user_id` (first time this attacker subject has been seen), falls back to `_get_fuzzy_user_object`, matches the victim row by email, and returns it. The request runs with the victim’s full `proxy_admin` context.
  5. In the background, the victim’s `sso_user_id` is overwritten with the attacker’s subject. A follow-up lookup on the user confirms the rebind.
  6. The same forged token now authenticates directly via the fast path – no fallback, no email match needed – and reaches admin-only endpoints: `key/list`, `user/list`, and everything else a `proxy_admin` can do, including every provisioned upstream API key behind the proxy.

Temporary Mitigations

Because an official patch is not yet available, operators should implement temporary controls immediately to mitigate exposure.

Recommendations for LiteLLM users:

1. Require a verified email at the trust boundary. The single control that directly neutralizes the disclosed attack is the one LiteLLM is missing: ensure `email_verified` is `true` before any token reaches the proxy. Enforce it at the IdP, or strip/rewrite the `email` claim upstream (at a reverse proxy or IdP claim-mapping layer) for any token that does not carry `email_verified: true`. This closes the exact path the exploit relies on – an unverified email claim being trusted for account resolution.

2. Pin identity to a stable, unique subject claim – not email. Configure `user_id_jwt_field` / `sso_user_id` so every trusted issuer supplies a stable, unique subject identifier from the first login onward, so the email fallback path is reached as rarely as possible. Layer an email-domain allowlist (`user_allowed_email_domain`) on top as defense-in-depth – while understanding it only blocks foreign-domain spoofing, not a same-domain attacker claiming a colleague’s address.

3. Don’t leave privileged accounts pre-provisioned-but-unlinked. The attack needs an existing account whose email an attacker can put into a signed token, reached before that account has a real identity bound to it. Minimize that window, especially for `proxy_admin` and other privileged accounts: rather than creating them by email ahead of time and waiting for a first SSO login to link them, provision them only once the account’s stable subject identity is known and can be bound directly, so there is no email-only account sitting available to be claimed. The fewer privileged, email-only, not-yet-linked accounts exist, the less this flaw has to act on.

How OX Can Help

Use OX Cloud’s runtime workload visibility to locate all LiteLLM deployments across your cloud accounts, including ones deployed outside standard processes.

1. Identify exposure — OX Cloud flags which of these workloads are internet-reachable via permissive inbound rules (e.g., 0.0.0.0/0 on ports 4000/8000).

2. Restrict access — In your cloud console, remove public inbound rules on LiteLLM ports and allow only internal VPC/VNet CIDRs or VPN gateway IPs.

3. Stay restricted — OX Cloud continuously monitors posture, alerting you if new or existing  LiteLLM instances become internet-exposed.

Note: this limits external exposure only; users inside your network can still exploit the flaw until LiteLLM ships a fix. Pair it with strict IdP token policies (issue email claims only after verification; disable self-service signup on the trusted IdP app).

For further information or assistance with this mitigation, please reach out to your OX Customer Support Representative.

Tags:

OX cloud 1

Active AI Defense. Complete Cloud Visibility.

Your agents hold identities you never issued. See what they touch, in real time.

Meet OX Cloud