fix(jwt): surface DB-fallback membership lookup failures at warning level

A transient get_team_membership failure on the DB team fallback path is
recoverable: the team is still resolved and the request proceeds, just
without per-team membership budget enforcement for that request. Logging
that at debug hid a silent budget-enforcement gap from operators, so it now
logs at warning and states that enforcement was skipped. Behavior is
otherwise unchanged: the resolved team is returned with a None membership
rather than failing the request, covered by
test_resolve_db_team_fallback_survives_membership_lookup_error.
This commit is contained in:
mateo-berri 2026-06-26 00:42:32 +00:00
parent 9911998e62
commit 4a2b0c0696
No known key found for this signature in database
2 changed files with 53 additions and 2 deletions

View file

@ -2038,8 +2038,9 @@ class JWTAuthManager:
),
)
except Exception:
verbose_proxy_logger.debug(
"JWT DB team fallback: membership lookup failed for team_id=%s",
verbose_proxy_logger.warning(
"JWT DB team fallback: membership lookup failed for team_id=%s; "
"proceeding without per-team membership budget enforcement",
candidate_team_id,
exc_info=True,
)

View file

@ -4827,6 +4827,56 @@ async def test_resolve_db_team_fallback_loads_team_membership():
assert team_membership.budget_id == "budget_xyz"
@pytest.mark.asyncio
async def test_resolve_db_team_fallback_survives_membership_lookup_error():
"""A transient membership-lookup failure must not deny an otherwise-authorized
request: the resolved team is still returned with a None membership (budget
enforcement degrades gracefully) instead of the error propagating as a 403."""
user_object = LiteLLM_UserTable(
user_id="u_flaky",
user_role=LitellmUserRoles.INTERNAL_USER,
teams=["team_flaky"],
)
async def fake_get_team(team_id, **kwargs):
return LiteLLM_TeamTable(team_id=team_id)
async def boom_get_membership(user_id, team_id, **kwargs):
raise Exception("transient DB error")
with (
patch(
"litellm.proxy.auth.handle_jwt.get_team_object",
new_callable=AsyncMock,
side_effect=fake_get_team,
),
patch(
"litellm.proxy.auth.handle_jwt.get_team_membership",
new_callable=AsyncMock,
side_effect=boom_get_membership,
),
):
(
team_id,
team_object,
team_membership,
) = await JWTAuthManager._resolve_db_team_fallback(
user_object=user_object,
user_id="u_flaky",
requested_model=None,
enforce_team_based_model_access=True,
team_id_upsert=False,
prisma_client=None,
user_api_key_cache=MagicMock(),
parent_otel_span=None,
proxy_logging_obj=MagicMock(),
)
assert team_id == "team_flaky"
assert team_object is not None
assert team_membership is None
@pytest.mark.asyncio
async def test_auth_builder_db_fallback_does_not_validate_rbac_team_against_db_membership():
"""When fallback_to_db_teams is on and the JWT carries an RBAC team role but no