Skip to content

[Fix] /spend/logs: align filter handling with user scoping#25594

Merged
yuneng-berri merged 3 commits intolitellm_yj_apr_11from
litellm_spend_logs_user_scoping
Apr 13, 2026
Merged

[Fix] /spend/logs: align filter handling with user scoping#25594
yuneng-berri merged 3 commits intolitellm_yj_apr_11from
litellm_spend_logs_user_scoping

Conversation

@yuneng-berri
Copy link
Copy Markdown
Collaborator

Relevant issues

Summary

The /spend/logs endpoint accepted api_key, request_id, and user_id query parameters as mutually exclusive filters (if/elif chain). When a non-admin caller's user_id was forced to their own identity earlier in the handler, passing api_key or request_id would route into a different branch and the user_id filter would not be applied.

Fix

  • In the date-range path, the three filter blocks now use independent if statements so user_id, api_key, and request_id combine with AND semantics on the same filter_query.
  • In the non-date-range path, the api_key / request_id / user_id / else branches are collapsed into a single combined find_many call built from a merged filter_query. The no-filter else case still falls through to the existing get_data call.

Testing

  • Verified against a running proxy on :4000:
    • Non-admin callers can no longer retrieve another user's log via request_id or api_key, in both the date-range and non-date-range paths.
    • Non-admin callers still see their own logs via request_id, api_key, and date range.
    • Admin callers retain full access across all filter combinations.
  • poetry run pytest tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py -v — 56 passed.

Type

🐛 Bug Fix

Screenshots

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 12, 2026

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

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 12, 2026 6:42am

Request Review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 12, 2026

Greptile Summary

This PR fixes a user-scoping bypass in /spend/logs where non-admin callers could sidestep the forced user_id filter by supplying api_key or request_id, which fell into separate elif branches that ignored the scoped user. Both the date-range path (now uses independent if blocks building a combined filter_query) and the non-date-range path (now merges all filters into a single scoped_filter for one find_many call) are corrected, and the two previous review concerns — missing token hashing in the date-range path and missing unit tests — have been addressed.

Confidence Score: 5/5

Safe to merge — the security fix is correctly applied across all code paths and previous review concerns have been addressed.

Both previous P1 concerns (missing date-range token hashing, no unit tests) are resolved. The AND-combination logic is correct in all three branches (date-range/find_many, date-range/group_by, non-date-range/find_many). The only remaining gap is the absence of a test for the summarize=True group_by path, which is P2 since the fix there is symmetric and the code is straightforward.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/spend_tracking/spend_management_endpoints.py Security fix: replaces elif-chain with independent if-blocks so user_id scoping is AND-combined with api_key/request_id in both date-range and non-date-range paths; token hashing also added to date-range path.
tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py Adds five targeted mock tests verifying user+api_key, user+request_id, and sk- hashing for both date-range and non-date-range paths; no real network calls.

Reviews (3): Last reviewed commit: "refactor: rename filter var to satisfy m..." | Re-trigger Greptile

Comment on lines 2299 to 2300
if api_key is not None and isinstance(api_key, str):
filter_query["api_key"] = api_key # type: ignore
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 token hashing in date-range path

The new non-date-range branch correctly hashes tokens that start with "sk-" before filtering, but the date-range path still stores the raw value. Because the DB stores hashed keys, a call with a raw API key plus a date range will match zero records. The fix is to apply the same hashing logic used below (lines 2381-2385).

Comment on lines 2378 to +2401
else:
spend_logs = await prisma_client.get_data(
table_name="spend", query_type="find_all"
)
filter_query: Dict[str, Any] = {}
if api_key is not None and isinstance(api_key, str):
if api_key.startswith("sk-"):
hashed_token = prisma_client.hash_token(token=api_key)
else:
hashed_token = api_key
filter_query["api_key"] = hashed_token
if request_id is not None and isinstance(request_id, str):
filter_query["request_id"] = request_id
if user_id is not None and isinstance(user_id, str):
filter_query["user"] = user_id

if not filter_query:
spend_logs = await prisma_client.get_data(
table_name="spend", query_type="find_all"
)
return spend_logs

return spend_logs
data = await prisma_client.db.litellm_spendlogs.find_many(
where=filter_query, # type: ignore
order={"startTime": "desc"},
)
return data
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 No unit tests for the non-date-range security fix

The PR fixes the scoping bypass for non-admin callers but adds no unit tests for the view_spend_logs endpoint covering the fixed behavior. Specifically, there are no tests verifying that an INTERNAL_USER calling with api_key or request_id still gets results scoped to their own user, and that calling with another user's request id returns empty. Per project guidelines, new security-relevant code paths should have corresponding unit tests.

…ests

Brings the date-range branch in line with the non-date-range branch which
already hashes sk- prefixed tokens before querying. Adds coverage for
filter-combination behavior in view_spend_logs.
@yuneng-berri yuneng-berri temporarily deployed to integration-postgres April 12, 2026 06:41 — with GitHub Actions Inactive
@yuneng-berri yuneng-berri temporarily deployed to integration-postgres April 12, 2026 06:41 — with GitHub Actions Inactive
@yuneng-berri yuneng-berri temporarily deployed to integration-postgres April 12, 2026 06:41 — with GitHub Actions Inactive
@yuneng-berri yuneng-berri merged commit 26c35aa into litellm_yj_apr_11 Apr 13, 2026
59 of 63 checks passed
@yuneng-berri yuneng-berri deleted the litellm_spend_logs_user_scoping branch April 13, 2026 18:31
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.

1 participant