From fc6d51cfc0da35b9bd8776477baecb1fe4d1e8c9 Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Wed, 10 Jun 2026 19:52:15 -0700 Subject: [PATCH] fix(auth_v2): gate OIDC login roles through the provider allowlist The browser OIDC login/callback path accepted IdP-asserted roles straight into the session, so a malicious or misconfigured IdP could assert platform_admin over SSO and have it land in the Principal - the same escalation the bearer token path already closes. The callback now runs the mapped claims through the shared _apply_role_policy with the matched provider config before minting the session, so roles outside allowed_roles are dropped and platform roles require allow_platform_roles. With the defaults (empty allowlist, platform off) no IdP-asserted role survives. --- litellm/proxy/auth_v2/oidc/router.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/auth_v2/oidc/router.py b/litellm/proxy/auth_v2/oidc/router.py index 61d72bcc8e5..df8c8d6d981 100644 --- a/litellm/proxy/auth_v2/oidc/router.py +++ b/litellm/proxy/auth_v2/oidc/router.py @@ -36,7 +36,7 @@ def _mapped_claims(userinfo: Dict[str, Any]) -> Dict[str, Any]: def build_oidc_router(auth: AuthSecurity) -> APIRouter: session = auth.config.session - issuers = {_provider_key(p): p.issuer for p in auth.config.oidc_providers} + providers = {_provider_key(p): p for p in auth.config.oidc_providers} oauth = OAuth() for provider in auth.config.oidc_providers: oauth.register( @@ -118,17 +118,22 @@ def build_oidc_router(auth: AuthSecurity) -> APIRouter: userinfo = await client.parse_id_token(token, nonce=txn.get("nonce")) else: userinfo = await client.userinfo(token=token) + from ..authenticators import _apply_role_policy + info = dict(userinfo) + provider_config = providers[provider] store = cast(ProvisioningStore, auth.resolver) await store.upsert_user(_user_from_userinfo(info)) + claims = _mapped_claims(info) + _apply_role_policy(claims, provider_config) session_id = auth.session_store.create_session( { "method": "oidc", "subject": info.get("sub"), - "issuer": info.get("iss") or issuers.get(provider), - "claims": _mapped_claims(info), + "issuer": info.get("iss") or provider_config.issuer, + "claims": claims, } ) target = safe_relay_state(txn.get("relay"), session.default_redirect_path)