_jsonb_param: json.dumps без allow_nan=False — NaN/Infinity ломают jsonb-INSERT, ран теряется молча #1580

Closed
opened 2026-06-16 17:08:10 +00:00 by bot-backend · 0 comments
Collaborator

Severity: medium · Категория: data-loss / silent-failure · Файл: backend/app/services/analysis_runs/repository.py:62-68

Файл: backend/app/services/analysis_runs/repository.py:62-68 (строка 68).

Код: return json.dumps(jsonable_encoder(value), ensure_ascii=False) — нет allow_nan=False, поэтому Python по умолчанию (allow_nan=True) выводит нестандартные литералы NaN, Infinity, -Infinity. Эта строка биндится в CAST(:result AS jsonb) (repository.py:110,124). JSONB-парсер PostgreSQL отвергает такие токены (invalid input syntax for type json ... Token "NaN" is invalid) → INSERT падает.

Импакт: persist_analysis_run ловит broad except Exception (repository.py:149) и возвращает None с одним logger.exception → ран НЕ сохраняется молча. result в analyze (parcels.py:2734-2793) — агрегат ~90 вычисленных float-метрик из эвристических суб-сервисов; round(...) не спасает (round(nan)=nan, round(inf)=inf), санитизации NaN/Inf на persist-пути нет. Любой NaN/Inf → весь ран теряется → forecast-таска (forecast.py:111-119) не находит базовый 'analyze-1.0' ран → форсайт никогда не считается, история анализов пустая, last_analysis_date остаётся None.

Фикс: добавить allow_nan=False в json.dumps и/или санитизировать result_payload (заменять nan/inf на None) перед persist; как минимум — не глотать сериализационную/INSERT-ошибку молча (различать ожидаемые DB-ошибки от программных). Каноничнее: json.dumps(jsonable_encoder(value), ensure_ascii=False, allow_nan=False) + pre-pass, заменяющий non-finite float'ы на None.

Почему баг: Дефолт json.dumps(allow_nan=True) производит невалидный JSON, который PostgreSQL jsonb отвергает; молчаливый except превращает это в тихую потерю рана вместо громкого падения. Ни один из ~30 json.dumps в backend не ставит allow_nan=False, но именно analysis_runs.result уникально уязвим: это агрегат вычисленных float-метрик (а не внешний geojson/props), и ошибка глотается best-effort'ом.

Как чинить: В _jsonb_param использовать json.dumps(jsonable_encoder(value), ensure_ascii=False, allow_nan=False) и обернуть в try/except ValueError с санитизацией (заменить NaN/Inf на None) перед записью — чтобы аномальное число не убивало весь ран. Либо прогонять payload через рекурсивную замену non-finite float на None до dumps.


deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.82 · unit B14

**Severity:** medium · **Категория:** data-loss / silent-failure · **Файл:** `backend/app/services/analysis_runs/repository.py:62-68` Файл: backend/app/services/analysis_runs/repository.py:62-68 (строка 68). Код: `return json.dumps(jsonable_encoder(value), ensure_ascii=False)` — нет `allow_nan=False`, поэтому Python по умолчанию (allow_nan=True) выводит нестандартные литералы NaN, Infinity, -Infinity. Эта строка биндится в `CAST(:result AS jsonb)` (repository.py:110,124). JSONB-парсер PostgreSQL отвергает такие токены (`invalid input syntax for type json ... Token "NaN" is invalid`) → INSERT падает. Импакт: persist_analysis_run ловит broad `except Exception` (repository.py:149) и возвращает None с одним logger.exception → ран НЕ сохраняется молча. result в analyze (parcels.py:2734-2793) — агрегат ~90 вычисленных float-метрик из эвристических суб-сервисов; round(...) не спасает (round(nan)=nan, round(inf)=inf), санитизации NaN/Inf на persist-пути нет. Любой NaN/Inf → весь ран теряется → forecast-таска (forecast.py:111-119) не находит базовый 'analyze-1.0' ран → форсайт никогда не считается, история анализов пустая, last_analysis_date остаётся None. Фикс: добавить `allow_nan=False` в json.dumps и/или санитизировать result_payload (заменять nan/inf на None) перед persist; как минимум — не глотать сериализационную/INSERT-ошибку молча (различать ожидаемые DB-ошибки от программных). Каноничнее: `json.dumps(jsonable_encoder(value), ensure_ascii=False, allow_nan=False)` + pre-pass, заменяющий non-finite float'ы на None. **Почему баг:** Дефолт json.dumps(allow_nan=True) производит невалидный JSON, который PostgreSQL jsonb отвергает; молчаливый except превращает это в тихую потерю рана вместо громкого падения. Ни один из ~30 json.dumps в backend не ставит allow_nan=False, но именно analysis_runs.result уникально уязвим: это агрегат вычисленных float-метрик (а не внешний geojson/props), и ошибка глотается best-effort'ом. **Как чинить:** В `_jsonb_param` использовать `json.dumps(jsonable_encoder(value), ensure_ascii=False, allow_nan=False)` и обернуть в try/except ValueError с санитизацией (заменить NaN/Inf на None) перед записью — чтобы аномальное число не убивало весь ран. Либо прогонять payload через рекурсивную замену non-finite float на None до dumps. --- <sub>deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.82 · unit B14</sub>
bot-backend added the
week ревью 1
label 2026-06-16 17:08:10 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1580
No description provided.