Skip to content

refactor: consolidate route auth for UI and API tokens#25473

Open
ryan-crabbe-berri wants to merge 1 commit intomainfrom
litellm_auth_rbac_cleanup
Open

refactor: consolidate route auth for UI and API tokens#25473
ryan-crabbe-berri wants to merge 1 commit intomainfrom
litellm_auth_rbac_cleanup

Conversation

@ryan-crabbe-berri
Copy link
Copy Markdown
Collaborator

Summary

  • Unify UI and API token authorization through the shared RBAC path
  • Backfill missing routes in role-based route lists so dashboard continues to work for all roles

Test plan

  • Unit tests pass (test_user_api_key_auth.py, test_jwt.py)
  • Manual verification with admin, internal_user, and internal_user_viewer tokens

Unify UI and API token authorization through the shared RBAC path
and backfill missing routes in role-based route lists.
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 10, 2026 4:38am

Request Review

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Apr 10, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_auth_rbac_cleanup (26e99f2) with main (9e6d2d2)

Open in CodSpeed

"ui" if token_team is not None and token_team == "litellm-dashboard" else "api"
)
_is_route_allowed = _is_allowed_route(
_is_route_allowed = _is_api_route_allowed(
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 10, 2026

Greptile Summary

This PR consolidates UI-token and API-token authorization by routing all tokens through the shared RBAC path, replacing the old role-agnostic _is_ui_route / _is_allowed_route pair with a unified _is_api_route_allowed. Routes that previously lived only in ui_routes (e.g. /health/services, /sso/get/ui_settings, /spend/logs/ui, /global/spend/all_tag_names, global activity endpoints) are backfilled into the appropriate role-based lists so the dashboard continues to work for all roles.

  • P1 — backward-incompatible removal: LiteLLMRoutes.ui_routes is deleted from the enum. Any JWT auth deployment with admin_allowed_routes: [\"ui_routes\"] (previously a tested configuration) will silently lose all route access because _allowed_routes_check falls through to exact-string matching on the literal "ui_routes" path, which never matches.

Confidence Score: 4/5

Safe to merge if the team confirms no deployments use admin_allowed_routes: ["ui_routes"] in JWT auth config, or if ui_routes is retained as a backward-compatible alias.

One P1 finding: deleting LiteLLMRoutes.ui_routes is a backward-incompatible enum removal that silently breaks any JWT auth config referencing the old name. All other changes are correct reformatting, security improvements, and route backfills. Score is 4 (not 3) because the affected config path is an advanced/enterprise feature unlikely to be widespread, but the silent failure mode warrants a flag.

litellm/proxy/_types.py — removal of the ui_routes enum member; and tests/proxy_unit_tests/test_jwt.py — the parametrize change that removes coverage of the old config value.

Important Files Changed

Filename Overview
litellm/proxy/_types.py Removes ui_routes enum member and backfills routes into RBAC lists; ui_routes deletion silently breaks JWT auth configs that reference it by name.
litellm/proxy/auth/auth_checks.py Consolidates _is_allowed_route/_is_ui_route into a single _is_api_route_allowed that always applies RBAC; logic is correct and a security improvement.
tests/proxy_unit_tests/test_jwt.py Mostly Black formatting; parametrize change from ["ui_routes"] to ["info_routes"] removes test coverage of the now-deleted config value.
tests/proxy_unit_tests/test_user_api_key_auth.py Replaces _is_ui_route test with a more comprehensive _is_api_route_allowed parametrize covering RBAC roles; coverage is improved for all roles.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming Request] --> B{Token type?}
    B -- "Before PR: team_id == litellm-dashboard" --> C[_is_ui_route check\nagainst ui_routes list\nrole-agnostic]
    B -- "Before PR: API token" --> D[_is_api_route_allowed\nRBAC-based]
    C -- "route in ui_routes" --> PASS1[✅ Allowed]
    C -- "route not in ui_routes" --> D
    B -- "After PR: ALL tokens" --> E[_is_api_route_allowed\nUnified RBAC path]
    E --> F{_is_user_proxy_admin?}
    F -- "Yes" --> PASS2[✅ Allowed]
    F -- "No" --> G[non_proxy_admin_allowed_routes_check]
    G --> H{Role?}
    H -- "INTERNAL_USER" --> I[internal_user_routes\n+ spend_tracking_routes\n+ global_spend_tracking_routes]
    H -- "INTERNAL_USER_VIEW_ONLY" --> J[internal_user_view_only_routes]
    H -- "PROXY_ADMIN_VIEW_ONLY" --> K[admin_viewer_routes]
    H -- "Org Admin" --> L[org_admin_allowed_routes]
    I --> PASS3[✅ Allowed if route matches]
    J --> PASS3
    K --> PASS3
    L --> PASS3
Loading

Reviews (1): Last reviewed commit: "refactor: consolidate route auth for UI ..." | Re-trigger Greptile

Comment on lines 610 to 623
internal_user_routes = (
[
"/global/spend/tags",
"/global/spend/keys",
"/global/spend/models",
"/global/spend/provider",
"/global/spend/end_users",
"/global/activity",
"/global/activity/model",
"/global/activity/cache_hits",
"/v1/models/{model_id}",
"/models/{model_id}",
"/guardrails/list",
"/v2/guardrails/list",
]
+ spend_tracking_routes
+ global_spend_tracking_routes
+ key_management_routes
)
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.

P1 ui_routes removal breaks existing JWT auth configurations

Deleting LiteLLMRoutes.ui_routes from the enum is backward-incompatible for any JWT auth configuration that lists "ui_routes" in admin_allowed_routes (or team_allowed_routes). That was a supported config — the test in test_jwt.py previously exercised ["ui_routes"] as a valid admin_allowed_routes value.

After this deletion, _allowed_routes_check finds that "ui_routes" is not in LiteLLMRoutes.__members__, so it falls through to the elif allowed_route == user_route branch and treats "ui_routes" as a literal route path. That never matches a real request path, so all routes those admins previously accessed via this config are silently blocked.

If you need to deprecate ui_routes, consider keeping the enum member as a thin alias over info_routes (or whatever the closest equivalent is) rather than deleting it outright, so existing configs continue to work.

Rule Used: What: avoid backwards-incompatible changes without... (source)



@pytest.mark.parametrize("admin_allowed_routes", [None, ["ui_routes"]])
@pytest.mark.parametrize("admin_allowed_routes", [None, ["info_routes"]])
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.

P2 Test change removes coverage of the breaking config case

Swapping ["ui_routes"] for ["info_routes"] in this parametrize is necessary since the enum member is gone, but it also means no test exercises what happens when an existing deployment has admin_allowed_routes: ["ui_routes"] in its JWT auth config — which, per the analysis above, now silently grants access to nothing.

If the intent is to treat the removal as an intentional breaking change, this is fine; but if the goal is a transparent migration, a test (or at minimum a changelog entry) documenting the old config value is now inert would be valuable.

Rule Used: What: Flag any modifications to existing tests and... (source)

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.

2 participants