ITADN

[Bug]: Customer block and unblock APIs do not consistently enforce EndUser.blocked

#35564Openemerzon 创建于 19 天前
proxy
E
emerzoncommented
### Check for existing issues - [x] I searched open and closed issues before filing - [x] Existing end-user budget reports such as [#34238](https://github.com/BerriAI/litellm/issues/34238) concern spend counters, not the `blocked` field ### What happened? `POST /customer/block` documents that subsequent LLM requests carrying the blocked end-user ID will be rejected, but the core auth path never checks `LiteLLM_EndUserTable.blocked` The endpoint writes `blocked=True` to Postgres even when the optional enterprise blocked-user callback is not configured. `get_end_user_object` loads that row for budget checks, but `common_checks` only checks the end-user budget and does not reject a blocked object. In an OSS or enterprise deployment without that callback, the block API succeeds while later requests continue The optional enterprise callback does check the database field, but it caches the result under `litellm:end_user_id:{id}` for 60 seconds. `/customer/block` does not invalidate that cache or the core auth cache `end_user_id:{id}`, so a warmed unblocked entry remains usable until expiry The inverse endpoint has a separate consistency problem. `POST /customer/unblock` requires the enterprise callback and removes the ID from `litellm.blocked_user_list`, but it never updates `LiteLLM_EndUserTable.blocked` back to `False`. A user blocked through `/customer/block` can therefore remain blocked by the callback after its cache refresh even though `/customer/unblock` returned success ### Steps to Reproduce Core enforcement gap 1. Run the proxy with Postgres and without the enterprise blocked-user callback 2. Call `POST /customer/block` with `{"user_ids":["blocked-demo"]}` 3. Confirm the database row has `blocked=True` 4. Send an LLM request with `"user":"blocked-demo"` 5. Observe that core auth admits the request Enterprise cache gap 1. Configure the enterprise blocked-user callback and make one request as `cached-demo` to cache `blocked=False` 2. Call `POST /customer/block` for that ID 3. Send another request within 60 seconds 4. Observe that the callback reads the stale unblocked cache entry Unblock persistence gap 1. Block an ID through `/customer/block` 2. Call `/customer/unblock` 3. Inspect `LiteLLM_EndUserTable.blocked` and observe it is still `True` 4. Let the enterprise cache expire and retry the request 5. Observe that the persisted row can block the user again ### Expected behavior `/customer/block` and `/customer/unblock` should update the same durable field, invalidate both cache namespaces, and core auth should enforce the durable `blocked` state without requiring an optional callback ### Relevant code - [Block endpoint and documented contract](https://github.com/BerriAI/litellm/blob/b1fd20f4cdc8f7a9b4e0c886a277465aedb05ec8/litellm/proxy/management_endpoints/customer_endpoints.py#L55-L105) - [Unblock endpoint](https://github.com/BerriAI/litellm/blob/b1fd20f4cdc8f7a9b4e0c886a277465aedb05ec8/litellm/proxy/management_endpoints/customer_endpoints.py#L108-L168) - [Core common checks](https://github.com/BerriAI/litellm/blob/b1fd20f4cdc8f7a9b4e0c886a277465aedb05ec8/litellm/proxy/auth/auth_checks.py#L500-L735) - [Enterprise blocked-user cache](https://github.com/BerriAI/litellm/blob/b1fd20f4cdc8f7a9b4e0c886a277465aedb05ec8/enterprise/enterprise_hooks/blocked_user_list.py#L60-L124) ### What part of LiteLLM is this about? Proxy ### What LiteLLM version are you on? v1.96.0, commit `b1fd20f4cdc8f7a9b4e0c886a277465aedb05ec8`
0 条评论