fix(proxy): reject non-finite spend via validate_finite_spend

A NaN/-inf spend would bypass spend >= max_budget enforcement. Add a
shared finite-value guard, defined above the litellm.proxy.* imports to
avoid the module-level cyclic-import warning.
This commit is contained in:
Andrii Butko 2026-06-24 13:57:36 +02:00
parent e0c8a6b483
commit e50c12e97b
2 changed files with 51 additions and 0 deletions

View file

@ -1,8 +1,27 @@
import math
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from fastapi import HTTPException, status
from pydantic import BaseModel
# Defined above the `litellm.proxy.*` imports so the name is bound even when
# this module is imported first through the proxy import cycle (CodeQL:
# module-level cyclic import). Depends only on `math` + `HTTPException`.
def validate_finite_spend(spend: Optional[float]) -> None:
"""Reject NaN/±inf spend before it reaches the DB / spend counter.
A non-finite spend would otherwise slip past `spend >= max_budget`
enforcement, since any comparison with NaN (and `-inf >= max_budget`)
is False, letting the entity keep spending past its configured budget.
"""
if spend is not None and not math.isfinite(spend):
raise HTTPException(
status_code=400,
detail={"error": f"spend must be a finite number. Received: {spend}"},
)
from litellm._logging import verbose_proxy_logger
from litellm.caching import DualCache
from litellm.proxy._types import (

View file

@ -570,3 +570,35 @@ class TestRequireCallerUserIdForNonAdmin:
assert exc_info.value.status_code == 403
assert "Service-account keys" in str(exc_info.value.detail)
class TestValidateFiniteSpend:
"""`validate_finite_spend` rejects NaN/±inf so a non-finite spend cannot
bypass `spend >= max_budget` enforcement (NaN/-inf compare false)."""
def test_none_is_allowed(self):
from litellm.proxy.management_endpoints.common_utils import (
validate_finite_spend,
)
assert validate_finite_spend(None) is None
def test_finite_value_is_allowed(self):
from litellm.proxy.management_endpoints.common_utils import (
validate_finite_spend,
)
assert validate_finite_spend(0.0) is None
assert validate_finite_spend(12.5) is None
@pytest.mark.parametrize("bad", [float("nan"), float("inf"), float("-inf")])
def test_non_finite_is_rejected(self, bad):
from fastapi import HTTPException
from litellm.proxy.management_endpoints.common_utils import (
validate_finite_spend,
)
with pytest.raises(HTTPException) as exc_info:
validate_finite_spend(bad)
assert exc_info.value.status_code == 400