[Fix] Backport /key/regenerate ownership-rebind + premium-gate guards (#27793) - #27819
Conversation
…e guards (#27793) Backport of #27793 onto litellm_1.84.0rc2. A non-admin caller could rebind their own key's user_id via /key/regenerate. _execute_virtual_key_regeneration had org/team guards but no user_id guard, and prepare_key_update_data did not strip the field — it survived model_dump(exclude_unset=True) into the Prisma update. On the next request, _return_user_api_key_auth_obj resolved the rebound user_id against litellm_usertable and returned PROXY_ADMIN whenever the target row's user_role was admin. /key/update had the equivalent guard inline at _validate_update_key_data; extract it to a shared helper _validate_caller_can_change_key_ownership and call from both /key/update and _execute_virtual_key_regeneration. Also tighten the premium gate that allowed the master-key rotation branch to skip the enterprise check. The previous predicate was a field-presence test, not an identity check. Verify the caller actually holds the master key via _is_master_key before allowing the non-premium path. Block explicit-null user_id and empty-string user_id as removal attempts; both 403-reject for non-admin callers.
Greptile SummaryThis backport closes two privilege-escalation paths in the key management layer: a non-admin user_id rebind via
Confidence Score: 4/5The change is a targeted security backport with well-scoped tests; the two fixed attack paths are now properly gated and no existing test assertions are altered. Both fixes are logically sound: the ownership guard correctly leverages model_fields_set to catch explicit-null user_id, and the premium gate now verifies actual master-key identity. The six new tests cover all critical cases. The patched security paths touch auth-critical code, so an extra reviewer pass is worthwhile before merging to a release branch. The auth logic in key_management_endpoints.py — specifically the new helper and the premium gate block — deserves a careful read before merging to the release branch.
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/key_management_endpoints.py | Adds _validate_caller_can_change_key_ownership helper and wires it into both /key/update and _execute_virtual_key_regeneration; tightens the premium-gate check to require actual master-key identity. Logic is correct and well-commented. |
| tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py | Adds 6 new focused tests for the rebind guard and premium gate; no existing assertions are weakened or removed. All tests use mocks only — no real network calls. |
Reviews (1): Last reviewed commit: "chore(proxy): backport /key/regenerate o..." | Re-trigger Greptile
Relevant issues
Backport of #27793 onto
litellm_1.84.0rc2.Summary
Failure Path (Before Fix)
A non-admin caller could rebind their own key's
user_idvia/key/regenerate._execute_virtual_key_regenerationhad org/team guards but nouser_idguard, andprepare_key_update_datadid not strip the field — it survivedmodel_dump(exclude_unset=True)into the Prisma update. On the next request,_return_user_api_key_auth_objresolved the rebounduser_idagainstlitellm_usertableand returnedPROXY_ADMINwhenever the target row'suser_rolewas admin.Separately, the premium gate that allowed the master-key rotation branch to skip the enterprise check was a field-presence test, not an identity check — a non-premium caller could send any value in
new_master_keyand the premium check would no-op.Fix
/key/update's inline guard into a shared helper_validate_caller_can_change_key_ownershipand call it from both/key/updateand_execute_virtual_key_regeneration. Future regenerate-style endpoints inherit the guard for free.user_idas removal attempts, both 403-rejected for non-admin callers. Usedata.model_fields_setto distinguish omitted from explicitly-set._is_master_keybefore allowing the non-premium master-key rotation path.Changes
This is a clean backport of all three commits from #27793 onto the 1.84.0rc2 release branch. Source-file diff (+69/-18) and test-file diff (+154) match the original PR exactly. No code changes from the original PR.
Testing
pytest tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py -k "regenerate_user_id_rebind or regenerate_premium" -v— 6/6 pass on this branch.pytest -k "key_regenerate or key_update or validate"— 45/45 related tests pass; no regressions in adjacent guards.Type
🐛 Bug Fix
✅ Test