5.0 KiB
Аудит кода 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 сделан