P0 аудита, каждый сначала подтверждён исполнением, а не принят со слов.
1. Release Gate объявлял проверку хеша, которой не было. Печаталась строка
PACKAGE_HASH_VERIFIED=True при том, что hashlib в scripts/release_gate.py
не вызывался ни разу: скачивались байты 0-10 через заголовок Range, и
этого хватало, чтобы счесть хеш проверенным. «Проверенным ассетом» при
этом оказывался первый в списке — checksums.txt, а не пакет.
2. Ворота публикации были fail-open. Измерено в трёх условиях: полный обрыв
сети -> PASS, манифест 404 -> PASS, пакет 404 -> PASS. Ворота пропускали
релиз при любом исходе, включая полное отсутствие релиза.
Разделено на офлайновую часть (проверки 1-7: версии, тесты, updater,
статика, секреты, список разрешённых адресов) и Publication Gate: релиз
есть, ассеты есть, пакет скачан ЦЕЛИКОМ, SHA-256 сошёлся с опубликованным
checksums.txt. Публикационные ворота блокируют в режиме публикации
(--publication или HERMES_RELEASE_PUBLICATION_GATE=1); в обычном прогоне
CI, где релиза для ветки нет и быть не должно, результат сообщается как
есть и не блокирует. Неизмеренное называется причиной, а не выдаётся за
проверенное. Проверено на живом релизе v0.1.3-b1: два пакета скачаны
целиком, хеши сошлись.
3. POST /api/action на loopback принимал межсайтовые запросы. Токен там не
требуется, а действие меняет состояние: удаляет учётные данные, чистит
аккаунты, переключает маршрутизацию, запускает входы OAuth. CORS от этого
не защищает — он мешает прочитать ответ, а не отправить запрос. Измерено
на конфигурации по умолчанию: POST с Content-Type text/plain уходит
кросс-сайтом без предварительного запроса, request.json() разбирает тело
независимо от Content-Type, и запрос с Origin чужого сайта без токена
доходил до исполнителя действий.
Проверяется Sec-Fetch-Site, при его отсутствии — Origin против адреса
запроса. Собственный интерфейс, адресная строка и не-браузерные клиенты
работают как раньше. Защита распространена на все пять небезопасных
методов, не только на /api/action.
4. pricing fallback: safe_load вместо safe_dump. dump сериализовал текст
обратно в строку, проверка isinstance(data, dict) не выполнялась никогда,
таблица цен не загружалась ни разу, а except это глушил.
5. Симуляция Linux в тесте stop_running_hub падала на Windows: os.getuid там
не существует.
Тесты: 756 -> 776 passed, 2 skipped, 4 deselected. ruff check . чисто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>