mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
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:
parent
e0c8a6b483
commit
e50c12e97b
2 changed files with 51 additions and 0 deletions
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue