fix(proxy): admin-gate permissions on _process_single_key_update (LIT-4137) - #32002
Conversation
|
bugbot run |
Greptile SummaryThis PR adds a call to
Confidence Score: 5/5The change adds a synchronous guard that raises before any DB access, so the only risk is over-rejection — but default callers that never set The inserted call is correctly typed ( No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/key_management_endpoints.py | Adds _check_permissions_caller_permission call into _process_single_key_update at the correct position; type-compatible with GenerateRequestBase, synchronous, raises before any DB I/O. |
| tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py | Two new async tests validate the 403 gate for non-admin callers with permissions set; no redundant local imports (amended commit), assertions are mutation-resistant. |
Reviews (3): Last reviewed commit: "fix(proxy): admin-gate `permissions` on ..." | Re-trigger Greptile
Greptile SummaryThis PR adds
Confidence Score: 4/5Safe to merge; the change is a small, additive permission gate with no side effects for callers that don't supply the guarded field. The five-line production change adds an existing, well-tested helper into one more call site. Both new tests are mock-only, mutation-killable, and cover the explicit-empty edge case. The only findings are redundant local imports inside the two new test functions — no logic issues. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/key_management_endpoints.py | Wires _check_permissions_caller_permission into _process_single_key_update right after _validate_max_budget; five-line addition that mirrors the guard already present in _validate_update_key_data and the regenerate path |
| tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py | Adds two async mock-only regression tests for the new gate; redundant local re-import of _process_single_key_update inside each test body (already imported at module level) |
Reviews (2): Last reviewed commit: "fix(proxy): admin-gate `permissions` on ..." | Re-trigger Greptile
| from litellm.proxy.management_endpoints.key_management_endpoints import ( | ||
| _process_single_key_update, | ||
| ) | ||
|
|
||
| update_key_request = UpdateKeyRequest( | ||
| key="abc123", | ||
| permissions={"get_spend_routes": True}, | ||
| ) |
There was a problem hiding this comment.
_process_single_key_update is already imported at module level (line 40), so the local re-import inside each test function is redundant. Removing it keeps the tests consistent with the rest of the file.
| from litellm.proxy.management_endpoints.key_management_endpoints import ( | |
| _process_single_key_update, | |
| ) | |
| update_key_request = UpdateKeyRequest( | |
| key="abc123", | |
| permissions={"get_spend_routes": True}, | |
| ) | |
| update_key_request = UpdateKeyRequest( | |
| key="abc123", | |
| permissions={"get_spend_routes": True}, | |
| ) |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| from litellm.proxy.management_endpoints.key_management_endpoints import ( | ||
| _process_single_key_update, | ||
| ) | ||
|
|
||
| update_key_request = UpdateKeyRequest(key="abc123", permissions={}) |
There was a problem hiding this comment.
Same redundant local import in the second test —
_process_single_key_update is already available from the module-level import.
| from litellm.proxy.management_endpoints.key_management_endpoints import ( | |
| _process_single_key_update, | |
| ) | |
| update_key_request = UpdateKeyRequest(key="abc123", permissions={}) | |
| update_key_request = UpdateKeyRequest(key="abc123", permissions={}) |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 9fa0ccd. Configure here.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…IT-4137) The bulk-update entrypoints `/key/bulk_update` and `/team/key/bulk_update` route through `_process_single_key_update`, not through `_validate_update_key_data`, so the `permissions` gate LIT-4092 wired into the single-key path never fires on bulk. Currently safe by construction: `BulkUpdateKeyRequestItem` doesn't declare `permissions` (Pydantic silently drops it), and `KeyUpdateFields` uses `model_config = ConfigDict(extra="forbid")` (Pydantic 422s at parse time). Neither structural barrier is enforced by tests on the field itself; a future widening of either allowlist to include `permissions` would reopen the class silently. This wires `_check_permissions_caller_permission` into `_process_single_key_update` right after `_validate_max_budget`, before `prepare_key_update_data`. Zero behavior change today for any caller routing through the current bulk request models; a defense-in-depth gate for the class. Tests: - test_process_single_key_update_non_admin_permissions_rejected - test_process_single_key_update_non_admin_permissions_explicit_empty_rejected Both mutation-killed against removing the gate. Full mapped test file (341 tests) green.
9fa0ccd to
57ac7b8
Compare
|
@greptileai re-review, amended commit (57ac7b8) drops the redundant local re-imports of |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 57ac7b8. Configure here.
Relevant issues
Linear ticket
Resolves LIT-4137
Pre-Submission checklist
@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).
Type
Bug Fix
Changes
The bulk-update entrypoints
/key/bulk_updateand/team/key/bulk_updateroute through_process_single_key_update, not through_validate_update_key_data, so the_check_permissions_caller_permissiongate LIT-4092 wired into the single-key path never fires on bulkThe bulk paths are currently safe by two independent structural barriers:
BulkUpdateKeyRequestItem(per-item request model for/key/bulk_update) doesn't declarepermissions, so Pydantic silently drops the field before it can reach persistence.KeyUpdateFields(broadcast payload for/team/key/bulk_update) usesmodel_config = ConfigDict(extra="forbid"), so Pydantic 422s at parse time. Neither structural barrier is pinned by a test that would fail if the barrier were ever loosenedThis wires
_check_permissions_caller_permission(data=update_key_request, user_api_key_dict=user_api_key_dict)into_process_single_key_updateright after_validate_max_budgetand beforeprepare_key_update_data. Zero behavior change today for callers routing through the current bulk request models. Defense-in-depth for the class: a future widening of either allowlist to includepermissionscannot reopen the vulnerabilityTwo regression tests in
tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.pytest_process_single_key_update_non_admin_permissions_rejected— non-adminUpdateKeyRequestwithpermissions={"get_spend_routes": True}returns 403test_process_single_key_update_non_admin_permissions_explicit_empty_rejected— same forpermissions={}viamodel_fields_setBoth mutation-killed against removing the gate. Full mapped test file (341 tests) green
Screenshots / Proof of Fix
Live proxy on
localhost:4010against Postgres. Admin mints two keys, then exercises the current bulk-update surfaceExisting bulk behavior on non-permissions field (unchanged control)
/key/bulk_updatewithpermissionsin the request body.BulkUpdateKeyRequestItemsilently drops the unknown field, the update proceeds without persistingpermissions/team/key/bulk_updatewithpermissionsinupdate_fields.KeyUpdateFieldsisextra="forbid", so Pydantic 422s at parse timeNon-admin cannot reach
/key/bulk_updateat all (existing admin-only gate at handler layer)The new gate is a defense-in-depth layer on the inner perimeter. Every write path through
_process_single_key_updatethat would ever carrypermissionsfrom a non-admin now hits the 403 before the DB writeRelated
Closes the third item in the LIT-4092 / LIT-4139 / LIT-4137 family. LIT-4092 gated
permissionson the single-key write paths; LIT-4139 did the same forallowed_routes; this PR extends thepermissionsgate to the bulk write path helper so all four write paths (/key/generate,/key/update,/key/regenerate, and both bulk endpoints via the helper) share the same admin-only ruleNote
Low Risk
Small authorization hardening on key management with no intended behavior change for current bulk APIs; tests cover the new gate.
Overview
Adds defense-in-depth so bulk key updates cannot set
permissionsunless the caller is a proxy admin._check_permissions_caller_permission(already used on single-key validation) is now invoked at the start of_process_single_key_update, which backs/key/bulk_updateand/team/key/bulk_update. AnyUpdateKeyRequestthat explicitly includespermissions—including{}viamodel_fields_set—returns 403 for non-admins before DB work.Current bulk request models still strip or forbid
permissions, so day-to-day behavior is unchanged; the gate protects the shared helper if those allowlists widen later. Two async unit tests lock in the 403 behavior on_process_single_key_update.Reviewed by Cursor Bugbot for commit 57ac7b8. Bugbot is set up for automated code reviews on this repo. Configure here.