diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index 11f6810aee1..2deb7d40004 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -1048,6 +1048,8 @@ class JWTAuthManager: ) return individual_team_id, team_object except HTTPException as e: + if e.status_code != 404: + raise # Claim doesn't map to a known team — defer to fallback. verbose_proxy_logger.debug( "JWT team_id claim '%s' did not resolve to a team: %s", diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index 7179aa510dd..8cbe48cf9cb 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -2839,9 +2839,10 @@ async def test_find_team_with_model_access_unresolved_group_claim_returns_none( @pytest.mark.asyncio async def test_find_and_validate_specific_team_id_non_http_exception_still_propagates(): - """Regression guard: only HTTPException (the "team doesn't exist in db" - 404 raised by get_team_object) is softened. Other errors — e.g. "No DB - Connected" — must still propagate so operator-side problems are loud.""" + """Regression guard: only the 404 HTTPException raised by + `get_team_object` ("team doesn't exist in db") is softened. Other + errors — e.g. "No DB Connected" — must still propagate so operator-side + problems are loud.""" jwt_handler = JWTHandler() jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth(team_id_jwt_field="team_id") token = {"sub": "user-1", "team_id": "some-claim-team"} @@ -2863,6 +2864,39 @@ async def test_find_and_validate_specific_team_id_non_http_exception_still_propa ) +@pytest.mark.asyncio +async def test_find_and_validate_specific_team_id_non_404_http_exception_propagates(): + """Regression guard: only 404 HTTPException is softened. If + `get_team_object` is ever updated to raise a different HTTP status code + (e.g. 403 for a blocked team), that error must still propagate rather + than silently fall through to the single-team DB fallback.""" + from fastapi import HTTPException + + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth(team_id_jwt_field="team_id") + token = {"sub": "user-1", "team_id": "some-claim-team"} + + for status_code in (400, 403, 500): + with patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + ) as mock_get_team: + mock_get_team.side_effect = HTTPException( + status_code=status_code, detail="non-404 failure" + ) + + with pytest.raises(HTTPException) as exc_info: + await JWTAuthManager.find_and_validate_specific_team_id( + jwt_handler=jwt_handler, + jwt_valid_token=token, + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + ) + assert exc_info.value.status_code == status_code + + @pytest.mark.asyncio async def test_find_team_with_model_access_enforce_team_based_access_still_raises(): """Regression guard: when no group claims are present and