diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 7fff640c497..39857984247 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -690,6 +690,13 @@ class LiteLLMRoutes(enum.Enum): "/organization/delete", "/organization/member_add", "/organization/member_update", + # member_delete is equally destructive as member_add / member_update + # and must be scoped the same way — otherwise it falls through to + # the management_routes / self_managed_routes path and lets any + # non-PROXY_ADMIN caller that reaches the route delete arbitrary + # org memberships without the organization_role_based_access_check + # that member_add / member_update trigger. + "/organization/member_delete", ] # Routes accessible by Admin Viewer (read-only admin access) diff --git a/litellm/proxy/management_endpoints/organization_endpoints.py b/litellm/proxy/management_endpoints/organization_endpoints.py index 25df9f0b0f7..670946ac0d8 100644 --- a/litellm/proxy/management_endpoints/organization_endpoints.py +++ b/litellm/proxy/management_endpoints/organization_endpoints.py @@ -1064,6 +1064,33 @@ async def organization_member_update( }, ) + # Reject attempts to change the role of a global PROXY_ADMIN via + # org-scoped operations. An org-admin of any org could otherwise + # alter a PROXY_ADMIN user's per-org role, which has downstream + # effects on admin UI filtering and scope derivation. + target_user_row = await prisma_client.db.litellm_usertable.find_unique( + where={"user_id": data.user_id} + ) + if target_user_row is not None and getattr( + target_user_row, "user_role", None + ) in ( + LitellmUserRoles.PROXY_ADMIN.value, + LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY.value, + ): + if ( + user_api_key_dict.user_role + != LitellmUserRoles.PROXY_ADMIN.value + ): + raise HTTPException( + status_code=403, + detail={ + "error": ( + "Only PROXY_ADMIN may modify the organization " + "role of a user who is a global PROXY_ADMIN." + ) + }, + ) + # Update member role if data.role is not None: await prisma_client.db.litellm_organizationmembership.update(