From 463e9cd7ff3612070aaab7657ce31d98373e5f5b Mon Sep 17 00:00:00 2001 From: cat0825 Date: Tue, 4 Aug 2026 11:27:09 +0800 Subject: [PATCH] fix(proxy): only apply blocked when explicitly supplied The model default blocked=False was being written on every customer update that omitted the field, silently unblocking blocked customers when admins changed unrelated fields like alias or budget. Only accept bool values for fields the caller explicitly supplied (data.fields_set()), keeping the isinstance(v, bool) semantics for explicit updates like blocked=True/False. Adds a regression test: updating a blocked customer without the blocked field must not reset the block. --- .../customer_endpoints.py | 5 +++- .../test_customer_endpoints.py | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/management_endpoints/customer_endpoints.py b/litellm/proxy/management_endpoints/customer_endpoints.py index 388888d960d..4127bc973e4 100644 --- a/litellm/proxy/management_endpoints/customer_endpoints.py +++ b/litellm/proxy/management_endpoints/customer_endpoints.py @@ -553,7 +553,10 @@ async def update_end_user( # get non default values for key non_default_values = {} for k, v in data_json.items(): - if v is not None and (isinstance(v, bool) or v not in ([], {}, 0)): + if v is not None and ( + (isinstance(v, bool) and k in data.fields_set()) + or v not in ([], {}, 0) + ): non_default_values[k] = v ## Get end user table data ## diff --git a/tests/test_litellm/proxy/management_endpoints/test_customer_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_customer_endpoints.py index c1479d4539c..6eb03256b67 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_customer_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_customer_endpoints.py @@ -105,6 +105,30 @@ def test_update_customer_unblock(mock_prisma_client, mock_user_api_key_auth): assert update_mock.call_args.kwargs["data"]["blocked"] is False +def test_update_customer_keeps_blocked_when_omitted(mock_prisma_client, mock_user_api_key_auth): + """ + Regression test: updating a blocked customer without supplying `blocked` + must NOT reset it to unblocked. `blocked=False` is the model default and + should only be applied when explicitly provided by the caller. + """ + mock_end_user = LiteLLM_EndUserTable(user_id="test-user-1", blocked=True) + updated_mock_end_user = LiteLLM_EndUserTable(user_id="test-user-1", blocked=True) + + mock_prisma_client.db.litellm_endusertable.find_first = AsyncMock(return_value=mock_end_user) + mock_prisma_client.db.litellm_endusertable.update = AsyncMock(return_value=updated_mock_end_user) + + response = client.post( + "/customer/update", + json={"user_id": "test-user-1", "alias": "Updated Test User"}, + headers={"Authorization": "Bearer test-key"}, + ) + + assert response.status_code == 200 + update_mock = mock_prisma_client.db.litellm_endusertable.update + update_mock.assert_called_once() + assert "blocked" not in update_mock.call_args.kwargs["data"] + + def test_update_customer_not_found(mock_prisma_client, mock_user_api_key_auth): """ Test that update_end_user raises a 404 ProxyException when user_id does not exist.