From 3f0245d312420751bc71d0582223e60ddf92af11 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:45:22 +0000 Subject: [PATCH] fix(proxy): compare allowed routes semantically --- .../key_management_endpoints.py | 17 +++++++++++++++-- .../test_key_management_endpoints.py | 13 ++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index a1c4fd842d2..385e9c8b01e 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -18,7 +18,7 @@ import os import re import secrets import traceback -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from datetime import datetime, timedelta, timezone from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, cast @@ -530,6 +530,15 @@ def _validate_caller_can_change_key_ownership( ) +def _allowed_routes_values_match( + submitted_routes: Sequence[str] | None, + existing_routes: Sequence[str] | None, +) -> bool: + if submitted_routes is None or existing_routes is None: + return submitted_routes == existing_routes + return set(submitted_routes) == set(existing_routes) + + def _check_allowed_routes_caller_permission( allowed_routes: Optional[list], user_api_key_dict: UserAPIKeyAuth, @@ -2267,7 +2276,11 @@ async def _validate_update_key_data( user_api_key_dict=user_api_key_dict, allowed_routes_was_provided="allowed_routes" in data.model_fields_set, allowed_routes_changed=( - "allowed_routes" in data.model_fields_set and data.allowed_routes != existing_key_row.allowed_routes + "allowed_routes" in data.model_fields_set + and not _allowed_routes_values_match( + data.allowed_routes, + existing_key_row.allowed_routes, + ) ), ) _check_passthrough_routes_caller_permission( diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index 658372cafee..b9a5284d14e 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -9589,6 +9589,8 @@ async def test_update_key_non_budget_fields_allowed_for_internal_user(monkeypatc mock_existing_key.key_alias = None mock_existing_key.models = [] mock_existing_key.metadata = {} + mock_existing_key.allowed_routes = [] + mock_existing_key.permissions = {} mock_existing_key.model_dump.return_value = { "token": test_hashed_token, "user_id": "internal_user", @@ -9650,7 +9652,12 @@ async def test_update_key_non_budget_fields_allowed_for_internal_user(monkeypatc # Updating key_alias (non-budget field) should succeed result = await update_key_fn( request=mock_request, - data=UpdateKeyRequest(key=test_hashed_token, key_alias="my-alias"), + data=UpdateKeyRequest( + key=test_hashed_token, + key_alias="my-alias", + allowed_routes=[], + permissions={}, + ), user_api_key_dict=user_api_key_dict, litellm_changed_by=None, ) @@ -14348,9 +14355,9 @@ async def test_update_key_non_admin_permissions_non_empty_rejected(monkeypatch): UpdateKeyRequest( key="sk-alice-personal", tpm_limit=42, - allowed_routes=["llm_api_routes"], + allowed_routes=["management_routes", "llm_api_routes"], ), - ["llm_api_routes"], + ["llm_api_routes", "management_routes"], {}, ), (