feat(auth): add scope and wildcard support for JWT routing overrides - #25939
feat(auth): add scope and wildcard support for JWT routing overrides#25939milan-berri wants to merge 3 commits into
Conversation
[Infra] Promote Internal Staging to main
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR extends JWT routing overrides with two new capabilities: an optional Confidence Score: 5/5Safe to merge — no P0/P1 findings; prior security concern about iss space-splitting is properly resolved. All remaining observations are P2 or below. The No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/proxy/auth/user_api_key_auth.py | Refactored _routing_selector_matches_claim to add split_space_delimited flag (only enabled for scope), wildcard matching via fnmatch.fnmatchcase, and moved the None-claim guard earlier; _matches_routing_override now checks scope. |
| litellm/proxy/_types.py | Added scope: Optional[Union[str, List[str]]] = None to JWTRoutingOverride and expanded docstring to document wildcard case-sensitivity and space-delimited scope behaviour. |
| tests/test_litellm/proxy/auth/test_user_api_key_auth.py | Added parametrized unit tests for _routing_selector_matches_claim and _matches_routing_override covering wildcards, scope space-splitting, list semantics, and injection guard; also added three async integration tests for scope match, mismatch, and combined scope+wildcard path. |
| docs/my-website/docs/proxy/token_auth.md | Expanded matching-behavior section to document scope selector, wildcard case-sensitivity, and scope-only space-splitting; added a worked YAML example. |
| docs/my-website/docs/proxy/oauth2.md | Added a one-liner summary of new scope/wildcard capabilities and updated cross-reference link to token_auth.md. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Bearer JWT received] --> B[_should_route_jwt_to_oauth2_override]
B --> C{routing_overrides configured?}
C -- No --> D[Standard JWT auth path]
C -- Yes --> E[get_unverified_claims]
E --> F{_matches_routing_override}
F --> G[_routing_selector_matches_claim
iss — exact / wildcard
no space-split]
G --> H{iss match?}
H -- No --> D
H -- Yes --> I[_routing_selector_matches_claim
client_id — exact / wildcard
no space-split]
I --> J{client_id match or absent?}
J -- No --> D
J -- Yes --> K[_routing_selector_matches_claim
scope — exact / wildcard
split_space_delimited=True NEW]
K --> L{scope match or absent?}
L -- No --> D
L -- Yes --> M[_routing_selector_matches_claim
aud — exact / wildcard
no space-split]
M --> N{aud match or absent?}
N -- No --> D
N -- Yes --> O[Route to OAuth2 introspection]
style K fill:#d4edda,stroke:#28a745
style O fill:#cce5ff,stroke:#004085
Reviews (3): Last reviewed commit: "docs(proxy): document JWT routing_overri..." | Re-trigger Greptile
…ctors Support optional scope matching and shell-style wildcard selectors (*, ?) for JWT OAuth2 routing overrides. Space-delimited tokenization applies only to the scope claim; iss/aud/client_id keep full-string matching on unverified claims. Document case-sensitive wildcard semantics. Add parametrized and integration tests for matcher, override composition, and routing behavior. Made-with: Cursor
760324c to
e5a3e9c
Compare
Align token_auth and oauth2 docs with scope selector, case-sensitive wildcards, and scope-only space-delimited claim handling. Made-with: Cursor
Live proxy test matrix (
|
| Piece | Value |
|---|---|
| Endpoint | POST {BASE}/v1/chat/completions (e.g. BASE=http://127.0.0.1:4051) |
| OAuth2 proof | Mock writes bearer token to /tmp/litellm_introspection_hits.log (one line = one introspection call) |
| Pass (expect OAuth2) | oauth_hits ≥ 1 and http_status = 200 |
| Fail (no OAuth2 routing) | oauth_hits = 0 (typically 401 on JWT path) |
Config rules (see YAML below)
- Rule A:
iss+scope: App:LiteLLM+client_id: *MID_LITELLM→ OAuth2 - Rule B: same
iss/scope+client_id: machine-??→ OAuth2
Matrix (token claims → expected)
| # | Test name | Token iss |
Token scope |
Token client_id |
Expected OAuth2 route | Expected HTTP* |
|---|---|---|---|---|---|---|
| 1 | match_star_client_id |
matrix-live.example.com |
App:LiteLLM |
MID_LITELLM |
Yes (Rule A: * match) |
200 |
| 2 | match_scope_space_delimited |
matrix-live.example.com |
openid App:LiteLLM |
BATCH_MID_LITELLM |
Yes (scope split + Rule A) | 200 |
| 3 | match_question_client_id |
matrix-live.example.com |
App:LiteLLM |
machine-01 |
Yes (Rule B: ??) |
200 |
| 4 | fail_wrong_scope |
matrix-live.example.com |
App:Other |
MID_LITELLM |
No | 401 |
| 5 | fail_wrong_client_for_both_rules |
matrix-live.example.com |
App:LiteLLM |
REDIS_LITELLM |
No | 401 |
| 6 | fail_iss_space_no_split_full_string |
matrix-live.example.com attacker.test |
App:LiteLLM |
MID_LITELLM |
No (iss not split; no match) | 401 |
| 7 | fail_question_client_too_long |
matrix-live.example.com |
App:LiteLLM |
machine-001 |
No (?? ≠ three digits) |
401 |
| 8 | fail_case_sensitive_star |
matrix-live.example.com |
App:LiteLLM |
mid_litellm |
No (*MID_LITELLM is case-sensitive) |
401 |
Example config
Proxy fragment (config_scope_wildcard_live_matrix.yaml in the repo):
model_list:
- model_name: gpt-4o-mini
litellm_params:
model: gpt-4o-mini
api_key: os.environ/OPENAI_API_KEY
general_settings:
master_key: sk-123
enable_jwt_auth: true
enable_oauth2_auth: false
litellm_jwtauth:
user_id_jwt_field: "sub"
admin_allowed_routes:
- openai_routes
- info_routes
- management_routes
- spend_tracking_routes
- global_spend_tracking_routes
routing_overrides:
- iss: "matrix-live.example.com"
scope: "App:LiteLLM"
client_id: "*MID_LITELLM"
path: "oauth2"
- iss: "matrix-live.example.com"
scope: "App:LiteLLM"
client_id: "machine-??"
path: "oauth2"|
re-opened it here: #26325 so CI run |
…25939) Extend routing_overrides with: - optional `scope` selector (same optional semantics as `client_id`) - shell-style wildcard matching (`*`, `?`) via fnmatchcase on all selectors - space-delimited scope tokenization, gated to the `scope` claim only to avoid iss/client_id injection risk Refactor _routing_selector_matches_claim to handle list claims, wildcards, and scope-only space splitting. _matches_routing_override now checks iss, optional client_id, optional scope, optional aud. Made-with: Cursor
…ides Backfills the `BerriAI/litellm-docs` site with the changes that originally shipped under `docs/my-website/` in BerriAI/litellm#25939 / #26325. After the docs source was migrated to this repo, those edits could no longer be carried in the code PR and were dropped on rebase. - proxy/token_auth.md: expand "Matching behavior" with AND semantics, the new optional `scope` selector, list/string forms, shell-style wildcards (`*`, `?`, case-sensitive), and the scope-only space-split rule (iss/aud/client_id are never split on spaces). Adds a worked example combining `scope` with a wildcard `client_id`. - proxy/oauth2.md: cross-reference the new wildcard/scope behavior and point readers to token_auth.md for full details. Code change is in BerriAI/litellm#26325 (litellm_internal_staging).
…ides (#31) Backfills the `BerriAI/litellm-docs` site with the changes that originally shipped under `docs/my-website/` in BerriAI/litellm#25939 / #26325. After the docs source was migrated to this repo, those edits could no longer be carried in the code PR and were dropped on rebase. - proxy/token_auth.md: expand "Matching behavior" with AND semantics, the new optional `scope` selector, list/string forms, shell-style wildcards (`*`, `?`, case-sensitive), and the scope-only space-split rule (iss/aud/client_id are never split on spaces). Adds a worked example combining `scope` with a wildcard `client_id`. - proxy/oauth2.md: cross-reference the new wildcard/scope behavior and point readers to token_auth.md for full details. Code change is in BerriAI/litellm#26325 (litellm_internal_staging).
* docs: add LLM-as-a-Judge guardrail guide with screenshots New guardrail type that uses an LLM to score responses against weighted criteria. Includes UI walkthrough, YAML config examples, blocked/passed response examples, and configuration reference. * docs(llm-judge): replace placeholder screenshots with real spend logs UI screenshots * docs(llm-judge): use real spend logs UI screenshots for blocked/passed guardrail views * docs(blog): make /blog responsive (#51) * docs(blog): make /blog responsive - Add mobile styles to swizzled BlogListPage (hero, marquee, posts, pagination). - Fix horizontal overflow caused by the marquee's white-space: nowrap propagating width up the flex chain. Break it with min-width: 0 on .page and #__docusaurus > *, plus defensive overflow-x: clip. - Respect prefers-reduced-motion (stop marquee animation). * Remove global overflow prevention styles Removed global styles to prevent horizontal overflow on mobile. * docs(proxy): add Grafana Cloud Pyroscope user and API token configuration options (#52) * docs(auth): document scope and wildcard support for JWT routing overrides (#31) Backfills the `BerriAI/litellm-docs` site with the changes that originally shipped under `docs/my-website/` in BerriAI/litellm#25939 / #26325. After the docs source was migrated to this repo, those edits could no longer be carried in the code PR and were dropped on rebase. - proxy/token_auth.md: expand "Matching behavior" with AND semantics, the new optional `scope` selector, list/string forms, shell-style wildcards (`*`, `?`, case-sensitive), and the scope-only space-split rule (iss/aud/client_id are never split on spaces). Adds a worked example combining `scope` with a wildcard `client_id`. - proxy/oauth2.md: cross-reference the new wildcard/scope behavior and point readers to token_auth.md for full details. Code change is in BerriAI/litellm#26325 (litellm_internal_staging). * docs(mcp,a2a): code-verified auth reference fixes + overview page (#156) (#184) * docs(mcp,a2a): code-verified auth reference fixes + overview page (#156) * docs(mcp): complete auth_type table, OAuth config reference, RBAC intersection model, hub-vs-public-internet distinction * docs(a2a): document x-litellm-api-key, trace-id enforcement, sub-agent propagation, agent access groups and full intersection model * docs(bedrock_agentcore): add LiteLLM A2A Gateway section — fixes broken anchor from a2a.md, documents dual JWT/SigV4 auth modes and full credential chain * docs: add AuthN/AuthZ overview page side-by-siding MCP and A2A gateways * docs(fixup): corrections from code-review pass — verified against current LiteLLM source * make changes --------- Co-authored-by: michelligabriele <gabriele.michelli@icloud.com> --------- Co-authored-by: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Co-authored-by: harish-berri <harish@berri.ai> Co-authored-by: milan-berri <milan@berri.ai> Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com> Co-authored-by: michelligabriele <gabriele.michelli@icloud.com>
* docs(blog): make /blog responsive (#51) * docs(blog): make /blog responsive - Add mobile styles to swizzled BlogListPage (hero, marquee, posts, pagination). - Fix horizontal overflow caused by the marquee's white-space: nowrap propagating width up the flex chain. Break it with min-width: 0 on .page and #__docusaurus > *, plus defensive overflow-x: clip. - Respect prefers-reduced-motion (stop marquee animation). * Remove global overflow prevention styles Removed global styles to prevent horizontal overflow on mobile. * docs(proxy): add Grafana Cloud Pyroscope user and API token configuration options (#52) * docs(auth): document scope and wildcard support for JWT routing overrides (#31) Backfills the `BerriAI/litellm-docs` site with the changes that originally shipped under `docs/my-website/` in BerriAI/litellm#25939 / #26325. After the docs source was migrated to this repo, those edits could no longer be carried in the code PR and were dropped on rebase. - proxy/token_auth.md: expand "Matching behavior" with AND semantics, the new optional `scope` selector, list/string forms, shell-style wildcards (`*`, `?`, case-sensitive), and the scope-only space-split rule (iss/aud/client_id are never split on spaces). Adds a worked example combining `scope` with a wildcard `client_id`. - proxy/oauth2.md: cross-reference the new wildcard/scope behavior and point readers to token_auth.md for full details. Code change is in BerriAI/litellm#26325 (litellm_internal_staging). * docs(mcp,a2a): code-verified auth reference fixes + overview page (#156) (#184) * docs(mcp,a2a): code-verified auth reference fixes + overview page (#156) * docs(mcp): complete auth_type table, OAuth config reference, RBAC intersection model, hub-vs-public-internet distinction * docs(a2a): document x-litellm-api-key, trace-id enforcement, sub-agent propagation, agent access groups and full intersection model * docs(bedrock_agentcore): add LiteLLM A2A Gateway section — fixes broken anchor from a2a.md, documents dual JWT/SigV4 auth modes and full credential chain * docs: add AuthN/AuthZ overview page side-by-siding MCP and A2A gateways * docs(fixup): corrections from code-review pass — verified against current LiteLLM source * make changes --------- Co-authored-by: michelligabriele <gabriele.michelli@icloud.com> * Update Claude Code compatibility matrix (#175) litellm_version: v1.83.14-stable claude_code_version: 2.1.126 generated_at: 2026-05-20T06:09:45Z Co-authored-by: litellm-compat-matrix-bot <litellm-bot@berri.ai> * docs(mcp): pass guardrails via extra_body in OpenAI SDK example (#188) The OpenAI Python SDK rejects guardrails as a top-level argument; use extra_body to send LiteLLM-specific params to the proxy. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(release_notes): add v1.84.1 and v1.85.1 patch release notes (#191) Patch releases on top of v1.84.0 and v1.85.0, each shipping the same three PRs: Gemini 3.5 Flash day-0 support (#28268), a Vertex AI tool-calling fix for Gemini 3.5+ HTTP 400 errors (#28324), and a cross-pod spend-counter seeding fix (#27854). Adds release_notes/v1.84.1/ and release_notes/v1.85.1/ pages and updates the release_notes overview (Latest Release block + table). * docs: replace slow Inkeep search with offline @easyops-cn/docusaurus-search-local Inkeep search was reported as slow and exhibited focus / Cmd+K bugs (cursor in the wrong place, page preventing repeated searches). Swap the navbar SearchBar over to @easyops-cn/docusaurus-search-local, which builds a static lunr index at build time and renders results instantly on the client. - Add @easyops-cn/docusaurus-search-local theme with both docs and release_notes routes indexed. - Keep stop words and stems (technical docs frequently search short tokens) and enable highlight-on-target-page. - Drop the SearchBar config from @inkeep/cxkit-docusaurus so it only provides the floating Ask AI chat button (still useful for AI Q&A). Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com> --------- Co-authored-by: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Co-authored-by: harish-berri <harish@berri.ai> Co-authored-by: milan-berri <milan@berri.ai> Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com> Co-authored-by: michelligabriele <gabriele.michelli@icloud.com> Co-authored-by: agent-shin <279878236+agent-shin@users.noreply.github.com> Co-authored-by: litellm-compat-matrix-bot <litellm-bot@berri.ai> Co-authored-by: Sameer Kankute <sameer@berri.ai> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: yuneng-jiang <yuneng@berri.ai> Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Enhancement request for JWT
routing_overridesto:scopeselector matching (same optional behavior asclient_id)issstill requiredPre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewCI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🆕 New Feature
✅ Test
Changes
scopetoJWTRoutingOverrideschema:scope: Optional[Union[str, List[str]]] = Nonefnmatch(*and?)scope)scope:_matches_routing_override(...)now checksiss, optionalclient_id, optionalscope, optionalaudtests/test_litellm/proxy/auth/test_user_api_key_auth.py: