Skip to content

fix: tighten budget field validation and authorization checks#27897

Merged
krrish-berri-2 merged 11 commits into
litellm_internal_stagingfrom
fix/budget-security-ghsa-wvg4-q775-2rv4
May 14, 2026
Merged

fix: tighten budget field validation and authorization checks#27897
krrish-berri-2 merged 11 commits into
litellm_internal_stagingfrom
fix/budget-security-ghsa-wvg4-q775-2rv4

Conversation

@krrish-berri-2

@krrish-berri-2 krrish-berri-2 commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Tighten input validation on budget-related fields across management endpoints
  • Add missing authorization checks on user self-update paths
  • Enforce caller-scoped limits when generating keys without explicit config

Test plan

  • make test-unit passes
  • Existing budget endpoint tests updated for revised error messages

@CLAassistant

CLAassistant commented May 14, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@krrish-berri-2 krrish-berri-2 changed the title fix: block NaN/Inf budget bypass and add missing non-admin guards (GHSA-wvg4 / GHSA-q775 / GHSA-2rv4) fix: tighten budget field validation and authorization checks May 14, 2026
@krrish-berri-2 krrish-berri-2 changed the base branch from main to litellm_internal_staging May 14, 2026 03:21
@krrish-berri-2 krrish-berri-2 requested a review from a team May 14, 2026 03:21
@codspeed-hq

codspeed-hq Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Congrats! CodSpeed is installed 🎉

🆕 16 new benchmarks were detected.

You will start to see performance impacts in the reports once the benchmarks are run from your default branch.

Detected benchmarks


Open in CodSpeed

Addresses three security issues:

GHSA-wvg4-6222-3q4r: /user/update exposes max_budget, soft_budget, spend
to self-editing non-admin users with no server-side guard. Non-admin callers
now receive HTTP 403 if any of those fields appear in the update payload.

GHSA-q775-qw9r-2r4g: _enforce_upperbound_key_params returned early (no-op)
when upperbound_key_generate_params was absent from config, letting any
authenticated user generate a key with unlimited max_budget. Fix adds a
delegated-authority ceiling in _common_key_generation_helper: non-admins
cannot grant a key more budget than their own key carries.

GHSA-2rv4-xv66-fpjg: float('nan') passes every `value < 0` guard because
nan < 0 is False in Python, and spend >= nan is always False, permanently
disabling budget enforcement for any entity carrying a NaN max_budget.
All write-time budget guards now use `not math.isfinite(v) or v < 0`.
_enforce_upperbound_key_params validates finiteness unconditionally (before
the early-return). All spend-enforcement comparisons in auth_checks.py are
now guarded with math.isfinite(max_budget) as defense-in-depth.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
@krrish-berri-2 krrish-berri-2 force-pushed the fix/budget-security-ghsa-wvg4-q775-2rv4 branch from 9105db7 to d0368bc Compare May 14, 2026 03:22
@codecov

codecov Bot commented May 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.15789% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...oxy/management_endpoints/organization_endpoints.py 20.00% 4 Missing ⚠️
litellm/proxy/auth/auth_checks.py 66.66% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens budget enforcement across the LiteLLM proxy by (1) adding math.isfinite() guards to every budget comparison to block NaN/Inf bypass attacks (GHSA-2rv4-xv66-fpjg), (2) adding a delegated-authority ceiling that prevents non-admin callers from generating keys whose max_budget exceeds their own, and (3) blocking non-admin users from writing budget fields on their own user record.

  • NaN/Inf validation is applied consistently at input (management endpoints) and at enforcement time (auth_checks.py) across keys, users, teams, orgs, and the global proxy budget.
  • Key generation ceiling (_common_key_generation_helper): _requested_max_budget is captured before default_key_generate_params and upperbound_key_generate_params fill it, so system-configured defaults do not trigger the ceiling check.
  • User self-escalation guard (_update_single_user_helper): max_budget, soft_budget, and spend are protected from self-modification by non-PROXY_ADMIN callers; six regression tests cover the GHSA scenarios plus admin bypass.

Confidence Score: 3/5

The NaN/Inf validation and GHSA fixes are mechanically correct, but the authorization changes for user-update and key-update paths are backwards-incompatible for deployments using org-admin or team-admin roles to manage budgets, and these concerns remain open from the prior review round.

The core NaN/Inf defense and delegated-authority ceiling are correctly implemented. However, the self-escalation guard in _update_single_user_helper blocks every non-PROXY_ADMIN role including org admins and team admins from writing budget fields on records they previously managed. Deployments relying on those admin tiers will start receiving 403s with no opt-out path. These backwards-incompatible changes have not been gated behind a feature flag as the repository rule for breaking changes requires.

internal_user_endpoints.py (self-escalation guard role coverage) and key_management_endpoints.py (delegated-authority ceiling and key-update authorization) carry the highest deployment risk.

Important Files Changed

Filename Overview
litellm/proxy/auth/auth_checks.py Adds math.isfinite() guard before every budget comparison to prevent NaN-based enforcement bypass; consistent and correct across all callsites.
litellm/proxy/management_endpoints/budget_management_endpoints.py Extends max_budget/soft_budget validation from not-negative to finite-and-not-negative in both /budget/new and /budget/update; error messages updated consistently.
litellm/proxy/management_endpoints/internal_user_endpoints.py Adds GHSA-wvg4 self-escalation guard blocking non-PROXY_ADMIN callers from writing max_budget/soft_budget/spend on their own record; also blocks ORG_ADMIN/TEAM_ADMIN roles from budget management (already flagged in previous review rounds).
litellm/proxy/management_endpoints/key_management_endpoints.py Adds NaN/Inf validation in _enforce_upperbound_key_params; adds GHSA-q775 delegated-authority ceiling with _requested_max_budget correctly captured before default-param fill-in. Key-update budget path protected via _check_key_admin_access. Backwards-compatibility concerns from prior rounds remain open.
litellm/proxy/management_endpoints/organization_endpoints.py Extends org create/update budget validation to include math.isfinite() check.
litellm/proxy/management_endpoints/team_endpoints.py Extends team create/update budget validation to include math.isfinite() guard; symmetric with other endpoint files.
tests/test_litellm/proxy/management_endpoints/test_budget_endpoints.py Four existing tests updated to match revised error message; assertions tightened, not weakened.
tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py Adds three regression tests for GHSA-wvg4: non-admin self-escalation blocked, spend reset blocked, PROXY_ADMIN cross-user update allowed.
tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py Adds six regression tests for GHSA-q775 covering unlimited delegation, ceiling enforcement, within-ceiling allowed, default-fill bypass, and admin bypass.

Reviews (9): Last reviewed commit: "chore: trigger re-review" | Re-trigger Greptile

Comment thread litellm/proxy/management_endpoints/key_management_endpoints.py
Comment thread litellm/proxy/management_endpoints/key_management_endpoints.py Outdated
…-q775)

Non-admin callers whose API key has no explicit max_budget (None) could
bypass the delegated-authority ceiling and create keys with arbitrary
budgets. Now blocks budget assignment when caller has no budget configured.
Also removes redundant inline import of LitellmUserRoles.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread litellm/proxy/management_endpoints/key_management_endpoints.py Outdated
Capture the caller-supplied max_budget before _enforce_upperbound_key_params
can fill it with a default, so auto-filled defaults don't trigger the
ceiling guard for non-admin users with no budget on their own key.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review


if (
user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value
and data.max_budget is not None

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.

High: Delegated budget can be omitted

The ceiling only runs when the child key supplies max_budget. An authenticated non-admin with a capped key can call /key/generate without max_budget and omit user_id, producing a new key that is not capped by the caller's budget; require generated keys to inherit the caller's limit or reject missing max_budget for capped non-admin callers.

fallback_spend=0.0,
)
if window_spend >= w["max_budget"]:
if math.isfinite(w["max_budget"]) and window_spend >= w["max_budget"]:

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.

High: Non-finite window budgets are treated as unlimited

budget_limits[].max_budget is accepted from key and team request bodies, but it is not included in the finite-number validation added here. Because this runtime check skips NaN/Inf, an authenticated caller who can create a key budget window can submit a non-finite window budget and that window will never block spend; reject non-finite nested budget values at write time and treat non-finite values as invalid during enforcement.

@veria-ai

veria-ai Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Budget validation and authorization checks tightened

This PR adds finite-number validation to several budget management paths and adds non-admin self-update guards around user budget fields. I reviewed the changed runtime budget checks, key generation ceiling logic, and user update authorization flow for new attacker-reachable bypasses in the diff.


Status: 5 open
Risk: 2/10

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread litellm/proxy/management_endpoints/key_management_endpoints.py Outdated
Move _requested_max_budget capture before both default_key_generate_params
and upperbound_key_generate_params mutations, so auto-filled values don't
trigger the ceiling check for non-admin users.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread litellm/proxy/management_endpoints/key_management_endpoints.py Outdated
Callers with max_budget=None (unlimited) can legitimately create
budget-capped keys. Only block when caller has an explicit budget
and the requested budget exceeds it.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

)
},
)
if _requested_max_budget > user_api_key_dict.max_budget:

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.

High: Budget multiplication through generated keys

This compares each requested child key budget to the caller's key budget independently. A non-admin caller with a $100 key can call /key/generate repeatedly with max_budget=100 and then spend $100 on each generated key, because virtual-key spend is enforced per token unless there is a separate user/team budget backing it. Require generated keys to share an aggregate budget with the caller, cap by remaining delegated budget, or reject non-admin budget delegation unless a user/team-level budget enforces the total.

Comment thread litellm/proxy/management_endpoints/internal_user_endpoints.py Outdated
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

@krrish-berri-2 krrish-berri-2 enabled auto-merge (squash) May 14, 2026 04:20
fallback_spend=user_object.spend or 0.0,
)
if user_spend >= user_budget:
if math.isfinite(user_budget) and user_spend >= user_budget:

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.

Medium: Non-finite user budget bypass

The runtime budget check skips enforcement for non-finite user_budget values. If /user/new accepts max_budget without finite-number validation, a caller allowed to create users can send NaN or Infinity and receive a user key whose user-level spend cap is skipped here. Fail closed at enforcement or add equivalent validation on user creation.

Suggested change
if math.isfinite(user_budget) and user_spend >= user_budget:
if not math.isfinite(user_budget) or user_spend >= user_budget:

- Extract inline audit log block from _update_single_user_helper into
  _schedule_user_update_audit_log to bring statement count below 50
- Update test_validate_max_budget assertion to match current error message
  ("must be a non-negative finite number" not "cannot be negative")

Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
# Non-admins cannot modify budget-sensitive fields even on their own record
# (GHSA-wvg4-6222-3q4r).
if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value:
_protected_fields = ("max_budget", "soft_budget", "spend")

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.

High: User budget reset bypass

budget_duration is accepted on self-updates, and _update_internal_user_params() converts it into a new budget_reset_at. A non-admin user with a capped budget can call /user/update on their own record with budget_duration: "1s", causing their spend to reset every second instead of using the admin-configured budget window.

Suggested change
_protected_fields = ("max_budget", "soft_budget", "spend")
_protected_fields = ("max_budget", "soft_budget", "spend", "budget_duration")

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

krrish-berri-2 and others added 3 commits May 13, 2026 21:35
The merge of litellm_internal_staging into this branch dropped the
closing `)` from verbose_proxy_logger.warning(...) in
_schedule_user_update_audit_log, causing a SyntaxError at import time.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
…on tests

The budget protection in /user/update was blocking ALL non-PROXY_ADMIN users
from modifying budget fields, even when updating other users' records (which
can_user_call_user_update already restricts). Scoping the guard to self-updates
makes the intent clearer and avoids false-positive backwards-incompatibility
concerns for admin workflows.

Adds three regression tests for GHSA-wvg4-6222-3q4r:
- Non-admin self-escalation of max_budget → blocked
- Non-admin self-escalation of spend → blocked
- Proxy admin updating another user's budget → allowed

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

krrish-berri-2 and others added 2 commits May 13, 2026 22:15
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Satisfies MyPy union-attr check: prisma_client can be None at import
time so guard early before accessing .db.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
@krrish-berri-2 krrish-berri-2 merged commit 410ce76 into litellm_internal_staging May 14, 2026
41 of 42 checks passed
@krrish-berri-2 krrish-berri-2 deleted the fix/budget-security-ghsa-wvg4-q775-2rv4 branch May 14, 2026 05:41
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…I#27897)

* fix: block NaN/Inf budget bypass and add missing non-admin guards

Addresses three security issues:

GHSA-wvg4-6222-3q4r: /user/update exposes max_budget, soft_budget, spend
to self-editing non-admin users with no server-side guard. Non-admin callers
now receive HTTP 403 if any of those fields appear in the update payload.

GHSA-q775-qw9r-2r4g: _enforce_upperbound_key_params returned early (no-op)
when upperbound_key_generate_params was absent from config, letting any
authenticated user generate a key with unlimited max_budget. Fix adds a
delegated-authority ceiling in _common_key_generation_helper: non-admins
cannot grant a key more budget than their own key carries.

GHSA-2rv4-xv66-fpjg: float('nan') passes every `value < 0` guard because
nan < 0 is False in Python, and spend >= nan is always False, permanently
disabling budget enforcement for any entity carrying a NaN max_budget.
All write-time budget guards now use `not math.isfinite(v) or v < 0`.
_enforce_upperbound_key_params validates finiteness unconditionally (before
the early-return). All spend-enforcement comparisons in auth_checks.py are
now guarded with math.isfinite(max_budget) as defense-in-depth.


* fix: close budget ceiling bypass for callers with no max_budget (GHSA-q775)

Non-admin callers whose API key has no explicit max_budget (None) could
bypass the delegated-authority ceiling and create keys with arbitrary
budgets. Now blocks budget assignment when caller has no budget configured.
Also removes redundant inline import of LitellmUserRoles.


* fix: only apply budget ceiling to explicitly requested max_budget

Capture the caller-supplied max_budget before _enforce_upperbound_key_params
can fill it with a default, so auto-filled defaults don't trigger the
ceiling guard for non-admin users with no budget on their own key.


* fix: capture requested max_budget before any defaults are applied

Move _requested_max_budget capture before both default_key_generate_params
and upperbound_key_generate_params mutations, so auto-filled values don't
trigger the ceiling check for non-admin users.


* fix: allow unlimited-budget callers to delegate any budget

Callers with max_budget=None (unlimited) can legitimately create
budget-capped keys. Only block when caller has an explicit budget
and the requested budget exceeds it.


---------

Co-authored-by: yuneng-jiang <[email protected]>
Co-authored-by: ryan-crabbe-berri <[email protected]>
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.

3 participants