merge main - #27163
Merged
Merged
Conversation
Previously, deleting a user via SCIM (`DELETE /scim/v2/Users/{id}`) or
marking them inactive (`PATCH active=false` / `PUT active=false`) only
touched the user row. Their virtual keys kept working because:
- `litellm_verificationtoken` was never updated.
- The auth path's combined-view query on the key never joined to the
user's active state.
- `get_user_object()` was wrapped in a silent `except` that set
`user_obj=None` when the owning user record was gone, so requests
proceeded normally.
Changes:
- Add `_set_user_keys_blocked(user_id, blocked)` in scim_v2.py that
flips only mismatched rows via `update_many` and invalidates each
affected token in the dual cache.
- Cascade SCIM lifecycle events to keys:
- `delete_user`: block all of the user's keys before deleting the
user row (preserves spend/audit while orphaning safely).
- `patch_user` / `update_user`: on `scim_active` transitions,
block (false) or unblock (true) the user's keys.
- Defense in depth in `user_api_key_auth`: reject the request when the
loaded `user_obj` has `metadata.scim_active == False`, even if a
cached key snuck past the per-key block.
- `transform_litellm_user_to_scim_user` now reflects the real
`scim_active` value instead of always returning `active=True`.
Tests:
- New `test_scim_key_deactivation.py` covering DELETE, PATCH
active=false, PATCH active=true, no-op patches, and the helper's
cache-invalidation contract.
- New `test_scim_deactivated_user_key_is_rejected` exercising the
auth-path defense.
- Existing PATCH tests updated with verificationtoken mocks for the
new code path.
The Prisma schema declares LiteLLM_VerificationToken.blocked as a
nullable Boolean with no default, so virtual keys created via the
key management endpoint persist with blocked=NULL. SQL equality
(`blocked = false`) never matches NULL rows, so the previous
`where={'blocked': not blocked}` filter silently skipped virtually
all real keys when SCIM tried to block them. This made SCIM
deprovisioning a no-op — and especially dangerous in DELETE flows
where the user row is removed afterwards, leaving orphaned but
fully-functional keys.
Match both `False` and `None` when blocking, and only `True`
when unblocking, so the state flip (and cache invalidation) actually
fires for the keys it should.
…responses
The redirect-following added to async_safe_get checks response.is_redirect
on every hop. Two vertex batch tests stub AsyncHTTPHandler.get with a bare
MagicMock, whose default-truthy is_redirect made the redirect path fire,
then crashed in httpx.URL().join() because headers.get('location') was
also a MagicMock instead of a string. Set is_redirect=False explicitly so
the mocked response models a non-redirect terminal response.
Also tighten _extract_redirect_url to raise SSRFError on non-string
Location values (defense-in-depth — a real httpx Response always returns
str|None, but this avoids a confusing TypeError if anything else ever
slips through).
This is an unrelated CI fix piggybacked on the SCIM PR to unblock the
batches test suite.
Tag each key SCIM blocks with metadata.scim_blocked=True. On reactivation unblock only those keys, leaving keys an admin blocked for unrelated reasons untouched.
… in UI
SCIM DELETE /Users/{id} previously called litellm_usertable.delete without
clearing rows that FK back to the user, so Postgres rejected the delete with
LiteLLM_InvitationLink_user_id_fkey and the SCIM caller saw a 500. Add a
helper to drop invitation_link, organization_membership, and team_membership
rows before the user delete (mirrors /user/delete in internal_user_endpoints).
Also add a Status column to the Virtual Keys and Internal Users tables so
admins can see at a glance which keys are blocked and which users SCIM has
deactivated. SCIM-blocked keys carry a tooltip explaining the origin.
Pin the dashboard's Node version to 20 via .nvmrc to match CI.
…itellm_fix_scim_virtual_key_deactivation
A SCIM PUT may legally omit `active` (full-replace with the field absent). Pydantic fills the SCIMUser.active default of True, so the PUT handler was overwriting metadata.scim_active with True even when the client never sent it — silently reactivating a previously SCIM-blocked user and unblocking their keys. Use model_fields_set to detect whether the client actually sent `active`. If omitted, preserve the prior scim_active value and skip the cascade to virtual keys. Also drop comments added in this PR that just narrate what the code does; keep only the docstrings and the SQL-NULL pitfall note that explain non-obvious behaviour. Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
secret_fields (containing raw HTTP headers including Authorization Bearer tokens) was being included in proxy_server_request['body'] because the body snapshot was a copy.copy(data) of the full request dict. This body gets serialized and persisted in the LiteLLM_SpendLogs table, exposing user credentials in the database. Root cause: data['secret_fields'] was set before the body snapshot at data['proxy_server_request']['body'] = copy.copy(data), so the full raw headers (including auth tokens) ended up in the snapshot. Fix (defense in depth): 1. Exclude 'secret_fields' when creating the body snapshot in litellm_pre_call_utils.py (primary fix) 2. Strip 'secret_fields' in _sanitize_request_body_for_spend_logs_payload as a secondary safeguard secret_fields remains available on the live data dict for legitimate downstream consumers (MCP, Responses API). Co-authored-by: Krrish Dholakia <krrish-berri-2@users.noreply.github.com>
…eactivation fix(scim): revoke virtual keys when SCIM deprovisions a user
…end-logs-a532 fix(security): prevent secret_fields from leaking into spend logs
Contributor
|
Too many files changed for review. ( |
|
|
Sameerlite
merged commit May 5, 2026
e460174
into
litellm_openai_realtime_ga
101 of 106 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Relevant issues
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Screenshots / Proof of Fix
Type
🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test
Changes