diff --git a/History/README.md b/History/README.md index 0feb2df..5ae1c90 100644 --- a/History/README.md +++ b/History/README.md @@ -107,6 +107,7 @@ - **2026-08-24-folder-select-implemented.md** — v0.0.68: кнопка «Выбрать папку» (webkitdirectory, рекурсивно, относительный путь). - **2026-08-24-folder-zip-not-lost.md** — v0.0.69: архивы из папки без документов не теряются (добавляются как есть); раскрытие zip с документами; склонение счётчика. - **2026-08-24-pull-retry-limit-counter.md** — v0.0.72: ретраи pull из ВМ, корректная ошибка лимита сессии, счётчик дедупа. +- **2026-08-24-code-review-sonnet.md** — код-ревью Соннета: 15 багов (SSRF, path traversal, слабый SID, zip-бомба и др.), промпт в docs/code-review-sonnet.md. - **2026-08-24-upload-logic-schema.md** — подробная схема логики загрузки + найденные баги/несоответствия. - **2026-08-24-help-button-limits-text.md** — v0.0.70: заметная кнопка HELP (вместо «?») + пояснение статуса «пропущен» в ограничениях. - **2026-08-24-ui-locks-session-freeze.md** — v0.0.66: блокировки UI (выбор/удаление) на время работы, заморозка сессии + кнопка «Новая сессия». diff --git a/History/sonnet/2026-08-24-code-review-sonnet.md b/History/sonnet/2026-08-24-code-review-sonnet.md new file mode 100644 index 0000000..5fa15f4 --- /dev/null +++ b/History/sonnet/2026-08-24-code-review-sonnet.md @@ -0,0 +1,49 @@ +# Код-ревью 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` (не покрыт ревью Соннета).