Skip to content

feat(auth): list and revoke lite login CLI sessions from the Admin UI - #36846

Closed
yassin-berriai wants to merge 3 commits into
litellm_internal_stagingfrom
litellm_lit5518_cli_session_visibility
Closed

feat(auth): list and revoke lite login CLI sessions from the Admin UI#36846
yassin-berriai wants to merge 3 commits into
litellm_internal_stagingfrom
litellm_lit5518_cli_session_visibility

Conversation

@yassin-berriai

@yassin-berriai yassin-berriai commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • lite login credentials are invisible to admins
  • No way to cut off a departing user's CLI access
  • Waiting for expiry was the only lever

How it solves it:

  • Every CLI login registers a revocable session record
  • Auth refuses a revoked session within one cache interval
  • New CLI Sessions page lists and revokes them

User Flow

Before: an admin who needs to cut off a contractor's CLI access has no action to take, so the credential keeps working until it expires

  1. The contractor runs lite login against https://litellm-domain and gets a working credential
  2. They send POST https://litellm-domain/v1/chat/completions with it and get a 200 with a completion
  3. The admin opens https://litellm-domain/ui/?page=api-keys looking for the credential and finds nothing: the Keys page lists zero keys
  4. The admin has no page anywhere in the dashboard that lists CLI logins, and no revoke action to take
  5. The contractor sends the same POST https://litellm-domain/v1/chat/completions and still gets a 200, and will keep getting one for the rest of the 24 hour lifetime

After: the admin sees the login in the dashboard and revokes it, and the contractor's next request is refused

  1. The contractor runs lite login against https://litellm-domain and gets a working credential
  2. They send POST https://litellm-domain/v1/chat/completions with it and get a 200 with a completion
  3. The admin opens https://litellm-domain/ui/?page=cli-sessions and sees a row for that login showing the contractor's user id, when it was issued, when it expires, and an Active badge
  4. The admin clicks Revoke on that row and confirms in the dialog; the row flips to a Revoked badge
  5. The contractor sends the same POST https://litellm-domain/v1/chat/completions and gets a 401 reading "Authentication Error - This CLI session was revoked. Run lite login to start a new one."

Another user with their own lite login credential is unaffected: only the revoked login stops working, and the contractor can no longer reach any route on the proxy with it, including the management routes their role would otherwise allow

Relevant issues

Linear ticket

Resolves LIT-5518

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Both runs hit a live proxy on a real Postgres against the real Anthropic API. lite login was driven for real, generic OIDC pointed at a throwaway local IdP, and the browser half of the device flow was followed with curl the way a browser would.

Before, at a4ab511

### 1. the credential lite login just minted works against a real provider
model  : claude-sonnet-5
content: lit5518 before-leg live
usage  : 36 tokens

### 2. an admin looks for an operator surface listing CLI sessions
GET /cli/session/list -> http=404
{"detail":"Not Found"}

### 3. the session is not on the Keys page either
virtual keys on the proxy   : 0
any of them a CLI session   : False

### 4. nothing server side records the session, so nothing can be revoked
rows in LiteLLM_CLISessionTable: 0

### 5. the only lever is expiry: the credential still works
POST /v1/chat/completions -> http=200

After, at 4cbcc2b (re-run against this exact head)

The proof above stayed valid through the previous rebase, but a further rebase onto current litellm_internal_staging and a follow-up commit that dropped two now-banned sys.path.insert test hacks (4cbcc2b082) landed since it was captured, so this section is a fresh run against the current tip rather than an identity argument. Same live proxy, same real Postgres, same real Anthropic API.

### 1. the contractor's credential works against a real provider before revoke
POST /v1/chat/completions {"model": "claude-sonnet-5", ...} -> http=200
content: lit5518 final proof
usage  : {"prompt_tokens": 22, "completion_tokens": 10, "total_tokens": 32}

### 2. the session is visible to an operator, with user, issue time and expiry
GET /cli/session/list -> http=200
{
    "sessions": [
        {
            "session_id": "9f0af7b4ae47076788661b136b548d2733a896fd22b43ccf02ebcbef77463acf",
            "user_id": "lit5518-contractor",
            "team_id": null,
            "created_at": "2026-08-22T19:07:24.147000Z",
            "expires_at": "2026-08-23T19:07:24.147000Z",
            "revoked_at": null,
            "revoked_by": null
        }
    ],
    "total_count": 1
}

### 3. the admin revokes it (this is the request the dashboard Revoke button sends)
POST /cli/session/9f0af7b4.../revoke -> http=200
revoked_at: 2026-08-22T19:07:51.713000Z

### 4. the same credential is refused right after, before it can spend another dollar
POST /v1/chat/completions -> http=401
message: Authentication Error - This CLI session was revoked. Run `lite login` to start a new one.

### 5. revoking a session id that does not exist is a 404, not a silent no-op
POST /cli/session/does-not-exist/revoke -> http=404
{"detail":"CLI session not found: does-not-exist"}

UI

The dashboard half is a new CLI Sessions entry in the AI Gateway section of the left nav, admin only, at ?page=cli-sessions. Chrome automation was unavailable on the capture box; see "Manual UI verification steps" below for the exact clicks.

Manual UI verification steps

  1. Run the proxy: python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --reload --use_v2_migration_resolver
  2. Run the dashboard dev server: npm run dev in ui/litellm-dashboard
  3. Complete a lite login from a terminal pointed at the running proxy, so a real CLI session gets registered
  4. Open http://localhost:4000/ui/?page=cli-sessions as a proxy admin, and expect a row for that login showing the user id, issued time, expiry, and an Active badge
  5. Click Revoke on that row and confirm in the dialog; expect the row to flip to a Revoked badge
  6. Run any command with the CLI (e.g. litellm models list) using the same login and expect it to fail, telling you to run lite login again
  7. Log in as a role other than proxy admin / proxy admin viewer (e.g. an internal user) and confirm the CLI Sessions entry is absent from the left nav under AI Gateway

Type

🆕 New Feature

Caveats (if any)

  • Sessions minted before this change keep working until expiry
  • Revocation is per session; there is no revoke-all-for-user yet
  • Convergence on other replicas is bounded by DEFAULT_IN_MEMORY_TTL
  • Expired session rows are hidden from the list but never pruned

Review notes

Both Greptile findings were real and both were taken. The first, that a valid CLI credential could fail whenever its revocation cache missed during a Prisma outage, was the sharper one: this credential previously authenticated with no database read at all on the admin path, so a lookup that raises is a new failure mode. The lookup now routes any failure through PrismaDBExceptionHandler.handle_db_exception, which is the proxy's existing seam for exactly this decision, so an operator who set allow_requests_on_db_unavailable keeps serving CLI sessions during an outage and one who did not gets the same failure every other DB-backed auth read already gives, rather than a private policy invented here. The second, that the page offered a Revoke control to roles the backend refuses, took two passes and the review was right to reject the first one. effectiveSessionRole normalizes proxy_admin_viewer to Admin so read parity works, which means a role-only check cannot tell a revoker from a reader; the gate now also requires !isViewOnly, the signal the rest of the dashboard already uses for exactly this. The page, the list query and the nav entry separately moved off all_admin_roles, which carries org admin, whom the proxy refuses on tenant-wide reads.

Two findings were not taken. The docs one asks for the revocation guidance to move to litellm-docs; the sentence I changed is a pre-existing in-repo claim that this PR makes false, quoted verbatim in the ticket as evidence, so correcting it in place is the smaller and more honest change and relocating the surrounding product docs is its own piece of work. The unbounded-registry one is real but not a new exposure: a row is only written by a completed SSO login, the same act that already mints a credential, and rows are bounded by that same authenticated flow. A retention sweep for expired sessions is worth doing and is listed as a caveat rather than smuggled into this PR.

The revocation lookup is cached in the shared auth DualCache rather than read per request, so a CLI session costs one indexed read per cache interval per replica instead of one per request. The pod that serves the revoke publishes the revoked state into that cache directly, which is why the capture shows the first 401 arriving 0.15s after the revoke rather than at the TTL boundary; a replica that did not serve the revoke picks it up from Redis once its own entry lapses, which is the DEFAULT_IN_MEMORY_TTL bound quoted above. There is no second cache to invalidate: a CLI credential never enters the key cache, because the only write-back on that path fires after a database load and a self-contained blob never gets one.

Registration is deliberately fail-closed and revocation deliberately fail-open on a missing row. A login that cannot be recorded is never handed to the CLI, since it could never be revoked; a session with no row at all predates this change and keeps authenticating until it expires, so upgrading does not sign every CLI user out.

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@yassin-berriai
yassin-berriai requested a review from a team August 13, 2026 23:13
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds persistent, individually revocable sessions for classic lite login credentials and an admin dashboard surface for listing and revoking them.

  • Registers hashed CLI session identifiers during login and checks cached revocation state during authentication.
  • Adds administrator list and revoke endpoints backed by a new Prisma table.
  • Adds the CLI Sessions dashboard page, role-aware controls, navigation, generated API types, and tests.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
litellm/proxy/auth/cli_session_registry.py Implements session registration, cached revocation checks, pagination, and idempotent revocation.
litellm/proxy/auth/user_api_key_auth.py Integrates CLI revocation enforcement into self-contained session-token authentication.
litellm/proxy/management_endpoints/cli_session_endpoints.py Adds administrator-authorized session listing and revocation endpoints.
litellm/proxy/management_endpoints/ui_sso.py Registers newly minted classic CLI sessions before releasing credentials to the polling client.
litellm-proxy-extras/litellm_proxy_extras/migrations/20260813210000_add_cli_session_table/migration.sql Creates the CLI session registry with lookup and expiration indexes.
ui/litellm-dashboard/src/components/CLISessionsPage/CLISessionsPage.tsx Adds the admin session-management view with view-only-aware revoke controls.
ui/litellm-dashboard/src/components/CLISessionsPage/CLISessionsTable/CLISessionsTableColumns.tsx Renders session identity, lifetime, status, and authorized revoke actions.
ui/litellm-dashboard/src/app/(dashboard)/hooks/cliSessions/useCLISessions.ts Adds role-gated list and revoke queries with cache refresh after mutation.

Reviews (6): Last reviewed commit: "test(cli-sessions): drop the cwd-relativ..." | Re-trigger Greptile

Comment thread litellm/proxy/auth/cli_session_registry.py Outdated
Comment thread litellm/proxy/client/README.md Outdated
Comment thread litellm/proxy/auth/cli_session_registry.py Outdated
@veria-ai

veria-ai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

PR overview

This pull request adds Admin UI support for listing and revoking LiteLLM CLI login sessions, backed by changes to the CLI session registry and authentication flow.

One issue has already been addressed, but a fail-open path remains for revoked proxy-admin CLI sessions when the database client is unavailable. An attacker who already possesses a revoked admin credential could continue authenticating during that condition, so revocation is not reliably enforced until the configured database-unavailability policy is applied.

Open issues (1)

Fixed/addressed: 1 · PR risk: 4/10

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.03846% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/auth/cli_session_registry.py 98.30% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_lit5518_cli_session_visibility (4cbcc2b) with litellm_internal_staging (490c9f9)

Open in CodSpeed

@yassin-berriai
yassin-berriai force-pushed the litellm_lit5518_cli_session_visibility branch from 75e9fe1 to 5fa1184 Compare August 14, 2026 00:05
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review at 5fa1184. Both findings are taken: DB failures now follow allow_requests_on_db_unavailable, and Revoke renders for proxy admin only.

Comment thread ui/litellm-dashboard/src/components/CLISessionsPage/CLISessionsPage.tsx Outdated
@yassin-berriai
yassin-berriai force-pushed the litellm_lit5518_cli_session_visibility branch from 5fa1184 to d8a4cad Compare August 14, 2026 00:17
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review at d8a4cad. The viewer gate now uses isViewOnly, since effectiveSessionRole normalizes proxy_admin_viewer to Admin. Other findings answered in the description.

@yassin-berriai
yassin-berriai force-pushed the litellm_lit5518_cli_session_visibility branch from d8a4cad to 6dd0fa5 Compare August 14, 2026 00:52
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review at 6dd0fa5. Rebased onto staging after LIT-4757 landed; only the docstring overlapped and test fixtures were updated.

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review at b09975b. Fixed the CI lint failure: a type-discipline budget regression, suppressed via the existing prisma-payload convention.

`lite login` mints a self-contained encrypted UserAPIKeyAuth blob rather than a
virtual key, so nothing about the session was stored server side and the auth
path authenticated it by decrypting it. An admin had no way to see who held a
live CLI credential and no way to cut one off before it expired.

Every login now registers a row in LiteLLM_CLISessionTable, keyed by the sha256
of the session token, and the auth path refuses a session whose row is revoked.
The lookup is cached in the shared auth DualCache for DEFAULT_IN_MEMORY_TTL, so
a session costs one read per cache interval per replica and a revoke converges
on every replica within that same interval. A CLI Sessions page in the dashboard
lists the sessions with their user, issue time and expiry, and revokes one.

Registration is fail-closed: a session that cannot be recorded is never handed
to the CLI, since it could never be revoked. Sessions minted before this change
have no row and keep working until they expire. A lookup that cannot reach the
database follows the proxy-wide allow_requests_on_db_unavailable posture rather
than inventing its own, so the credential's availability during an outage is
whatever the operator already chose for every other DB-backed auth read.
@yassin-berriai
yassin-berriai force-pushed the litellm_lit5518_cli_session_visibility branch from b09975b to 1acd0a2 Compare August 22, 2026 18:32
The two new CLI-session test files were added before #37802 swept the
suite's sys.path.insert(0, os.path.abspath(...)) calls, so rebasing onto
staging put them over the TQ003 test-quality-budget ceiling and failed lint.
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review at 4cbcc2b. Fixed the rebase-induced lint failure: two new test files still had the cwd-relative sys.path.insert hack that #37802 swept from the rest of the suite, which pushed TQ003 over budget.

Every lite login inserted a row that was never removed, so the registry grew without bound as logins accumulated. Registration now deletes rows whose expires_at has passed, bounding the table by the number of live sessions.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
operator who opted into serving during an outage keeps serving CLI sessions,
and one who did not gets the same failure every other DB-backed auth read gives.
"""
if prisma_client is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: Revoked admin sessions fail open without a database client

An attacker holding a revoked proxy-admin CLI credential can continue authenticating when prisma_client is unavailable because this branch reports the session as unrevoked, and admin session tokens return before the auth path's standard no-database check. Apply the configured allow_requests_on_db_unavailable policy here and fail closed when that setting is disabled.

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Ideally, lite login should not be provisioning a new identity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants