docs: D6-D9 — HEALTHCHECK, env regression test, new docs, archive spec

Add Docker HEALTHCHECK and mail-path /healthz liveness; env-doc regression
test; architecture.md and development.md; product.md and expanded security.md;
retire live specification.md to docs/archive/.

Co-Authored-By: Claude <claude-opus-5-thinking-high@noreply@anthropic.com>
This commit is contained in:
2026-08-05 00:33:49 +03:00
parent 7f24b2923c
commit baaed5991b
16 changed files with 703 additions and 28 deletions
+71 -12
View File
@@ -1,19 +1,78 @@
# Безопасность: принятые риски
# Безопасность
**Что здесь.** Обязательные требования к безопасности — [ТЗ 7.6](specification.md);
соответствие им проверено полным аудитом на v1.0 и здесь не пересказывается.
Hardening сверх обязательного 7.6 (security-заголовки, проверка origin, cookie
`__Host-` с обнаружением дублей — Фаза 14) тоже закрыт, история — в
[CHANGELOG.md](../CHANGELOG.md) и `git log`. Этот документ держит третью
категорию: то, что закрыто **сознательно не было**, чтобы решение не потерялось
и не переоткрывалось заново.
**Что здесь.** (1) **Обязательные требования** — чеклист, который v1.0 обязан
выполнять; полный аудит на v1.0 пройден. (2) **Принятые риски** — сознательные
отступления сверх обязательного, чтобы решение не потерялось.
Здесь, а не в [implementation-plan.md](implementation-plan.md), потому что план —
только про несделанную работу, а принятый риск — не работа, а решение: у него нет
состояния «в очереди», есть условие, при котором к нему возвращаются.
Hardening сверх обязательного (security-заголовки, проверка origin, cookie
`__Host-` с обнаружением дублей — Фаза 14) закрыт; история — в
[CHANGELOG.md](../CHANGELOG.md) и `git log`.
Продуктовые границы: [product.md](product.md). Устройство as-built:
[architecture.md](architecture.md).
---
## Обязательные требования
Панель публична из интернета — пункты ниже **не опциональны**.
### Первичная инициализация администратора
- Одноразовая secret-ссылка `/setup/<token>`, **не** env с готовым хэшем пароля.
- Токен ≥128 бит (`crypto/rand`); дублируется в `/data/setup-token`.
- Срок жизни токена — **10 минут**; после истечения или рестарта без завершённой
настройки — перегенерация и новый вывод в лог.
- Rate limiting на `/setup/<token>` по IP, отдельно от логина.
- Сравнение токена — **константное по времени** (`subtle.ConstantTimeCompare`).
- Неудачные попытки **не** инвалидируют токен досрочно (защита от DoS настройки).
- После создания администратора — токен навсегда недействителен, `/setup/*` → 404.
- Пароль администратора — только bcrypt (или argon2) в SQLite; без plaintext/MD5.
- `PANEL_USERNAME` / `PANEL_PASSWORD_HASH` в env **не используются**.
### SASL-пароли приложений
- Панель **генерирует** пароль при создании/перевыпуске, показывает **один раз**.
- В `sasldb2` — в форме, требуемой SASL (не plaintext в панели); утерян — только
перевыпуск.
### Ввод и конфигурация
- Серверная валидация email/доменов (whitelist символов); клиентская не считается
защитой.
- Режим «список адресов» — каждый адрес принадлежит домену приложения до записи.
- `postfix reload` и любой `exec`**без** shell-интерполяции пользовательского
ввода; аргументы отдельными элементами.
- Запись в конфиг-файлы — с экранированием (нет инъекции директив Postfix).
### Аутентификация и сессии
- Rate limiting на логин (по IP, с блокировкой/задержкой).
- Сессии: криптографически случайный токен; cookie `HttpOnly`, `Secure`, `SameSite`.
- Сессии в SQLite (SHA-256 токена, не сам токен); скользящий idle
(`PANEL_SESSION_IDLE_DAYS`).
### Вывод и процесс
- Рендер через `html/template` с автоэкранированием (очередь, лог, журнал, темы).
- Процесс панели **не root** (`user=panel` в supervisord); доступ к путям через
группу `selfpost` и минимальные права.
### Почтовый тракт (связанное с безопасностью)
- **Не open relay** — только SASL; `reject_unauth_destination`;
`smtpd_sender_login_maps` + `reject_sender_login_mismatch`.
- TLS обязателен до передачи кредов (465 wrapper / 587 `encrypt`).
- `TRUSTED_PROXY_CIDR` — только явно доверенные прокси для `X-Forwarded-For`
при rate-limit логина; пусто = XFF игнорируется.
---
## Принятые риски
Здесь, а не в [implementation-plan.md](implementation-plan.md): план — про
несделанную работу, принятый риск — решение с условием возврата.
- **`POST` без `Sec-Fetch-Site` и без `Origin` пропускается.**
Клиент, не посылающий ни одного из двух — по-настоящему старый браузер или
webview с замороженным движком, — остаётся уязвим к CSRF с любого сайта.
@@ -28,7 +87,7 @@ Hardening сверх обязательного 7.6 (security-заголовки
вопросу считать появление требования «устойчиво независимо от браузера».
От XSS внутри самой панели не спас бы и токен: код, исполняющийся в origin
панели, отправит запрос сам — против этого работают автоэкранирование
`html/template` (7.6.7) и CSP, поэтому шаблоны не должны содержать
`html/template` и CSP, поэтому шаблоны не должны содержать
inline-скриптов и inline-стилей.
## Как этот список пополняется