diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 82fff1e..beb981b 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -48,7 +48,11 @@ - **Порча и тихий отказ:** `POST /domains/{id}/delete` (домен вместе с DKIM-ключом, опубликованная TXT-запись становится мусором), `POST /applications/{aid}/delete`, `POST /applications/{aid}/password` (ротация рвёт отправку живому приложению; новый пароль атакующий не увидит), `POST /domains/{id}/ratelimit` с лимитом в 1 письмо (деградация, которую заметят не сразу). - **Не проходит:** кража секретов через `POST /backup` и `POST /domains/{id}/export` (ответ кросс-origin не прочитать), захват учётки через `POST /account` (требует текущий пароль), login-CSRF (аккаунт один, для логина нужен его же пароль) и `POST /setup/{token}` (нужен сам секретный токен). - **Вопрос:** рекомендация — **(б)** в составе Фазы 14.A: единственный реалистичный вектор здесь — строка 2, и она закрывается пятнадцатью строками в уже планируемом мидлваре. **(в)** — если нужна гарантия независимо от браузера. **(а)** остаётся честным выбором, если соседние поддомены считаются доверенными. + **Решено: вариант (б)** — проверка `Origin`/`Sec-Fetch-Site` в том же мидлваре, что и security-заголовки. Токены (в) не делаем. **Реализация — см. Фазу 14.A.** + + **Принятый риск (строка 3):** `POST` без обоих заголовков пропускается, т.е. клиент, не посылающий ни `Sec-Fetch-*`, ни `Origin` — по-настоящему старый браузер или webview с замороженным движком — остаётся уязвим к CSRF с любого сайта. Принято сознательно: панель однопользовательская, админ выбирает браузер сам, а строгий режим не «защитил бы» такой клиент, а просто сломал бы в нём панель. + + **Что можно закрыть при необходимости, в порядке возрастания цены:** (1) ужесточить политику — резать `POST` без обоих заголовков; закрывает строку 3, ценой полной неработоспособности панели в таких клиентах, изменение в одну строку внутри того же мидлваря; (2) вариант (в), токен, привязанный к сессии — закрывает строку 3 без потери совместимости, цена — скрытое поле в ~20 формах; триггером считать появление требования «устойчиво независимо от браузера». Строка 4 (XSS) обоими не закрывается ни при каком раскладе — против неё работают `html/template` и CSP. 3. **Cookie без префикса `__Host-`.** Сейчас `selfpost_session` (`Secure`/`HttpOnly`/`SameSite=Lax`/`Path=/`). Префикс `__Host-` дал бы браузерный гарант «только HTTPS, только этот хост, без Domain». Мелочь, но бесплатная. **Вопрос:** переименовать (учесть dev-режим `PANEL_COOKIE_SECURE=false` — `__Host-` требует `Secure`, т.е. только когда secure включён). 4. **Setup-ссылка печатается в stdout контейнера.** По ТЗ (7.6.1) — так и задумано, но если логи контейнера уезжают в агрегатор, токен там осядет на 10 минут. **Решено:** базовый вариант объявления остаётся stdout (по ТЗ), код не меняется — файл `/data/setup-token` (0600) уже пишется ([internal/web/setup.go](../internal/web/setup.go)). Остаётся документационная задача: указать этот файл как более защищённую альтернативу для тех, у кого логи уезжают в централизованный агрегатор. **Реализация — см. Фазу 14.B.** @@ -73,14 +77,19 @@ **Статус:** запланирована, не начата. -**Цель:** довести до кода два уже принятых, но пока не реализованных решения из -раздела [A. Безопасность](#a-безопасность--hardening-сверх-обязательного-76) — -пункты A.1 и A.4 (оставшиеся A.2/A.3 — открытые вопросы без решения). +**Цель:** довести до кода принятые решения из раздела +[A. Безопасность](#a-безопасность--hardening-сверх-обязательного-76) — пункты +A.1 (заголовки), A.2 (проверка origin) и A.4 (документация). Пункт A.3 +(`__Host-`) остаётся открытым вопросом без решения. -### A. Security-заголовки ответа (пункт A.1) +### A. Security-заголовки ответа и проверка origin (пункты A.1 и A.2) -Добавить в панель единый middleware, оборачивающий все ответы (кроме, возможно, -уже застриманных HTMX-фрагментов, где это не мешает), выставляющий: +Оба решения — один и тот же мидлварь, обёрнутый вокруг всего `mux` (а не только +вокруг `authed`, чтобы `POST /login` и `POST /setup/{token}` тоже попали под +проверку). + +**A.1 — заголовки.** Выставлять на всех ответах (кроме, возможно, уже +застриманных HTMX-фрагментов, где это не мешает): - `Strict-Transport-Security` (только когда `PANEL_COOKIE_SECURE`/TLS включён — по аналогии с `__Host-`/`Secure`-логикой, HSTS на голом HTTP в dev-режиме бессмысленен и может быть вреден); - `X-Content-Type-Options: nosniff`; @@ -88,9 +97,24 @@ - `Referrer-Policy: same-origin` (или `no-referrer` — выбрать более строгий, поведение панели не зависит от referrer); - `Content-Security-Policy` — минимальная политика под текущий фронтенд (inline-стили/скрипты, HTMX): нужно свериться с шаблонами ([internal/web/templates](../internal/web/templates)) на предмет `