[Fix] handle metadata=null on service-account keys

Addresses Greptile P1: `metadata: null` on a key with a reserved field
was crashing inside prepare_metadata_fields because cast(dict, None) is
a runtime no-op, so the loop hit `reserved_field in None` and returned
500 instead of 400.

Extend the rejection condition to cover `casted_metadata is None`, so
attempts to clear an immutable field via `metadata: null` return 400
consistently with the overwrite path. Non-service-account keys still
fall through to the existing "clear all metadata" behavior.
This commit is contained in:
Ryan Crabbe 2026-04-18 11:52:54 -07:00
parent 01acbb8d3d
commit 208d583a33
No known key found for this signature in database
2 changed files with 20 additions and 1 deletions

View file

@ -1523,7 +1523,7 @@ def prepare_metadata_fields(
existing_value = existing_metadata.get(reserved_field)
if existing_value is None:
continue
if (
if casted_metadata is None or (
reserved_field in casted_metadata
and casted_metadata[reserved_field] != existing_value
):

View file

@ -1300,6 +1300,25 @@ async def test_update_rejects_explicit_null_service_account_id():
assert exc_info.value.status_code == 400
@pytest.mark.asyncio
async def test_update_rejects_whole_metadata_null_on_service_account_key():
"""
`metadata: null` on a key with a reserved field would have dereferenced None
inside the reserved-field loop and returned a 500. Must surface as a 400
since the effect would be to clear an immutable field.
"""
data = UpdateKeyRequest(key="sk-1", metadata=None, team_id="IJ")
existing_key = LiteLLM_VerificationToken(
token="hashed",
team_id="IJ",
metadata={"service_account_id": "sa-123"},
)
with pytest.raises(HTTPException) as exc_info:
await prepare_key_update_data(data=data, existing_key_row=existing_key)
assert exc_info.value.status_code == 400
@pytest.mark.asyncio
async def test_update_without_metadata_still_preserves_existing():
"""Omitting metadata entirely must not drop existing metadata fields."""