65 lines
5.0 KiB
Markdown
65 lines
5.0 KiB
Markdown
# Аудит кода contracts-flask — 2026-06-30
|
||
|
||
## Контекст
|
||
|
||
После переименования `services/` → `compare/` и восстановления сервиса проведён аудит кодовой базы.
|
||
Фокус: **что реально упадёт, сломается или работает нестабильно**. Без вылизывания.
|
||
Сервис непубличный, дев-окружение. Auth пока не нужен.
|
||
|
||
---
|
||
|
||
## ⚠️ Исправлено в этом цикле
|
||
|
||
### #1. POST-хендлеры без try вокруг Content-Length и json.loads
|
||
**Файл:** `deploy/convert_server.py`
|
||
**Проблема:** `_handle_api_sync`, `_handle_api_groups`, `_handle_apply_groups`, промпт-хендлеры — все делают `json.loads(self.rfile.read(length))` без try. Битый Content-Length → ValueError, пустой body → JSONDecodeError. Оба не ловятся → оборванное соединение.
|
||
**Фикс:** обёрнуто в try/except с возвратом `{"ok": false, "error": "..."}`.
|
||
|
||
### #11. ZIP-бомба в DrHider
|
||
**Файл:** `deploy/drhider/drhider.py`
|
||
**Проблема:** проверяется только заявленный `info.file_size` (можно подделать), нет лимита фактически распакованных байт, нет проверки ratio сжатия.
|
||
**Фикс:** скопирована защита из `compare/unzip.py` — накопительный счётчик распакованных байт + ratio-проверка (100×).
|
||
|
||
### #6. classify сбрасывает status у уже классифицированных документов
|
||
**Файл:** `deploy/compare/classify.py`
|
||
**Проблема:** `reset_classify_status(batch_id)` в начале `classify_batch()` сбрасывает на 'pending' ВСЕ документы батча, включая уже классифицированные. При повторном запуске — жжёт токены LLM на переклассификацию.
|
||
**Фикс:** `reset_classify_status` теперь сбрасывает только `'processing'` (crash recovery), оставляя `'classified'` и `'garbage'` нетронутыми.
|
||
|
||
---
|
||
|
||
## 🟡 Известные проблемы (не исправлены — низкий приоритет)
|
||
|
||
### SSE: двойная запись в мёртвый сокет
|
||
`convert_server.py` — при отвале клиента `run_pipeline` бросает `BrokenPipeError`, except ловит и **снова** зовёт `_sse` → второе исключение. Грязные трейсы, но не крашит сервер.
|
||
|
||
### classify: «успешный» JSON от LLM, который не dict
|
||
`classify.py` — `json.loads` может вернуть список/строку вместо dict → `AttributeError` → документ молча помечается failed. Случается редко.
|
||
|
||
### .doc через /upload не работает
|
||
`parse.py` зовёт `_parse_docx` (python-docx) для `.doc`, а тот читает только OOXML/.docx. Формат разрешён в `upload.py`, но не парсится. Надо либо убрать `.doc` из whitelist, либо звать `/convert-doc`.
|
||
|
||
### Гонка счётчиков в ThreadPoolExecutor
|
||
`classify.py` мутирует `garbage`, `json_total`, `type_counts` из 4 потоков через `nonlocal` без локов. Счётчики могут привирать на 1-2 единицы.
|
||
|
||
### _safe_json_parse — 4 уровня эвристик
|
||
Реаниматор битого JSON. Лечит симптом «LLM обрезал ответ». Дописывание кавычек/скобок вслепую может дать валидный но мусорный JSON → тихая неверная классификация.
|
||
|
||
### Весь файл в base64 в БД
|
||
`upload.py` хранит `original_bytes` как base64 (+33%) в Postgres. `SELECT *` таскает мегабайты. База пухнет.
|
||
|
||
### LLM URL/key захардкожены в 3 местах
|
||
`classify.py`, `drhider_server.py`, `llm_client.py`. Рассинхрон — тот же класс проблемы, что с `DB_NAME`.
|
||
|
||
### DrHider: PDF→txt + потеря форматирования DOCX
|
||
PDF на выходе становится `.txt`. Замена в DOCX сваливает текст в первый run → форматирование плывёт.
|
||
|
||
---
|
||
|
||
## ✅ Что НЕ является проблемой (проверено)
|
||
|
||
- SQL-инъекций нет — весь `db/` на параметризованных запросах
|
||
- Path traversal в static-раздаче прикрыт `realpath`+`startswith`
|
||
- Path traversal в ZIP-распаковке прикрыт (`unzip.py`, `drhider.py`)
|
||
- Nginx защищает от больших тел (client_max_body_size)
|
||
- DrHider двойной encode fix сделан
|