fix(team): reach the callback routes for a team id containing a colon

The route gate expands {team_id:path} to "[^:]+" so a colon-suffixed provider
route is not swallowed, which means the two entries added here matched a team
id with a slash but not one with a colon, while the router accepts both.
team_id is a free-form string, so a team whose id contains a colon kept the
old proxy-admin-only denial and its admin could not manage its own callbacks.
List both spellings rather than relaxing the shared matcher, which every
":path" route depends on. The comment claimed the two matchers agree; they do
not, so it now says what each placeholder actually accepts.
This commit is contained in:
Yucheng Zhu 2026-09-01 11:20:02 -07:00
parent e64b6f6a68
commit 2e0d81a9da
2 changed files with 19 additions and 4 deletions

View file

@ -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",

View file

@ -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)