From 91f44f3de90e2da07cab2ea9f9d465240d4393ba Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 21 May 2026 03:48:17 +0000 Subject: [PATCH] fix(jwt,mcp): clarify issuers fallthrough + add TTL on mcp permission cache - LiteLLM_JWTAuth.issuers docs now state explicitly that unlisted issuers fall back to the global JWT_AUDIENCE/JWT_ISSUER path; the field is additive routing, not an allow-list. Matches actual control flow in handle_jwt.auth_jwt and the regression tests asserting backwards compatibility with the global JWKS path. - MCPRequestHandler._get_{org,agent}_object_permission now pass ttl=DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL on async_set_cache, mirroring the auth_checks.py pattern so the cache TTL is explicit on both DualCache layers. --- .../mcp_server/auth/user_api_key_auth_mcp.py | 11 +++++++++-- litellm/proxy/_types.py | 9 ++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py index 5992d3e86de..34c7fe783a1 100644 --- a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py +++ b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py @@ -7,6 +7,7 @@ from starlette.requests import Request from starlette.types import Scope from litellm._logging import verbose_logger +from litellm.constants import DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL from litellm.proxy._types import ( LiteLLM_TeamTable, ProxyException, @@ -1155,6 +1156,7 @@ class MCPRequestHandler: await user_api_key_cache.async_set_cache( key=cache_key, value=MCPRequestHandler._ORG_NO_PERMISSION_SENTINEL, + ttl=DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL, ) return None @@ -1164,7 +1166,9 @@ class MCPRequestHandler: # get_end_user_object / get_team_object in auth_checks.py). obj_perm = LiteLLM_ObjectPermissionTable(**org_row.object_permission.dict()) await user_api_key_cache.async_set_cache( - key=cache_key, value=obj_perm.dict() + key=cache_key, + value=obj_perm.dict(), + ttl=DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL, ) return obj_perm except Exception as e: @@ -1334,6 +1338,7 @@ class MCPRequestHandler: await user_api_key_cache.async_set_cache( key=cache_key, value=MCPRequestHandler._AGENT_NO_PERMISSION_SENTINEL, + ttl=DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL, ) return None @@ -1341,7 +1346,9 @@ class MCPRequestHandler: **agent_row.object_permission.dict() ) await user_api_key_cache.async_set_cache( - key=cache_key, value=obj_perm.dict() + key=cache_key, + value=obj_perm.dict(), + ttl=DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL, ) return obj_perm except Exception as e: diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 23e786a690e..290324d94b5 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -4397,8 +4397,11 @@ class JWTIssuerConfig(BaseModel): """ Issuer-bound JWT validation configuration. - When configured, LiteLLM selects this issuer by the token's unverified `iss` - claim, then validates the token only against this issuer's JWKS and audience. + When a token's unverified `iss` claim matches an entry in + ``LiteLLM_JWTAuth.issuers``, LiteLLM validates it only against that + issuer's JWKS and audience. Tokens whose `iss` does not match any + configured issuer fall back to the global JWT_AUDIENCE/JWT_ISSUER + validation path; `issuers` is additive routing, not an allow-list. """ issuer: str = Field(description="Exact expected JWT issuer (`iss`) value.") @@ -4550,7 +4553,7 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase): ) issuers: Optional[List[JWTIssuerConfig]] = Field( default=None, - description="Optional issuer-bound JWT validation rules. When set, tokens must match one configured issuer by exact `iss` claim before JWKS lookup.", + description="Optional issuer-bound JWT validation rules. When a token's `iss` matches a configured issuer, validation uses that issuer's JWKS, audience, and claim mappings. Tokens with an unlisted `iss` fall back to the global JWT_AUDIENCE/JWT_ISSUER validation path — this is additive routing, not an allow-list.", ) #########################################################