Files
selfpost/docs/security.md
T
mix e93a277ee7 security: phase D pre-release review — pass; harden saslpasswd2 argv
Fable review of the full diff from the v1.0 audit (Phase 11, 65a420d) to
HEAD plus a complete pass over the docs/security.md checklist (former spec
7.6). No exploitable findings. One defence-in-depth fix: the application
login is passed to saslpasswd2 behind a -- end-of-options marker so a
login starting with - can never be parsed as a flag. Accepted risks
unchanged; plan § D closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 13:32:52 +03:00

107 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Безопасность
**Что здесь.** (1) **Обязательные требования** — чеклист, который v1.0 обязан
выполнять; полный аудит на v1.0 пройден. Предрелизная ревизия (план § D,
модель Fable, 2026-08-06) прошла по всему дифу от аудита v1.0 (Фаза 11) до
HEAD и по чек-листу целиком: эксплуатируемых находок нет; одна правка
defence-in-depth — `--` перед логином в argv `saslpasswd2`
([internal/app/sasl.go](../internal/app/sasl.go)). (2) **Принятые риски**
сознательные отступления сверх обязательного, чтобы решение не потерялось.
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 с любого сайта.
Принято сознательно: панель однопользовательская, админ выбирает браузер
сам, а строгий режим не «защитил бы» такой клиент, а просто сломал бы в нём
панель. Ужесточение — одна строка в `originAllowed`
([internal/web/security.go](../internal/web/security.go)): вернуть `false`
вместо `true` в ветке «нет обоих заголовков».
- **CSRF-токены, привязанные к сессии, не делаются.** Проверка origin
закрывает соседний поддомен, но зависит от поведения браузера; токен — нет.
Цена — скрытое поле примерно в двух десятках форм. Триггером вернуться к
вопросу считать появление требования «устойчиво независимо от браузера».
От XSS внутри самой панели не спас бы и токен: код, исполняющийся в origin
панели, отправит запрос сам — против этого работают автоэкранирование
`html/template` и CSP, поэтому шаблоны не должны содержать
inline-скриптов и inline-стилей.
- **Send-log может навсегда остаться `queued` после рестарта панели или
пересоздания контейнера.** Log-tailer стартует с конца `mail.log`; файл не в
`/data` и теряется при recreate. Rename-ротация это не лечит. См. [architecture.md](architecture.md)
§ Log tailer — known gaps.
## Как этот список пополняется
Предрелизная проверка на уязвимости ([implementation-plan.md](implementation-plan.md)
§ D, модель Fable) закрывает каждую находку одним из двух способов: правка до
тега — либо запись сюда, с обоснованием и условием возврата, как у пунктов выше.
Третьего варианта («посмотрели и ладно») нет.