4.5 KiB
4.5 KiB
Код-ревью 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(не покрыт ревью Соннета).