# Аудит для Sonnet — пофайловый разбор Ты должен прочитать КАЖДЫЙ файл из списка ниже и выдать по нему детальный отчёт. ## Формат отчёта ПОФАЙЛОВО Для каждого файла: ``` ### convert_server.py | Строка | Тип | Серьёзность | Что не так | Как исправить | |--------|-----|-------------|------------|---------------| | 242 | dead code | low | Дубликат _handle_cleanup | Удалить второй | ``` После пофайлового разбора — СВОДНАЯ ТАБЛИЦА всех проблем по серьёзности: critical → high → medium → low. --- ## Файл 1: `contractor/deploy/convert_server.py` **Что это:** HTTP роутер на http.server + ThreadingMixIn. Все endpoint'ы приложения. **Что смотреть:** - ВСЕ `do_GET`, `do_POST`, `do_DELETE` — правильная диспетчеризация? Нет мёртвых путей? - `_handle_process_v2` — валидация UUID? SSE корректно закрывается при ошибке? Утечка соединений? - `_handle_upload` — размер тела? Content-Type проверка? Таумаут? - `_handle_cleanup` — ДВА определения (строки ~242 и ~255). Второй перекрывает первый. Dead code. Порядок DELETE правильный? - `_handle_api_sync` — читает JSON body. Что если тело пустое/битое? Что если keep_ids содержит 10000 id? Нет лимита. - `_handle_api_document` — doc_id из URL без валидации UUID → psycopg2.InvalidTextRepresentation на "fake-id". - `_handle_api_document_delete` — cascade порядок: spec_current → spec_events → supplements → document. Правильный? - `_handle_classify_batch` — batch_id из JSON без валидации UUID. - `_handle_apply_groups` — валидация структуры groups? - `_handle_api_prompts_*` — role из query params без валидации. - `_json()` — экранирует ли спецсимволы в данных? - `_sse()` — f-string с json.dumps. Данные от LLM могут содержать спецсимволы. - Импорт `execute` на строке 12 — не конфликтует с другими импортами? - `ALTER TABLE ADD COLUMN IF NOT EXISTS` — права на DDL? Идемпотентно? - CORS — `_send_cors()` вызывается везде? OPTIONS? --- ## Файл 2: `contractor/deploy/app.js` **Что это:** Весь фронтенд (42KB). Загрузка, классификация, группы, сравнение, промпты. **Что смотреть:** - `fileQueue`, `contractId`, `batchId` — глобальные. Где расходятся с сервером? - `renderTable()` — `innerHTML` из `fileQueue[].name`, `fileQueue[].status`. XSS? - `fileInput change` — гонка удаления старых + upload новых? - `syncDB()` — fire-and-forget, без await, без проверки ответа. - `runClassify()` — утечка таймеров при ошибке? - `loadGroups()` — ЕДИНСТВЕННОЕ место где diffBody. Больше никто не должен. - `runCompareForGroup()` — compareCard. Все ссылки compareBody/compareStatus? - `llmBtn` — compareCard. То же. - SSE handler'ы — ДВА почти идентичных. Дублирование. - `showText()` — `escHtml(classify_raw)` ок, но `counterparty`, `own_number` — НЕ экранированы! XSS. - `escHtml()` — экранирует `<>&"'`? - `stepDone/Active/resetStepper` — innerHTML через replace. Спецсимволы в id? - `window._groupsData` — устаревает после remove→re-classify. runCompareForGroup использует индекс. - `showClassifyBtn()` — идемпотентно? - `lucide.createIcons()` — не ломает onclick? --- ## Файл 3: `contractor/deploy/app_utils.js` **Что это:** Утилиты: форматирование, removeFile, escHtml, модалки. **Что смотреть:** - `removeFile()` — syncDB+resetStepper+showClassifyBtn через typeof. Если нет — молча. - `formatDate()`, `formatSize()` — null/undefined safe? - `escHtml()` — все опасные символы: `< > & " '`? - `moveUp/Down` — не вызывают syncDB (правильно, состав не меняется). --- ## Файл 4: `contractor/deploy/services/classify.py` **Что это:** LLM-классификация. ThreadPoolExecutor, выжимка, вызов LLM. **Что смотреть:** - `classify_batch()` — reset + list_pending. Гонка между ними? - `_classify_one()` — `_smart_extract` может упасть до LLM. Обрабатывается? - `_smart_extract()` — неожиданная структура elements_json? - `_call_llm_classify()` — httpx `verify=False`. MITM уязвимость. - `_safe_json_parse()` — edge cases: Null/None, числа без кавычек, пустой ответ. - `MAX_WORKERS=4` — 4 одновременных запроса создают каждый свой пул? - `LLM_KEY` из env — если не задан? - API key в коде? (берётся из env, ок) --- ## Файл 5: `contractor/deploy/services/grouping.py` **Что это:** Группировка документов по контрактам + виртуальные группы. **Что смотреть:** - `group_documents()` — виртуальные группы: что если parent_number и own_number оба null? - Коллизия normalize_number: разные номера → одинаковый нормализованный. - Сортировка по doc_date: None у всех → нестабильный порядок. - `apply_groups()` — нет проверки на существующий contract (дубликат при повторе). - `supplements_list.remove(s)` внутри цикла for — может пропускать элементы. --- ## Файл 6: `contractor/deploy/services/process.py` **Что это:** SSE пайплайн сравнения. **Что смотреть:** - `run_pipeline()` — битая ссылка supplement→document? timeout? retry? - `call_llm()` — таймаут? - SSE события — все ли обрабатываются на фронте? --- ## Файл 7: `contractor/deploy/services/parse.py` **Что это:** Парсинг PDF/DOCX. **Что смотреть:** - Расширение: .PDF uppercase? Без расширения? - PDF: битый/зашифрованный → исключение? - DOCX: .doc (OLE) → исключение? - Таблицы: пустые, объединённые ячейки? - file_data = None? --- ## Файл 8: `contractor/deploy/services/upload.py` **Что это:** Multipart загрузка через cgi.FieldStorage. **Что смотреть:** - cgi.FieldStorage deprecated. Большие файлы? - content_length — отрицательное/огромное → rfile.read? - filename — path traversal (`../../etc/passwd`)? - 100MB файл → весь в памяти. - contract_id без валидации → SQL. - `delete_by_document` до создания нового → исключение = потеря старого без создания нового. - base64 → +33% размер. --- ## Файл 9: `contractor/deploy/db/connection.py` **Что это:** ThreadedConnectionPool. **Что смотреть:** - `DB_PASS` из env, пустой → ошибка подключения. - `minconn=1, maxconn=10` — достаточно? - getconn/putconn всегда в finally? - `_connection` глобальная — сервер не стартует без БД. --- ## Файл 10: `contractor/deploy/db/documents.py` **Что это:** CRUD documents + classify_raw. **Что смотреть:** - `insert()` — все параметры через %s? - `set_classification()` — classify_raw=None → NULL. - `set_classify_failed()` — не чистит старые поля классификации. - `delete()` — без cascade! - `get()` — SELECT *, может вернуть base64 original_bytes. --- ## Файл 11: `contractor/deploy/db/supplements.py` **Что это:** CRUD supplements + cascade. **Что смотреть:** - `delete_by_document()` — spec_current WHERE contract_id удаляет ВСЁ для контракта. Если несколько supplements → остальные теряют spec_current. - `list_by_contract()` — orphan supplements игнорируются. - `insert()` — нет FK проверки. --- ## Файл 12: `contractor/deploy/db/contracts.py` **Что это:** CRUD contracts. **Что смотреть:** - `insert()` — нет уникальности, можно дубликат. - `delete_orphaned()` — вызывается? Где? --- ## Файл 13: `contractor/deploy/db/spec_events.py` **Что это:** Event Sourcing. **Что смотреть:** - `get_next_seq()` — `MAX(seq)+1`. Гонка! Два потока → одинаковый seq. - `reset()` — необратимо. - `apply_ops()` — валидация структуры? --- ## Файл 14: `contractor/deploy/db/spec_current.py` **Что это:** Текущая спецификация. **Что смотреть:** - Кто обновляет spec_current после удаления spec_events? - `get_elements_json()` — где используется? --- ## Файл 15: `contractor/deploy/db/prompts.py` **Что это:** CRUD промптов с версионированием. **Что смотреть:** - `seed_defaults()` — `if count>0: return` — если есть extract но нет classify → classify не создастся. - `_ensure_classify_prompt()` — отдельный механизм, почему? - `save_new_version()` — UPDATE+INSERT не атомарно. Гонка. - `activate()` — гонка. - `delete_prompt()` — проверка is_active потом DELETE. Гонка. - `_serialize()` — мутирует словарь. --- ## Файл 16: `contractor/deploy/llm_prompt.py` **Что это:** Билдер промптов. **Что смотреть:** - `build_prompt()` — f-string с данными парсинга. Безопасно? - `_fetch_prompt()` — HTTP к Lucee. Таймаут? Fallback при недоступности? - `build_classify_prompt()` — прямой доступ к БД, не через HTTP. Почему? - FALLBACK_* хардкод — дублирование с БД. --- ## Файл 17: `contractor/index.cfm` **Что это:** HTML-оболочка. **Что смотреть:** - `?v=1.0.175` хардкод — менять при каждом обновлении JS. - pipelineStepper — ○⏳✓ через replace. Надёжно? - XSS через prompt body в textarea/div? - Z-index конфликты модалок? - inline onclick — ломаются при перезагрузке JS? --- ## СВОДНАЯ ТАБЛИЦА (выдать после пофайлового разбора) | # | Файл:строка | Серьёзность | Тип | Описание | Как исправить | |---|-------------|-------------|-----|----------|---------------| ## ТОП-5 (срочно) 5 проблем, которые надо чинить прямо сейчас.