Files
portal/docs/superpowers/specs/2026-06-17-security-headers-mw-spec.md
T
Дмитрий 714d6885f9 feat(security): middleware безопасных HTTP-заголовков — закрытие ZAP Medium anti-clickjacking
X-Frame-Options SAMEORIGIN + X-Content-Type-Options nosniff + Referrer-Policy на все web-ответы (go-live аудит 17.06). CSP вынесен отдельно (SPA Vue+Vuetify). TDD-тест на публичном /.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:12:32 +03:00

64 lines
3.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.
# Спека — middleware безопасных HTTP-заголовков (закрытие ZAP Medium anti-clickjacking)
## Цель
Добавить Laravel-middleware, проставляющий безопасные HTTP-заголовки на все
web-ответы портала, чтобы закрыть Medium-находку go-live аудита 17.06.2026
«Missing Anti-clickjacking Header» (плюс базовые `X-Content-Type-Options`,
`Referrer-Policy`). Реализация по TDD; CSP в этот объём НЕ входит (отдельная
выверенная задача под Vue+Vuetify SPA).
## H1 — какие заголовки ставит middleware {#H1}
**Контракт.** Новый класс `App\Http\Middleware\SecurityHeaders` в `handle()` после
`$next($request)` проставляет на ответе:
- `X-Frame-Options: SAMEORIGIN` (анти-clickjacking);
- `X-Content-Type-Options: nosniff`;
- `Referrer-Policy: strict-origin-when-cross-origin`.
**Edge-case.** Заголовки ставятся без условий на все ответы web-группы; уже
существующие значения перезаписываются предсказуемо (`headers->set`).
**Конвенция.** Стиль файла — как у прочих middleware: `declare(strict_types=1)`,
namespace `App\Http\Middleware`, сигнатура `handle(Request, Closure): Response`
(Symfony Response).
**Критерий.** Класс существует, в `handle()` выставлены ровно три заголовка.
## H2 — регистрация {#H2}
**Контракт.** Middleware регистрируется глобально на web-группу в
`bootstrap/app.php` внутри `->withMiddleware(...)` через
`$middleware->web(append: [SecurityHeaders::class])` (импорт класса сверху, как
`SetTenantContext`/`EnsureSaasAdmin`). Все маршруты портала — в web-группе, значит
покрытие полное.
**Критерий.** В `bootstrap/app.php` есть append SecurityHeaders в web-группу.
## H3 — тест (TDD) {#H3}
**Контракт.** Pest-feature-тест `tests/Feature/SecurityHeadersTest.php` бьёт по
публичному маршруту `/` (welcome SPA, без auth/БД) и проверяет наличие и значения
трёх заголовков. Сначала RED (middleware ещё нет), после реализации — GREEN.
**Edge-case.** Маршрут `/` отдаёт 200 (Route::view welcome), БД не требуется —
тест лёгкий и не зависит от тестовой базы.
**Критерий.** Тест есть; до реализации падает, после — проходит.
## H4 — CSP вне объёма {#H4}
**Контракт.** `Content-Security-Policy` в этом middleware НЕ ставится: корректная
политика под Vue 3 + Vuetify 3 SPA (script-src/style-src/inline) требует выверки и
прогона по экранам, иначе ломает интерфейс. Фиксируется как отдельная задача.
**Критерий.** В коде middleware CSP отсутствует; в комментарии класса — пометка,
что CSP вынесен отдельно.
```verified-context-json
[
{"id":"vc1","kind":"EXTRACTED","ref":"app/bootstrap/app.php","anchor":"withMiddleware(function"},
{"id":"vc2","kind":"EXTRACTED","ref":"app/app/Http/Middleware/SetTenantContext.php","anchor":"Устанавливает app.current_tenant_id"}
]
```