fix(team): don't log legitimate 403s at error level on /callback endpoints

Greptile flagged that ``disable_team_logging`` and ``get_team_callbacks``
re-wrap any ``HTTPException`` (including the 403 from the access guard)
through a catch-all that logs at ``.error()`` before re-raising — so
every legitimate access-denied response would pollute alerting
dashboards as a "server error".

Add explicit ``except HTTPException: raise`` and
``except ProxyException: raise`` branches before the catch-all (matching
the pattern already used in ``add_team_callbacks``). 4xx now propagates
quietly; only genuinely unexpected exceptions still hit the
error-level log.

Tests assert ``HTTPException`` is now the surfaced shape (instead of
the previous ``ProxyException`` re-wrap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
user 2026-04-29 21:56:04 +00:00
parent 140628063c
commit 578846e57d
No known key found for this signature in database
2 changed files with 21 additions and 25 deletions

View file

@ -251,20 +251,18 @@ async def disable_team_logging(
},
}
except HTTPException:
# Legitimate 4xx (e.g. 403 from the access guard, 404 for an
# unknown team). Re-raise without the error-level log noise that
# the catch-all branch below would produce.
raise
except ProxyException:
raise
except Exception as e:
verbose_proxy_logger.error(
f"litellm.proxy.proxy_server.disable_team_logging(): Exception occurred - {str(e)}"
)
verbose_proxy_logger.debug(traceback.format_exc())
if isinstance(e, HTTPException):
raise ProxyException(
message=getattr(e, "detail", f"Internal Server Error({str(e)})"),
type=ProxyErrorTypes.internal_server_error.value,
param=getattr(e, "param", "None"),
code=getattr(e, "status_code", status.HTTP_500_INTERNAL_SERVER_ERROR),
)
elif isinstance(e, ProxyException):
raise e
raise ProxyException(
message="Internal Server Error, " + str(e),
type=ProxyErrorTypes.internal_server_error.value,
@ -349,6 +347,13 @@ async def get_team_callbacks(
},
}
except HTTPException:
# Legitimate 4xx (e.g. 403 from the access guard) — re-raise
# without the error-level log noise that the catch-all below
# would produce.
raise
except ProxyException:
raise
except Exception as e:
verbose_proxy_logger.error(
"litellm.proxy.proxy_server.get_team_callbacks(): Exception occurred - {}".format(

View file

@ -108,18 +108,16 @@ async def test_add_team_callbacks_rejects_unauthorized_caller(
async def test_disable_team_logging_rejects_unauthorized_caller(
patched_prisma, unauthorized_caller
):
# The endpoint catches HTTPException and re-wraps it as ProxyException
# with the original status code preserved.
from litellm.proxy._types import ProxyException
with pytest.raises((HTTPException, ProxyException)) as exc:
# The HTTPException from the access guard now propagates directly
# — the catch-all that previously re-wrapped (and logged at error
# level) was narrowed so legitimate 4xx don't pollute alerting.
with pytest.raises(HTTPException) as exc:
await disable_team_logging(
http_request=Mock(spec=Request),
team_id="team-victim",
user_api_key_dict=unauthorized_caller,
)
code = getattr(exc.value, "status_code", None) or getattr(exc.value, "code", None)
assert int(code) == 403
assert exc.value.status_code == 403
patched_prisma.db.litellm_teamtable.update.assert_not_called()
@ -127,20 +125,13 @@ async def test_disable_team_logging_rejects_unauthorized_caller(
async def test_get_team_callbacks_rejects_unauthorized_caller(
patched_prisma, unauthorized_caller
):
# The endpoint catches generic Exception and re-wraps as ProxyException;
# an HTTPException raised by the access guard surfaces as a 403
# ProxyException — both shapes are acceptable failure modes, what
# matters is that the caller does NOT receive the team's callback data.
from litellm.proxy._types import ProxyException
with pytest.raises((HTTPException, ProxyException)) as exc:
with pytest.raises(HTTPException) as exc:
await get_team_callbacks(
http_request=Mock(spec=Request),
team_id="team-victim",
user_api_key_dict=unauthorized_caller,
)
code = getattr(exc.value, "status_code", None) or getattr(exc.value, "code", None)
assert int(code) == 403
assert exc.value.status_code == 403
@pytest.mark.asyncio