Skip to content

[Refactor] Align /v2/key/info response handling with v1#25313

Merged
yuneng-berri merged 1 commit intomainfrom
litellm_align_v2_key_info_with_v1
Apr 7, 2026
Merged

[Refactor] Align /v2/key/info response handling with v1#25313
yuneng-berri merged 1 commit intomainfrom
litellm_align_v2_key_info_with_v1

Conversation

@yuneng-berri
Copy link
Copy Markdown
Collaborator

Summary

The /v2/key/info endpoint was missing response filtering that the v1 /key/info endpoint already had. This aligns the two endpoints so v2 applies the same per-key permission checks (_can_user_query_key_info) and strips internal fields from the response.

Changes

  • Apply _can_user_query_key_info() to each key returned by /v2/key/info, matching v1 behavior
  • Strip the token field from response objects, matching v1 behavior
  • Resolve key_aliases to tokens before querying, so the alias path doesn't skip the token filter

Type

🧹 Refactoring

The /v2/key/info endpoint was missing response filtering that
the v1 /key/info endpoint already had. This aligns the two
endpoints so v2 applies the same per-key permission checks and
strips internal fields from the response. Also fixes the
key_aliases query path to resolve aliases before querying.
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 7, 2026 10:23pm

Request Review

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Apr 7, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_align_v2_key_info_with_v1 (021429b) with main (bf8b615)

Open in CodSpeed

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 7, 2026

Greptile Summary

This PR closes real security and correctness gaps in POST /v2/key/info: it adds per-key access control via _can_user_query_key_info, strips the hashed token field from responses, and resolves key_aliases to real tokens before querying — preventing a latent unbounded full-table scan when keys was None.

  • Security improvement: unauthorized keys are now silently filtered rather than returned to the requester
  • Token stripping: k_dict.pop(\"token\", None) added, matching v1 behavior
  • Alias resolution: a pre-pass find_many resolves aliases to hashed tokens; empty result short-circuits cleanly
  • Missing v1 parity (P1): attach_object_permission_to_dict is called in v1 (line 2751) but omitted in v2 — keys with object_permission_id will return incomplete data through v2
  • Alias-only response (P2): \"key\": data.keys returns null when the request used only key_aliases, producing an inconsistent response shape
  • Unnecessary DB join (P2): include={\"litellm_budget_table\": True} in the alias-resolution query fetches data that is immediately discarded
  • No tests (P2): the new access-control, alias-resolution, and silent-skip paths have no unit test coverage despite CLAUDE.md explicitly requiring it

Confidence Score: 4/5

Safe to merge after addressing the missing attach_object_permission_to_dict call; remaining items are P2 style or cleanup

One P1 issue — attach_object_permission_to_dict is called in v1 but omitted in v2, silently returning incomplete data for keys with object permissions. The remaining three items (unnecessary DB join, null key field, missing tests) are P2 and do not block merge.

key_management_endpoints.py lines 2662-2668 — the serialization block in info_key_fn_v2 is missing the object permission attachment present in v1

Vulnerabilities

No security vulnerabilities introduced. The PR improves security by adding _can_user_query_key_info checks to /v2/key/info and stripping hashed token values from responses. The alias-path fix prevents a latent unbounded full-table scan (passing token=None to get_data) that would have exposed all keys to any authenticated caller. The silent-skip approach for unauthorized keys (rather than 403) is an acceptable design choice for a bulk endpoint and does not constitute a security regression.

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/key_management_endpoints.py Adds access-control filtering, token stripping, and alias resolution to /v2/key/info; one P1 gap — attach_object_permission_to_dict is present in v1 but omitted in v2, causing incomplete responses for keys with object permissions

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant V2 as POST /v2/key/info
    participant DB as Database (Prisma)
    participant AC as _can_user_query_key_info

    C->>V2: {keys: [...], key_aliases: [...]}
    alt key_aliases present
        V2->>DB: find_many(key_alias IN key_aliases)
        DB-->>V2: alias rows → extract tokens
    end
    V2->>DB: get_data(token=tokens_to_query, find_all)
    DB-->>V2: key_info rows
    loop for each key
        V2->>AC: check user permission
        AC-->>V2: allowed / denied
        alt allowed
            V2->>V2: model_dump(), pop(token)
            Note over V2: ⚠️ attach_object_permission missing
        else denied
            V2->>V2: skip key silently
        end
    end
    V2-->>C: {key: data.keys, info: filtered_key_info}
Loading

Comments Outside Diff (1)

  1. litellm/proxy/management_endpoints/key_management_endpoints.py, line 2601-2668 (link)

    P2 No tests added for new access-control and alias-resolution paths

    CLAUDE.md explicitly requires: "Always add tests when adding new entity types or features" and for name→ID resolution: test (a) alias resolves and is allowed, (b) alias resolves but is not allowed, (c) alias does not resolve at all. None of these paths have coverage. The silent-skip behavior for unauthorized keys (rather than a 403) is especially important to cover — this is where access-control bugs tend to hide. Relevant test file: tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py.

    Context Used: CLAUDE.md (source)

Reviews (1): Last reviewed commit: "[Refactor] Align /v2/key/info response h..." | Re-trigger Greptile

Comment on lines 2662 to +2667
try:
k = k.model_dump() # noqa
k_dict = k.model_dump()
except Exception:
# if using pydantic v1
k = k.dict()
filtered_key_info.append(k)
k_dict = k.dict()
k_dict.pop("token", None)
filtered_key_info.append(k_dict)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing attach_object_permission_to_dict — incomplete v1 parity

The PR's stated goal is full v1 parity. The v1 endpoint (info_key_fn, line 2751) calls attach_object_permission_to_dict(key_info, prisma_client) after serializing each key, enriching response objects that carry an object_permission_id with the full object_permission sub-object. The v2 endpoint omits this step entirely, so any key with an object_permission_id will return incomplete data through v2 — silently and with no error.

Suggested change
try:
k = k.model_dump() # noqa
k_dict = k.model_dump()
except Exception:
# if using pydantic v1
k = k.dict()
filtered_key_info.append(k)
k_dict = k.dict()
k_dict.pop("token", None)
filtered_key_info.append(k_dict)
try:
k_dict = k.model_dump()
except Exception:
k_dict = k.dict()
k_dict.pop("token", None)
k_dict = await attach_object_permission_to_dict(k_dict, prisma_client)
filtered_key_info.append(k_dict)

Comment on lines +2638 to +2641
alias_rows = await prisma_client.db.litellm_verificationtoken.find_many(
where={"key_alias": {"in": data.key_aliases}},
include={"litellm_budget_table": True},
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unnecessary include in alias resolution query

Only row.token is used from alias_rows — the joined litellm_budget_table data is immediately discarded. The include causes an extra DB join on every alias-path request with no benefit.

Suggested change
alias_rows = await prisma_client.db.litellm_verificationtoken.find_many(
where={"key_alias": {"in": data.key_aliases}},
include={"litellm_budget_table": True},
)
alias_rows = await prisma_client.db.litellm_verificationtoken.find_many(
where={"key_alias": {"in": data.key_aliases}},
)

k_dict = k.dict()
k_dict.pop("token", None)
filtered_key_info.append(k_dict)
return {"key": data.keys, "info": filtered_key_info}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 "key": null in response for alias-only requests

When the request body contains only key_aliases (no keys), data.keys is None, producing {"key": null, "info": [...]}. Callers querying by alias can't correlate which info objects map to which aliases, and the null field creates an inconsistent response shape vs. v1. Consider reflecting the requested identifiers:

Suggested change
return {"key": data.keys, "info": filtered_key_info}
return {"key": data.keys or data.key_aliases, "info": filtered_key_info}

@yuneng-berri yuneng-berri merged commit 08f34aa into main Apr 7, 2026
103 of 107 checks passed
@yuneng-berri yuneng-berri deleted the litellm_align_v2_key_info_with_v1 branch April 7, 2026 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants