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