bulk_enqueue_geo (/geo/bulk): невалидный thematic_id в середине списка → 400 при уже запущенных geo-jobs (partial execution, нет atomicity/idempotency) #1562

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

Severity: medium · Категория: idempotency/partial-failure + input-validation · Файл: backend/app/api/v1/admin_scrape.py:894-962

Файл admin_scrape.py:894-962 (модель — :768). BulkGeoEnqueueRequest.thematic_ids: list[int] = Field(default=[2]) объявлено без enum/валидатора членства в {1,2,5}; pydantic пропускает любой int. Единственная проверка — внутри цикла на :895 (if thematic_id not in _THEMATIC_META: raise HTTPException(400)). Цикл for thematic_id in payload.thematic_ids (:894) идёт по порядку и для каждого валидного id СРАЗУ: собирает cad (:903-906), бьёт на чанки, создаёт jobs через enqueue_helper (:936) и дикетчит их process_nspd_geo_job.apply_async(args=[job_id], queue=geo_queue) (:949). ИМПАКТ: запрос thematic_ids=[2,99] → для 2 уже создаются строки в nspd_geo_jobs и задачи летят в очередь geo (реально жгут NSPD-запросы), затем на 99 бросается HTTPException 400 'thematic_id=99 не поддерживается'. Клиент получает 400 (запрос как бы отклонён), но БД+брокер содержат запущенные jobs. Нет ни rollback job-строк, ни revoke задач. Оператор видит ошибку, исправляет список и повторяет → дубль jobs для thematic_id=2, удвоенная нагрузка на NSPD (WAF-risk) и двойной счёт targets. ФИКС: провалидировать ВЕСЬ payload.thematic_ids ДО создания любого job — либо pydantic field_validator/Enum на поле thematic_ids (отвергать на уровне 422 до тела хендлера), либо предварительный проход в начале хендлера (for tid in payload.thematic_ids: if tid not in _THEMATIC_META: raise 400) перед тем как что-либо создавать/дикетчить.

Почему баг: Контракт endpoint'а нарушен: ответ 400 (запрос отклонён) при фактически выполненных и работающих side-effect'ах. Оператор видит ошибку, повторяет запрос с исправленным списком → дубль jobs для thematic_id=2, удвоенная нагрузка на NSPD (WAF-risk) и двойной счёт targets. Правильно — провалидировать ВЕСЬ payload.thematic_ids до создания любого job'а (либо через pydantic-валидатор/enum на поле, либо отдельным предварительным проходом), иначе работа должна быть атомарной.

Как чинить: Валидировать все thematic_ids до начала цикла создания jobs: либо ограничить поле через pydantic (например Field(...) + field_validator проверяющий подмножество {1,2,5}), либо в начале хендлера сделать bad = [t for t in payload.thematic_ids if t not in _THEMATIC_META]; if bad: raise HTTPException(400, ...) ПЕРЕД любым enqueue_helper/apply_async.


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

**Severity:** medium · **Категория:** idempotency/partial-failure + input-validation · **Файл:** `backend/app/api/v1/admin_scrape.py:894-962` Файл admin_scrape.py:894-962 (модель — :768). BulkGeoEnqueueRequest.thematic_ids: list[int] = Field(default=[2]) объявлено без enum/валидатора членства в {1,2,5}; pydantic пропускает любой int. Единственная проверка — внутри цикла на :895 (if thematic_id not in _THEMATIC_META: raise HTTPException(400)). Цикл for thematic_id in payload.thematic_ids (:894) идёт по порядку и для каждого валидного id СРАЗУ: собирает cad (:903-906), бьёт на чанки, создаёт jobs через enqueue_helper (:936) и дикетчит их process_nspd_geo_job.apply_async(args=[job_id], queue=geo_queue) (:949). ИМПАКТ: запрос thematic_ids=[2,99] → для 2 уже создаются строки в nspd_geo_jobs и задачи летят в очередь geo (реально жгут NSPD-запросы), затем на 99 бросается HTTPException 400 'thematic_id=99 не поддерживается'. Клиент получает 400 (запрос как бы отклонён), но БД+брокер содержат запущенные jobs. Нет ни rollback job-строк, ни revoke задач. Оператор видит ошибку, исправляет список и повторяет → дубль jobs для thematic_id=2, удвоенная нагрузка на NSPD (WAF-risk) и двойной счёт targets. ФИКС: провалидировать ВЕСЬ payload.thematic_ids ДО создания любого job — либо pydantic field_validator/Enum на поле thematic_ids (отвергать на уровне 422 до тела хендлера), либо предварительный проход в начале хендлера (for tid in payload.thematic_ids: if tid not in _THEMATIC_META: raise 400) перед тем как что-либо создавать/дикетчить. **Почему баг:** Контракт endpoint'а нарушен: ответ 400 (запрос отклонён) при фактически выполненных и работающих side-effect'ах. Оператор видит ошибку, повторяет запрос с исправленным списком → дубль jobs для thematic_id=2, удвоенная нагрузка на NSPD (WAF-risk) и двойной счёт targets. Правильно — провалидировать ВЕСЬ payload.thematic_ids до создания любого job'а (либо через pydantic-валидатор/enum на поле, либо отдельным предварительным проходом), иначе работа должна быть атомарной. **Как чинить:** Валидировать все thematic_ids до начала цикла создания jobs: либо ограничить поле через pydantic (например `Field(...)` + field_validator проверяющий подмножество {1,2,5}), либо в начале хендлера сделать `bad = [t for t in payload.thematic_ids if t not in _THEMATIC_META]; if bad: raise HTTPException(400, ...)` ПЕРЕД любым enqueue_helper/apply_async. --- <sub>deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.9 · unit B03</sub>
bot-backend added the
week ревью 1
label 2026-06-16 17:05:32 +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#1562
No description provided.