# CSP — Como ativar e como listar os scripts que precisam corrigir

**Escopo:** portal público (`Portal\*`). Admin (`/cms`, `/admin`) e prefeitura
(`/prefecture`) ficam **fora** do CSP por decisão de isolamento.

**Status atual:** `CspNonce` implementado, porém **DESLIGADO** via
`config('csp.enabled') = false` (ver `config/csp.php`). Nenhum header CSP é emitido,
então o devtools fica limpo e o servidor não acumula log. O objetivo deste documento é
explicar **onde** ligar, **em que ordem** subir a intensidade, e **como descobrir** tudo
que ainda precisa de ajuste antes do enforce (SEC-1e).

---

## 1. Onde o CSP vive

| Arquivo | Papel |
|---|---|
| `config/csp.php` | Chave `enabled` (`false` hoje). Única alavanca de liga/desliga. |
| `app/Http/Middleware/CspNonce.php` | Gera o nonce por request, injeta nos `<script>`/`<style>` inline, monta a política e emite o header. Tudo guardado por `config('csp.enabled')` (linhas 26 e 47). |
| `app/Http/Kernel.php` (grupo `web`, linha 37) | `CspNonce::class` está **depois** de `CacheControl` — garante que o ETag seja emitido antes do nonce, mantendo o `304` (A2) estável. **Não reordene** sem revalidar HIT/MISS. |
| `app/Http/Middleware/PageCache.php` (linha 129) | No HIT do FPC chama `CspNonce::applyToCachedResponse()`, que re-injeta nonce fresco. Também guardado por `config('csp.enabled')`. |
| `routes/web.php` (linha 16) | `POST /csp-report` → `Portal\CspReportController@store`. Endpoint que recebe os relatórios de violação. |
| `app/Http/Controllers/Portal/CspReportController.php` | Grava cada relatório em `storage/logs/csp-violations.log` (JSON por linha). |
| `wiki/infra-update/csp-epico-seguranca.md` | Visão geral do épico (SEC-1e / SEC-1f). |

A política atual (quando ligada) é **Report-Only** com `'nonce-{R}'` + `'unsafe-hashes'`
+ allowlist de host (`gstatic`, `google`, `vlibras.gov.br`, `jsdelivr`, `addtoany`,
`facebook`, `youtube`). Sem `unsafe-inline`, sem `sha384` (o nonce é por request). A
coleta de violações (via relatório) fica desligada por padrão.

---

## 2. Como ativar (3 níveis de intensidade)

### Nível 0 — DESLIGADO (estado atual)
`config/csp.php`:
```php
return [ 'enabled' => false ];
```
Nenhum header CSP é emitido. Devtools limpo, sem log. Borda cacheia normalmente.

### Nível 1 — Canário com COLETA (para LISTAR o que falta)
Liga o header **e** a coleta de violações, para o browser reportar tudo que viola.
Não bloqueia nada (segue Report-Only).

1. `config/csp.php` → `'enabled' => true`.
2. Em `CspNonce::applyHeadersAndNonce()`, recompor o endpoint de relatório (hoje removido
   na Opção A):
```php
$endpoint = $request->getSchemeAndHttpHost() . '/csp-report';
$response->headers->set(
    'Content-Security-Policy-Report-Only',
    $this->policy($nonce) . " report-uri " . $endpoint . "; report-to csp;"
);
$response->headers->set('Reporting-Endpoints', 'csp="' . $endpoint . '"');
```
3. `php artisan cache:page:clear` e reload do fpm (`kill -USR2 21` no container).
4. Navegue o site (home, notícias, licitações, legislação, contato). Cada violação vira um
   POST em `storage/logs/csp-violations.log`. Veja a seção 3.

### Nível 2 — ENFORCE (SEC-1e) — só depois de mapear tudo
Trocar o nome do header de `Content-Security-Policy-Report-Only` para
`Content-Security-Policy` em `applyHeadersAndNonce()`. Exige antes:
- zero violação legítima no log do Nível 1;
- a **borda (A4 / Multi-CDN)** configurada para servir **Report-Only** (downgrade
  enforce→Report-Only), evitando servir um nonce fixo em modo bloqueante;
- resolução do conteúdo injetado em runtime por libs vendor (ver 4.4).

---

## 3. Como LISTAR os scripts que precisam corrigir

Há três formas, da mais automática para a mais manual.

### 3.1 Via relatórios do browser (recomendado — Nível 1 ligado)
Com a coleta ativa, cada violação é gravada em `storage/logs/csp-violations.log` como
JSON:
```json
{"time":"...","ip":"...","user_agent":"...","referer":"...","report":{"blocked-uri":"...","violated-directive":"script-src-elem","source-file":"https://.../infra-update.devs.maxima.inf.br","line-number":19,"script-sample":"console.log(1)","original-policy":"..."}}
```
Para agregar e contar o que falta:
```bash
# violações por diretiva (script-src-elem, style-src-attr, script-src-attr, ...)
grep -oE '"violated-directive":"[^"]+"' storage/logs/csp-violations.log | sort | uniq -c | sort -rn

# violações por arquivo de origem (onde estão os blocos problemáticos)
grep -oE '"source-file":"[^"]+"' storage/logs/csp-violations.log | sort | uniq -c | sort -rn

# amostras de script/style inline violados (conteúdo exato a hashear ou mover)
grep -oE '"script-sample":"[^"]*"' storage/logs/csp-violations.log | sort | uniq -c | sort -rn
```
O `script-sample` é o conteúdo exato do bloco/atributo violado. O hash a adicionar na
política é `sha256` desse conteúdo (o próprio browser já sugere em devtools — ver 3.2).

### 3.2 Via devtools (manual, preciso)
No console do browser, cada aviso termina com:
```
Consider using a hash ('sha256-XXXX') or a nonce.  arquivo:linha:col
```
Isso já dá o **hash exato** a acrescentar na política e o **local** exato. É o método
mais rápido para correções pontuais.

### 3.3 Varredura estática no código (pré-voo)
Para antecipar a maior parte antes de ligar o canário:
```bash
# <script> inline (sem src=) nas views/blades
grep -rnE "<script(?![^>]*\bsrc=)" resources/views --include=*.blade.php

# handlers de evento inline (onclick=, onload=, onsubmit=, ...)
grep -rnoE "\bon[a-z]+=" resources/views --include=*.blade.php

# atributos style="" inline
grep -rnE "style=" resources/views --include=*.blade.php
```
> ⚠️ A varredura estática **não** captura conteúdo injetado em runtime por libs vendor
> (ex.: `fontawesome-all.js`, `vlibras-plugin.js`). Isso só aparece no Nível 1 (3.1) ou
> no devtools (3.2).

---

## 4. Categorias de problema e como corrigir

### 4.1 `<script>` / `<style>` inline no corpo do HTML
**Já resolvido automaticamente** pelo `CspNonce` quando ligado: ele injeta o `nonce`
nesses blocos (sem `src=`). Não é preciso mexer nos Blades. Aparecem no log apenas se o
middleware estiver desligado ou a injeção falhar.

### 4.2 Handlers de evento (`onclick="aumenta()"`, etc.) e `style="..."`
O `'unsafe-hashes'` na política **sozinho não libera** esses atributos — ele exige o
**hash** de cada um na política. Dois caminhos:
- **(a) Hash estático:** calcular `sha256` do conteúdo do handler/atributo e adicionar
  `'sha256-...'` à diretiva correspondente (`script-src` / `style-src`) junto de
  `'unsafe-hashes'`. Só serve para conteúdo **estático** (handlers com texto fixo).
- **(b) Refatorar (recomendado):** mover para `addEventListener` no JS e para **classes
  CSS** (em vez de `style=""`). Elimina a violação de vez e melhora manutenção.

### 4.3 `style=""` dinâmico (ex.: `background-image: url('/storage/...')`)
Conteúdo variável → **não dá para hashear**. Corrigir movendo o estilo para uma classe
CSS (ou setando via `element.style` a partir de um `<style>` com nonce, não via
atributo). 

### 4.4 Injeção em runtime por libs vendor (`fontawesome-all.js`, `vlibras-plugin.js`)
A lib cria `<style>`/`<script>` em tempo de execução, fora do HTML inicial — **não dá
para nonce/hash** (o `CspNonce` só alcança o HTML renderizado). Sob enforce estrito, esse
conteúdo **será bloqueado** e quebra funcionalidade (ícones, VLibras). Caminhos:
- libs com versão que não injeta inline (ex.: Font Awesome via CSS+SVG, VLibras com
  loader que não cria `<style>` inline);
- ou aceitar o bloqueio (impacto funcional) — inviável para produção;
- `unsafe-inline` **não é aceito** pelo projeto (vedado em SEC-1e). Este é o **bloqueador
  real** do enforce e exige decisão antes do Nível 2.

---

## 5. Critério para virar enforce (SEC-1e)

Só após **todas** as condições:
1. Log do Nível 1 com **zero** recurso legítimo bloqueado (apenas o que se decide
   conscientemente não suportar).
2. Borda A4 configurada para downgrade enforce→Report-Only.
3. Conteúdo vendor runtime (4.4) resolvido ou aceito explicitamente.
4. `php artisan cache:page:clear` + reload fpm + validação de headers (`curl -sI`) em
   home/notícias/contato/licitações/legislação, e `304` estável.

---

## 6. Comandos de validação rápida

```bash
# header CSP presente? (container canário, porta 32791→443)
curl -k -sI https://127.0.0.1:443/ | grep -i "content-security-policy"

# 304 estável (ETag não muda com o nonce)
ETAG=$(curl -k -sI https://127.0.0.1:443/ | grep -i etag | tr -d '\r' | awk '{print $2}')
curl -k -sI -H "If-None-Match: $ETAG" https://127.0.0.1:443/ | grep -i "^HTTP/"

# lint + testes
php -l app/Http/Middleware/CspNonce.php
php vendor/bin/phpunit tests/Unit/CspNonceTest.php
```
