# História 14 — Cabeçalhos de segurança (edge)

- **Spec:** `tests/14-security-headers.spec.ts`
- **Camadas:** E2E (Playwright `request`) + PHPUnit (`SecurityHeadersTest`)
- **Dono:** Core (middleware `security.headers`) + edge (`docker/web/Caddyfile` — spec 16)
- **Status:** ativa

## Jornada
Confirmar que a CSP e os cabeçalhos de segurança vêm do **edge** (Caddy), estáticos e cacheáveis, e
que o HTML público é **determinístico** (sem token/nonce por request).

## Critérios de aceite
1. A CSP do portal é estrita: `default-src 'self'`, **sem** `unsafe-inline`/`unsafe-eval` e **sem**
   CDN (`cdn.jsdelivr.net`, `fonts.googleapis.com`/`gstatic`); `frame-ancestors 'self'`.
2. O HTML público **não** carrega token (`csrf-token`) nem nonce por request — duas requisições
   devolvem o mesmo corpo (`ETag` estável — ADR-022).
3. O embed de notícia (`/noticias/embed/*`) é a única rota com `frame-ancestors *` (embutível).
4. O app **não** emite CSP (delegada ao edge) — coberto no PHPUnit (`SecurityHeadersTest`).
5. O conteúdo público sai `Cache-Control: public` com `ETag`/304 e **sem `Set-Cookie`**; `Vary`
   cobre compressão e preferências de a11y (ADR-022).
6. O CMS exige **Trusted Types** (`require-trusted-types-for 'script'`); o portal não (VLibras).
7. Os assets do Vite (nome com hash) são `immutable` no edge.

## Fora de escopo (dono · fase)
- CSP por hash no deploy (hashes anexados na borda) — spec 16/17.
