Commit graph

757 commits

Author SHA1 Message Date
Mateo Wang
078a60478f
Merge pull request #41485 from BerriAI/litellm_jwt_token_exchange_grant
feat(proxy): add RFC 8693 token exchange for IdP JWTs on the gateway token endpoint
2026-09-18 21:27:20 -07:00
mateo-berri
50629ff5ca Merge branch 'main' of https://github.com/BerriAI/litellm into litellm_jwt_token_exchange_grant
# Conflicts:
#	tests/test_litellm/proxy/auth/test_auth_checks.py
2026-09-18 17:30:38 -07:00
yassin
2231a3ca43 feat(mcp): give each allowed MCP client an alias and a value
mcp_allowed_clients entries become {alias, value} objects: the value is what the JWT claim or header must equal, the alias is the name the dashboard and logs show. The Network Settings section is renamed Allowed Clients with one alias/value row per client

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 22:29:17 +00:00
yassin
cca7ab8b1b test(mcp): type the REST allowlist test stubs
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 21:50:16 +00:00
yassin
0536fb3062 fix(mcp): fail closed on empty JWT claims and gate the REST tool routes on mcp_allowed_clients
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 21:29:59 +00:00
yassin
9917375d4b Merge remote-tracking branch 'origin/main' into litellm_mcp_client_allowlist
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

# Conflicts:
#	litellm/proxy/_experimental/mcp_server/server.py
2026-09-18 20:46:50 +00:00
yassin
fe8cf02823 feat(mcp): resolve the allowlisted client identity from the JWT claim or an opt-in header instead of clientInfo.name
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 19:27:43 +00:00
yassin
c5919a3c0e Merge remote-tracking branch 'origin/main' into litellm_mcp_client_allowlist
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

# Conflicts:
#	litellm/proxy/_experimental/mcp_server/server.py
#	litellm/proxy/proxy_server.py
2026-09-18 19:01:08 +00:00
yassin
ce48a3fbcc test(mcp): give the new cache and tombstone patches TQ008 reasons and match the keyword eviction call
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 02:49:41 +00:00
yassin
fd834f6f8b fix(mcp): broadcast BYOK and OAuth credential eviction to peer workers and expire admin session tombstones
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 02:10:50 +00:00
yassin
ea37596b88 feat(mcp): let proxy admins force-close live MCP sessions and revoke stored user credentials
Adds an admin-only DELETE /v1/mcp/sessions that terminates stateful MCP gateway sessions on the current worker by session id prefix and/or by the LiteLLM user that opened them, tombstones the terminated ids so a client reusing one gets 404 instead of a silently recreated stateless session, and lets PROXY_ADMIN name a user_id on the BYOK and OAuth credential delete routes. Full and view-only admins can list every user's stored credential metadata for a server (never the secret). The dashboard gains Disconnect controls on the Live Connections tab and a User Credentials tab with Revoke controls, both hidden from read-only admins.

Resolves LIT-8001

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 01:01:26 +00:00
mateo-berri
788e6eb737 merge: origin/main into litellm_jwt_token_exchange_grant 2026-09-17 17:59:23 -07:00
mateo-berri
82d252210e fix(proxy): word the token exchange's 503 by whether the database fault can clear
A permanent database fault (a missing or version-skewed query engine)
in the subject_token check was answered with the same "retry" wording
as a transient outage. The status stays 503 temporarily_unavailable,
the only OAuth error a client reads as the server's fault and what the
mint path already answers to the same fault, but the description now
says retrying will not help until the deployment is repaired, using
PrismaDBExceptionHandler.is_permanent_database_fault the way the mint
path does.
2026-09-17 17:20:17 -07:00
mateo-berri
8e3742a5f3 fix(proxy): answer 503 temporarily_unavailable when the token exchange cannot verify the subject token
Some checks are pending
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run
A subject token JWT auth could not check, because the IdP's JWKS was unreachable with no cached copy or the auth database was down, came back as 400 invalid_request with the same fixed message a bad token gets, so clients re-logged in instead of retrying the way they already do for a mint-time 503. Those checks now answer 503 temporarily_unavailable and log the reason, while real rejections stay 400 invalid_request.
2026-09-17 16:39:02 -07:00
yassin
33531649c3 perf(mcp): count gateway session groups with Counter and pin the oversized initialize peek invariant
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 23:20:47 +00:00
yassin
c230393731 fix(mcp): refuse sessionless and stale-session POSTs that skip initialize while mcp_allowed_clients is set
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 23:18:54 +00:00
yassin
5f6702ee47 fix(mcp): admit an allowlisted initialize that fills the peek cap exactly and type the allowlist test helpers
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 22:22:19 +00:00
yassin
a9ab7392ae feat(mcp): show live gateway sessions by AI client and user
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 22:15:55 +00:00
yassin
c0f335d8ca fix(mcp): cap the body the client allowlist inspects at 64 KiB
With mcp_allowed_clients set the gateway used to read the whole POST body to
find clientInfo.name, so an authenticated client could make the proxy buffer an
arbitrarily large payload. Inspection is now capped at MCP_ALLOWLIST_PEEK_MAX_BYTES
and a sessionless POST that exceeds the cap is rejected with 403 before routing,
while posts on an admitted session stream through unchanged

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 21:46:15 +00:00
yassin
d240a5b6bb test(mcp): import json at module level in the MCP allowlist tests
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 21:04:56 +00:00
yassin
14639bbb5a fix(mcp): read the whole initialize body under allowlist enforcement and surface a stored empty allowlist in the UI
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 20:55:09 +00:00
yassin
f29be6e1ee fix(ui): save the MCP private ranges and client allowlist sequentially
The proxy stores both fields with a whole-row read-modify-write of
general_settings, so two concurrent writes from one save can drop one
of them. Also drops docstrings and suppressions the diff did not need

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 20:05:35 +00:00
yassin
99eeb813c4 Merge remote-tracking branch 'origin/main' into litellm_mcp_client_allowlist 2026-09-17 19:34:42 +00:00
yassin
a705e0396e refactor(mcp): type the allowlist 403 body and replay consumed messages immutably
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 18:55:47 +00:00
yassin
47be6c8aeb feat(mcp): allowlist client applications for MCP gateway access
Adds the mcp_allowed_clients general setting, enforced against the
clientInfo.name each MCP client sends in its initialize request. A client
not on the list, or one that does not identify itself, is rejected with
403 before any stateful session is created. The setting is configurable
from config.yaml and from the Admin UI MCP network settings page

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 18:34:36 +00:00
Joshua Valluru
5b91195406 fix(mcp): retain selected guardrails for virtual REST calls 2026-09-17 10:40:33 -07:00
Joshua Valluru
743684bdbe fix(mcp): preserve request-selected guardrails during tool execution 2026-09-17 09:57:08 -07:00
mateo-berri
d8c3a38a51 Merge remote-tracking branch 'origin/main' into litellm_jwt_token_exchange_grant
# Conflicts:
#	tests/test_litellm/proxy/auth/test_auth_checks.py
2026-09-16 18:17:57 -07:00
mateo-berri
769b47457e fix(proxy): keep the token exchange off gateways that map JWTs to virtual keys 2026-09-16 17:22:27 -07:00
Mateo Wang
9e1eb546e4
Merge pull request #41514 from BerriAI/litellm_mcp_api_key_static_header_slot
fix(mcp): count admin static headers as api_key credential slots
2026-09-16 17:22:08 -07:00
mateo-berri
574ea15b8f fix(mcp): count admin static headers as api_key credential slots
An api_key server whose key lives in static_headers, the documented
shape for upstreams that expect a custom header name, dispatched fine
before the fail-closed check and was rejected as misconfigured after
it. The check now treats every static header the admin configured as a
credential slot for api_key mode, on both the MCP client path and the
OpenAPI tool path, with regression tests at all three layers.
2026-09-16 16:42:20 -07:00
mateo-berri
ce722ab1b3 fix(proxy): evict the member's cached user row on team member add
JWT auth caches the user row before it adds the user to the JWT's team, and admission checks the credential's team against the cached row on whichever worker takes the next request. On a two-worker gateway the credential minted for a newly joined team answered 403 "not in your team memberships" until the management-object TTL ran out, because /team/member_add only evicted the membership spend sentinel. The add now evicts the added members' cached user rows and broadcasts the eviction to the other workers, the way /team/member_delete already did

The mint test now also covers a user SCIM deactivated after the cache last saw them active: the database read refuses the mint while the cached row still says active
2026-09-16 16:31:28 -07:00
joshua-berri
41410e9556
Merge pull request #41364 from BerriAI/litellm_fix_mcp_auth_fail_closed_4501
fix(mcp): fail closed on missing upstream credentials
2026-09-16 23:26:38 +00:00
mateo-berri
167edf2769 fix(proxy): read the database user row only in the credential mint
The token exchange mint keeps reading the user row from the database, since JWT auth caches the user it creates before adding it to the JWT's team and a mint off that cached row refused the first exchange for a new user. Introspection and the refresh revalidation go back to the cache read, so a resource server calling /introspect per request pays no database read.
2026-09-16 15:10:03 -07:00
mateo-berri
3a8ac47e99 fix(proxy): mint the exchange credential off the database user row, not the cache
JWT auth caches the user it creates before it adds that user to the JWT's
team, and adding a team member never evicts the cached user row, so the mint
read a row with no teams and refused the very first token exchange for a
never-seen user as not a member. The loader now reads the row from the
database and leaves the fresh row in the cache for the requests the
credential makes next
2026-09-16 14:12:15 -07:00
mateo-berri
7cb01cf47f fix(proxy): exchange role default, logged rejections, gated grant listing
The exchange refused the very user JWT auth upserts (its row has no
user_role) as "no longer active". The credential now carries the role the
proxy already enforces for that user on every request, internal_user when
the row has none, the same rule _get_user_role applies on the data plane.

A rejected subject_token no longer echoes JWT auth's wording on the public
/token endpoint: the response is a fixed invalid_request and the reason
goes to the proxy log, since that wording can name the JWKS URL or relay
the IdP's reply.

The exchange grant is listed on /register, /.well-known/litellm-cli-auth,
and the aggregate authorization-server metadata only when JWT auth is on,
backed by a database, and licensed, so a client never selects a grant the
gateway would then refuse.
2026-09-16 13:53:10 -07:00
mateo-berri
489a3ecf95 feat(proxy): add RFC 8693 token exchange for IdP JWTs on the gateway token endpoint
A registered gateway DCR client can now POST /token with
grant_type=urn:ietf:params:oauth:grant-type:token-exchange and an IdP
JWT as subject_token. The gateway proves the JWT the way its JWT auth
does, resolves the user and team, and answers with the proxy-API
credential and a refresh token, so a fresh laptop with only an IdP login
gets a gateway key without a browser round trip.

"/token" joins mcp_inference_routes so the default JWT team allowlist
reaches the exchange, and the JWT auth builder accepts any header
mapping so the request headers pass through unchanged.
2026-09-16 13:19:45 -07:00
Joshua Valluru
70ef8b24b6 fix(mcp): reject bare schemes in raw authorization 2026-09-16 10:44:00 -07:00
yassin
5fee1c8710 Merge remote-tracking branch 'origin/main' into litellm_model_access_denied_message
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

# Conflicts:
#	tests/test_litellm/proxy/auth/test_handle_jwt.py
2026-09-16 16:27:18 +00:00
Joshua Valluru
6c517bfc49 fix(mcp): reject scheme-only API key authorization payloads 2026-09-16 09:00:17 -07:00
Joshua Valluru
fdb8e3533b fix(mcp): validate credentials in existing request paths 2026-09-16 08:07:10 -07:00
Joshua Valluru
87190604f6 chore: integrate current main for MCP security compatibility 2026-09-16 07:54:13 -07:00
Joshua Valluru
ee676d59f2 fix(mcp): preserve browser OAuth for unrelated bearer tokens 2026-09-15 23:08:14 -07:00
Joshua Valluru
e035682ed1 refactor(auth): separate JWT identity and OAuth authorization 2026-09-15 22:15:57 -07:00
Joshua Valluru
258176de76 fix(mcp): validate rendered static credential payloads 2026-09-15 20:32:25 -07:00
Joshua Valluru
be506936bd fix(mcp): preserve explicit caller authorization credentials 2026-09-15 19:52:40 -07:00
Joshua Valluru
84e14789d1 fix(mcp): preserve usable alternate header credentials 2026-09-15 19:18:48 -07:00
Joshua Valluru
c7e4160ee6 fix(mcp): enforce OAuth write policy across signed callbacks 2026-09-15 19:15:34 -07:00
Joshua Valluru
97211bc356 fix(mcp): authorize per-user OAuth credential writes 2026-09-15 19:00:47 -07:00
yassin
b085a3c151 fix(mcp): return fixed client message on sampling model access denial
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 01:51:29 +00:00