Skip to content

Commit 96e59ba

Browse files
author
amabito
committed
fix(evaluators): budget R8 -- reject NaN/Inf limit_usd in config validation
R8 finding: float("nan") passed the `v <= 0` validator (IEEE 754: nan <= 0 is False). NaN limit_usd silently disabled budget enforcement because all NaN comparisons return False. Fix: added math.isfinite(v) guard to validate_limit_usd. Tests: NaN and Inf limit_usd rejection. 61 budget tests, 291 total evaluator tests passing.
1 parent 5a100f8 commit 96e59ba

2 files changed

Lines changed: 19 additions & 2 deletions

File tree

evaluators/builtin/src/agent_control_evaluators/budget/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import math
56
from typing import Any, Literal
67

78
from pydantic import Field, field_validator, model_validator
@@ -43,8 +44,8 @@ def at_least_one_limit(self) -> "BudgetLimitRule":
4344
@field_validator("limit_usd")
4445
@classmethod
4546
def validate_limit_usd(cls, v: float | None) -> float | None:
46-
if v is not None and v <= 0:
47-
raise ValueError("limit_usd must be positive")
47+
if v is not None and (not math.isfinite(v) or v <= 0):
48+
raise ValueError("limit_usd must be a finite positive number")
4849
return v
4950

5051
@field_validator("limit_tokens")

evaluators/builtin/tests/budget/test_budget.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,22 @@ def test_negative_limit_rejected(self) -> None:
220220
with pytest.raises(ValidationError, match="positive"):
221221
BudgetLimitRule(limit_usd=-1.0)
222222

223+
def test_nan_limit_usd_rejected(self) -> None:
224+
"""float('nan') passes v <= 0 check but must still be rejected.
225+
226+
IEEE 754: nan <= 0 is False, so without an explicit isfinite guard,
227+
nan silently passes validation. A nan limit_usd causes utilization=nan
228+
and exceeded=False always (all nan comparisons are False), permanently
229+
disabling the budget limit -- a silent security bypass.
230+
"""
231+
with pytest.raises(ValidationError, match="finite"):
232+
BudgetLimitRule(limit_usd=float("nan"))
233+
234+
def test_inf_limit_usd_rejected(self) -> None:
235+
"""float('inf') limit is accepted by v <= 0 but means limit never triggers."""
236+
with pytest.raises(ValidationError, match="finite"):
237+
BudgetLimitRule(limit_usd=float("inf"))
238+
223239
def test_token_only_limit(self) -> None:
224240
rule = BudgetLimitRule(limit_tokens=1000)
225241
assert rule.limit_usd is None

0 commit comments

Comments
 (0)