From 1a8062ad343263767fee7fd42d0d43ff678e1555 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 29 Jul 2026 06:20:43 -0500 Subject: [PATCH] Reject non-finite numbers in GenAI review descriptions A model returning NaN for confidence or potential_threat_level slipped through the model_construct fallback, which skips validation, and was written into the review segment's JSON data. NaN is not valid JSON, so every subsequent /review request failed with "Out of range float values are not JSON compliant", blanking the review page for any time range containing the poisoned row. --- frigate/genai/__init__.py | 10 ++++++++++ frigate/util/builtin.py | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/frigate/genai/__init__.py b/frigate/genai/__init__.py index 3aca4a8fb4..4466e2a652 100644 --- a/frigate/genai/__init__.py +++ b/frigate/genai/__init__.py @@ -23,6 +23,7 @@ from frigate.genai.prompts import ( build_review_summary_prompt, ) from frigate.models import Event +from frigate.util.builtin import has_non_finite_number logger = logging.getLogger(__name__) @@ -164,6 +165,15 @@ class GenAIClient: except json.JSONDecodeError as je: logger.error("Failed to parse review description JSON: %s", je) return None + + # model_construct skips validation, so non-finite numbers that + # the validated path would have rejected have to be caught here + if has_non_finite_number(raw): + logger.error( + "Discarding review description containing non-finite numbers." + ) + return None + # observations and confidence are required on the model; fill an empty default # if the response omitted it so attribute access stays safe. raw.setdefault("observations", []) diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py index 35dba2f3a7..c1a6e787cb 100644 --- a/frigate/util/builtin.py +++ b/frigate/util/builtin.py @@ -472,6 +472,18 @@ def sanitize_float(value): return value +def has_non_finite_number(value: Any) -> bool: + """Return True if any number in a parsed JSON value is NaN or infinite.""" + if isinstance(value, float): + return not math.isfinite(value) + if isinstance(value, dict): + return any(has_non_finite_number(v) for v in value.values()) + if isinstance(value, list): + return any(has_non_finite_number(v) for v in value) + + return False + + def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float: return 1 - cosine_distance(a, b)