Files
portal/docs/superpowers/specs/2026-06-17-security-headers-mw-spec.md
T
Дмитрий 84936929eb 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

3.6 KiB
Raw Blame History

Спека — 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

Контракт. Новый класс 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 — регистрация

Контракт. Middleware регистрируется глобально на web-группу в bootstrap/app.php внутри ->withMiddleware(...) через $middleware->web(append: [SecurityHeaders::class]) (импорт класса сверху, как SetTenantContext/EnsureSaasAdmin). Все маршруты портала — в web-группе, значит покрытие полное.

Критерий. В bootstrap/app.php есть append SecurityHeaders в web-группу.

H3 — тест (TDD)

Контракт. Pest-feature-тест tests/Feature/SecurityHeadersTest.php бьёт по публичному маршруту / (welcome SPA, без auth/БД) и проверяет наличие и значения трёх заголовков. Сначала RED (middleware ещё нет), после реализации — GREEN.

Edge-case. Маршрут / отдаёт 200 (Route::view welcome), БД не требуется — тест лёгкий и не зависит от тестовой базы.

Критерий. Тест есть; до реализации падает, после — проходит.

H4 — CSP вне объёма

Контракт. Content-Security-Policy в этом middleware НЕ ставится: корректная политика под Vue 3 + Vuetify 3 SPA (script-src/style-src/inline) требует выверки и прогона по экранам, иначе ломает интерфейс. Фиксируется как отдельная задача.

Критерий. В коде middleware CSP отсутствует; в комментарии класса — пометка, что CSP вынесен отдельно.

[
  {"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"}
]