docs: add item D.5 — pre-release vulnerability review by Fable

A single security pass once B.1-B.3 and C.4 are in, not four per-item
passes: those four rewrite authentication, log-file handling, the
entrypoint gate and the release workflow, so what matters is the final
state. Scope is the whole diff since v1.0.0 (phases 12-14 included)
plus a fresh walk over spec 7.6, run by Fable rather than Opus so the
reviewer is not the author. Findings are either fixed before the tag
or recorded in section A as accepted risks; together with the C.4 e2e
this gates tagging. The old section D (2.x pointer) becomes E, its
items renumbered 6 and 7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 23:09:26 +03:00
parent d18e70b67e
commit a1160e0fb8
2 changed files with 19 additions and 5 deletions
+4 -2
View File
@@ -16,6 +16,8 @@
Правило: безопасность / инфра / риск-критичное → **Opus**; UI / документация / бойлерплейт → **Sonnet**; тривиальная механика → **Haiku**.
Исключение — **ревизия** (не написание) кода: предрелизная проверка на уязвимости (пункт **D.5** плана) делается моделью **Fable**, чтобы проверял не тот, кто писал.
## Коммиты
Коммит на **каждом осмысленном шаге** (не каждое сохранение файла, но и не только конец фазы): рабочий под-функционал, зелёная сборка, конец фазы. Минимум — один коммит на закрытую фазу + промежуточные на связные под-шаги. Ветка `main` (если пользователь не попросит отдельную). Push/PR — только по явной команде. Сообщение коммита завершается трейлером `Co-Authored-By: Claude <модель> <noreply@anthropic.com>` — с той моделью, которая этот шаг делала (на момент Фазы 14 — `Claude Opus 5`).
@@ -35,8 +37,8 @@
## Текущее состояние
- **Выполнено и принято:** базовый линейный план 0→11 (v1.0; аудит безопасности ТЗ 7.6 — полное соответствие), Фаза 12 (UI/UX), Фаза 13 (страница `/status`, DNS-проверки домена) и Фаза 14 (security-заголовки, проверка origin, cookie `__Host-` + обнаружение дублей, документация про `/data/setup-token`). Что именно сделано — в `git log` и `CHANGELOG.md`, здесь не дублируется.
- **Решено, но ещё не реализовано:** пункты **B.1**, **B.2**, **B.3** и **C.4** плана, именно в этом порядке. B.1 — сессии переезжают в SQLite (скользящий срок бездействия 7 дней, `PANEL_SESSION_IDLE_DAYS`, опросы не продлевают, смена пароля разлогинивает всех). B.2 — ротация `mail.log` уходит с `copytruncate` на «переименовать + `postfix reload`» (правки в `logrotate-mail.conf`, `follow()` в `internal/logtail`, `readLogTail()` в `internal/web`; на стенде проверить, что после reload новый `mail.log` создаётся). B.3 — незаданный `SELFPOST_HOSTNAME` роняет контейнер в `entrypoint.sh` с развёрнутым текстом ошибки плюс синтаксическая проверка значения. C.4 — герметичный контейнерный e2e отдельным Go-модулем `test/e2e/` поверх поставляемого compose, гейт перед публикацией образа по тегу, нативная матрица amd64/arm64 вместо qemu в `release.yml`; делается **после** B.1–B.3, стендовые проверки B.1/B.3 переезжают в него регрессиями. Параметры всех четырёх согласованы полностью, кода нет: делать отдельными шагами.
- **Дальше — то, что перечислено в `implementation-plan.md`:** открытые вопросы закрыты, раздел D теперь только указатель на объём 2.x (входящий релей O1+ и роль администратора домена; 2FA снята с рассмотрения); остаются принятые риски раздела A (`POST` без `Sec-Fetch-Site`/`Origin` пропускается, токенов нет) и опциональная **Фаза O1+** (входящий релей, линия 2.x.x, требует согласования).
- **Решено, но ещё не реализовано:** пункты **B.1**, **B.2**, **B.3** и **C.4** плана, именно в этом порядке. B.1 — сессии переезжают в SQLite (скользящий срок бездействия 7 дней, `PANEL_SESSION_IDLE_DAYS`, опросы не продлевают, смена пароля разлогинивает всех). B.2 — ротация `mail.log` уходит с `copytruncate` на «переименовать + `postfix reload`» (правки в `logrotate-mail.conf`, `follow()` в `internal/logtail`, `readLogTail()` в `internal/web`; на стенде проверить, что после reload новый `mail.log` создаётся). B.3 — незаданный `SELFPOST_HOSTNAME` роняет контейнер в `entrypoint.sh` с развёрнутым текстом ошибки плюс синтаксическая проверка значения. C.4 — герметичный контейнерный e2e отдельным Go-модулем `test/e2e/` поверх поставляемого compose, гейт перед публикацией образа по тегу, нативная матрица amd64/arm64 вместо qemu в `release.yml`; делается **после** B.1–B.3, стендовые проверки B.1/B.3 переезжают в него регрессиями. Параметры всех четырёх согласованы полностью, кода нет: делать отдельными шагами. Замыкает очередь **D.5** — предрелизная проверка на уязвимости моделью Fable по всему дифу от `v1.0.0` плюс повторный проход по ТЗ 7.6; вместе с e2e это гейт перед тегом.
- **Дальше — то, что перечислено в `implementation-plan.md`:** открытые вопросы закрыты, раздел E теперь только указатель на объём 2.x (входящий релей O1+ и роль администратора домена; 2FA снята с рассмотрения); остаются принятые риски раздела A (`POST` без `Sec-Fetch-Site`/`Origin` пропускается, токенов нет) и опциональная **Фаза O1+** (входящий релей, линия 2.x.x, требует согласования).
- **Прод:** `selfpost.mixfed.ru`, реальный Let's Encrypt сертификат, живой e2e (DKIM/SPF pass). Контейнер там всё ещё на образе v1.0 — Фаза 14 в него не выкатывалась. При апгрейде: админа один раз разлогинит (сменилось имя cookie), а от reverse-proxy требуется передача исходного `Host` (Apache-фрагмент из `deploy/` это делает).
## Рабочая петля (dev loop) — ВАЖНО