# Код-ревью DrHider — ответ Соннета (2026-08-24) _sonnet. Промпт: `docs/code-review-sonnet.md` (12 файлов кода, 8 вопросов)._ _Ревью по v0.0.72. Ничего не исправлено — только задокументировано._ ## 🔴 Критичные | Баг | Файл | ~строка | Суть | Фикс | |---|---|---|---|---| | **SSRF** | `api_bp.py` `upload_refs` | ~93 | `url` из клиентского JSON без валидации → `httpx.stream("GET", url)` + `follow_redirects=True`. Доступ к metadata/kube-apiserver | валидировать URL-prefix == `VM_UPLOAD_URL` до запроса | | **Path traversal / zip slip** | `api_bp.py` + `builder.py` | ~88, ~55 | `name = ref.get("name")` без санитизации → `../../evil.md` в выходном ZIP | `name = os.path.basename(ref.get("name",""))` | ## 🟠 Средние | Баг | Файл | ~строка | Суть | |---|---|---|---| | Серверная ошибка не отображается | `api_bp.py` + `index.html` | ~388, ~840 | сервер шлёт `event: error` — это встроенное имя EventSource; клиент не слушает кастомное → «SSE connection failed» вместо реального msg. Переименовать в `proc_error` | | idx-мисматч при `expand_zips` на бэке | `obfuscator.py` + `index.html` | ~90, sendIdx | фронт фильтрует zip по `.pdf/.doc/.docx/.txt/.md`, бэк расширяет ВСЕ файлы → число/порядок не совпадает → `sendIdx[idx]=undefined` | | Слабый SID (48 бит) | `session.py` | 85 | `uuid4().hex[:12]`; убрать `[:12]` → 128 бит | | ZIP-бомба: обход через поддельный `file_size` | `extractor.py` | ~240 | лимит проверяется по `info.file_size` (central dir, подделывается) ДО чтения; проверять ПОСЛЕ `zf.read()` | | Неатомарность pull при ошибке | `api_bp.py` `upload_refs` | ~115 | при исчерпании ретраев 502 БЕЗ `session_id`; частичная сессия-сирота до TTL | ## 🟡 Мелкие | Баг | Файл | ~строка | Суть | |---|---|---|---| | Отмена не работает в extract-фазе | `obfuscator.py` | ~130 | в extract-цикле нет `cancel_event.is_set()`; .doc (liberta 120с) → отмена ждёт все извлечения | | TOCTOU: сессия между check и pause_ttl | `api_bp.py` | ~165-171 | `get_files` (lock снят) → `pause_ttl`; маловероятно | | `fileTimers` не заполняется | `index.html` | ~792 | мёртвый код, fallback всегда 0.0с | | Self-XSS: `f.name` в innerHTML | `index.html` | ~280, ~380 | имя файла без escaping → `textContent`/`escapeHtml()` | | ZIP с non-doc расширениями → мусор | `extractor.py` | ~257 | фильтровать расширения в `expand_zips` | | Сессия не чистится после `/download` | `api_bp.py` | ~430 | 500МБ висит 30 мин → OOM; `cleanup(sid)` | | LLM-таймаут тихо обнуляет чанк | `llm_client.py` + `scanner.py` | ~70, ~220 | `except → return []`, PII не найден без предупреждения | | Debug-эндпоинт `session_files` | `api_bp.py` | ~139 | список файлов любой сессии по SID без токена | | LLM prompt injection | `scanner.py` | ~200 | текст документа дословно в prompt → подавление NER | ## Подтверждено корректным (Соннет) - Различимость «сессия не найдена» vs «лимит» ✓ - Гонок на `cancel_event`/`queue` нет (thread-safe) ✓ - Утечек httpx/zipfile/EventSource/таймеров нет ✓ - Пустой txt, битый pdf, 0 файлов/все сверх лимита — обрабатываются ✓ ## Что уже исправлено ранее (до этого ревью) - Ретраи pull (v0.0.72) — Соннет отметил остаточную неатомарность. - Разделение статусов «пропущен (лимит)» / «не извлечён» (v0.0.71). - Счётчик дедупа (v0.0.72). ## TODO-связь - Открытый баг кнопки «Обфусцировать» при 0 файлов — `docs/WhatTODO.md` (не покрыт ревью Соннета).