From 578846e57d6822907b16ed2f96bccbc77f080d04 Mon Sep 17 00:00:00 2001 From: user <70670632+stuxf@users.noreply.github.com> Date: Wed, 29 Apr 2026 21:56:04 +0000 Subject: [PATCH] fix(team): don't log legitimate 403s at error level on /callback endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../team_callback_endpoints.py | 23 +++++++++++-------- .../test_team_callback_endpoints.py | 23 ++++++------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/litellm/proxy/management_endpoints/team_callback_endpoints.py b/litellm/proxy/management_endpoints/team_callback_endpoints.py index f28db70ef5d..934824dd89f 100644 --- a/litellm/proxy/management_endpoints/team_callback_endpoints.py +++ b/litellm/proxy/management_endpoints/team_callback_endpoints.py @@ -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( diff --git a/tests/test_litellm/proxy/management_endpoints/test_team_callback_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_team_callback_endpoints.py index 745c0583bbd..e26820ea9cd 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_team_callback_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_team_callback_endpoints.py @@ -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