714d6885f9
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>
64 lines
3.6 KiB
Markdown
64 lines
3.6 KiB
Markdown
# Спека — 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"}
|
||
]
|
||
```
|