Skip to content

feat(auth): add scope and wildcard support for JWT routing overrides - #25939

Closed
milan-berri wants to merge 3 commits into
BerriAI:litellm_internal_stagingfrom
milan-berri:litellm_jwt_override_scope_wildcard
Closed

feat(auth): add scope and wildcard support for JWT routing overrides#25939
milan-berri wants to merge 3 commits into
BerriAI:litellm_internal_stagingfrom
milan-berri:litellm_jwt_override_scope_wildcard

Conversation

@milan-berri

Copy link
Copy Markdown
Collaborator

Enhancement request for JWT routing_overrides to:

  • support optional scope selector matching (same optional behavior as client_id)
  • support wildcard matching for selectors (not full regex), with iss still required

Pre-Submission checklist

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

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

🆕 New Feature
✅ Test

Changes

  • Added scope to JWTRoutingOverride schema:
    • scope: Optional[Union[str, List[str]]] = None
  • Extended routing override claim matching:
    • wildcard support with fnmatch (* and ?)
    • list-aware selector and claim matching
    • space-delimited string claim handling (notably for scope)
  • Updated override evaluation to include scope:
    • _matches_routing_override(...) now checks iss, optional client_id, optional scope, optional aud
  • Added/refined tests in tests/test_litellm/proxy/auth/test_user_api_key_auth.py:
    • parametrized matcher tests for exact/list/wildcard/scope tokenization semantics
    • parametrized override tests for combined selector behavior (AND semantics)
    • focused async routing tests for OAuth2 path vs JWT fallback behavior
  • Kept route-scope behavior unchanged:
    • JWT override to OAuth2 remains restricted to LLM + info routes (no expansion to management routes)

@vercel

vercel Bot commented Apr 17, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 17, 2026 1:59pm

Request Review

@codecov

codecov Bot commented Apr 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.69258% with 32 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/llms/custom_httpx/llm_http_handler.py 7.14% 26 Missing ⚠️
litellm/proxy/_experimental/mcp_server/server.py 93.02% 3 Missing ⚠️
litellm/llms/anthropic/common_utils.py 92.30% 2 Missing ⚠️
litellm/llms/bedrock/realtime/handler.py 50.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends JWT routing overrides with two new capabilities: an optional scope selector (matching the same optional semantics as client_id) and shell-style wildcard support (*, ?) on all selector fields via fnmatch.fnmatchcase. Space-delimited scope tokenization is correctly gated behind the split_space_delimited flag and is only applied when matching the scope claim, directly addressing the prior security review concern about crafted iss values being split to gain routing matches.

Confidence Score: 5/5

Safe to merge — no P0/P1 findings; prior security concern about iss space-splitting is properly resolved.

All remaining observations are P2 or below. The split_space_delimited gate is correctly wired only for the scope claim, preventing the previously flagged injection vector on iss. Tests are mock-based (no real network calls), parametrized to cover exact, wildcard, list, and scope-splitting semantics, and include the security regression case for space-injection on non-scope claims. Documentation accurately reflects the case-sensitive wildcard semantics and scope-only splitting.

No files require special attention.

Important Files Changed

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
Loading

Reviews (3): Last reviewed commit: "docs(proxy): document JWT routing_overri..." | Re-trigger Greptile

Comment thread litellm/proxy/auth/user_api_key_auth.py Outdated
Comment thread litellm/proxy/auth/user_api_key_auth.py
…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
Align token_auth and oauth2 docs with scope selector, case-sensitive
wildcards, and scope-only space-delimited claim handling.

Made-with: Cursor
@milan-berri

Copy link
Copy Markdown
Collaborator Author

Live proxy test matrix (routing_overrides: scope + * + ?)

Setup under test

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"

@milan-berri

Copy link
Copy Markdown
Collaborator Author

re-opened it here: #26325 so CI run

milan-berri added a commit that referenced this pull request Apr 28, 2026
…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
milan-berri added a commit to milan-berri/litellm-docs that referenced this pull request Apr 28, 2026
…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).
mubashir1osmani pushed a commit to BerriAI/litellm-docs that referenced this pull request May 21, 2026
…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).
mubashir1osmani added a commit to BerriAI/litellm-docs that referenced this pull request May 21, 2026
* 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>
mubashir1osmani added a commit to BerriAI/litellm-docs that referenced this pull request May 21, 2026
* 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>
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