diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index faed138ac7d..b960ca2c1b3 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -832,10 +832,16 @@ class LiteLLMRoutes(enum.Enum): "/team/{team_id}/members/me", # POST/GET the team's logging callbacks, and DELETE one of them. Every # handler calls _verify_team_access, which admits only a proxy admin, an - # org admin for the team, or an admin of this team. The :path converter - # mirrors the route registration, which accepts a team id containing "/". + # org admin for the team, or an admin of this team. + # + # Two spellings per route because neither placeholder alone covers every + # team id the router accepts: the gate expands {x:path} to "[^:]+", which + # takes a slash but not a colon, and {x} to "[^/]+", which takes a colon + # but not a slash. team_id is a free-form string, so both are reachable. "/team/{team_id:path}/callback", "/team/{team_id:path}/callback/{callback_name}", + "/team/{team_id}/callback", + "/team/{team_id}/callback/{callback_name}", "/model/new", "/model/update", "/model/delete", diff --git a/tests/test_litellm/proxy/auth/test_route_checks.py b/tests/test_litellm/proxy/auth/test_route_checks.py index 52956301f82..7f53a75b072 100644 --- a/tests/test_litellm/proxy/auth/test_route_checks.py +++ b/tests/test_litellm/proxy/auth/test_route_checks.py @@ -3551,6 +3551,10 @@ TEAM_CALLBACK_ROUTES = ( # contain a slash "/team/tenant/06bda574/callback", "/team/tenant/06bda574/callback/langfuse", + # team_id is a free-form string, so it may also contain a colon, which the + # gate's :path expansion excludes + "/team/tenant:06bda574/callback", + "/team/tenant:06bda574/callback/langfuse", ) @@ -3590,8 +3594,13 @@ def test_team_callback_routes_are_self_managed(): look identical for an internal_user while silently denying the org admins and view-only roles that list does not cover. """ - assert "/team/{team_id:path}/callback" in LiteLLMRoutes.self_managed_routes.value - assert "/team/{team_id:path}/callback/{callback_name}" in LiteLLMRoutes.self_managed_routes.value + for template in ( + "/team/{team_id:path}/callback", + "/team/{team_id:path}/callback/{callback_name}", + "/team/{team_id}/callback", + "/team/{team_id}/callback/{callback_name}", + ): + assert template in LiteLLMRoutes.self_managed_routes.value @pytest.mark.parametrize("route", TEAM_CALLBACK_ROUTES)