# Segurança da V2 — achados por leitura

> **Método.** Análise estática com âncora; **nenhum achado foi explorado em runtime**. Pelo protocolo
> (P3), nada aqui é “crítico provado”: severidade é **potencial** e está marcada como tal. O ambiente
> do `lotep` não foi executado nesta revisão.

## 1. Autenticação e sessão

| Item | Evidência | Risco |
|---|---|---|
| `CmsAuthenticate` exige apenas `roles()->count() > 0` | `V2/Core/Middleware/CmsAuthenticate.php:30` | **qualquer** papel (inclusive um papel sem nenhuma permissão) passa na etapa de autenticação; a autorização fica só no middleware seguinte |
| Login sem throttle | `V2/Core/Routes/cms.php:13-16` | força bruta sem `Retry-After` (contraste ADR-021) |
| Logout por `GET` | `V2/Core/Routes/cms.php:15` | logout por link/imagem (CSRF de logout) |
| Sessão sem regeneração explícita no login | `V2/Core/Controllers/Auth/LoginCmsController.php:34-58` | fixação de sessão `[INFERENCE]` (não verificado no framework 5.5) |
| Canal de IA `CmsAiToken` | `V2/Core/Middleware/CmsAiToken.php:42-96` | impersonação stateless por header; bloqueado em `production` (`:52`), mas depende de `APP_ENV` correto |

## 2. Autorização (o ponto mais frágil)

O middleware **ignora o parâmetro declarado na rota** (`v2.permission:noticias`) e resolve o módulo
pelo primeiro `active_pattern` que casar com a URL (`V2PermissionMiddleware.php:40-48`). Consequências:

1. **Fail-open:** rota que não casa com nenhum `active_pattern` passa sem checagem (`:75-76`).
2. **Verbo não verificado:** `GET/HEAD` sempre passam no nível `read` (`:104-107`); qualquer ação de
   leitura que mute estado (ex.: “aprovar” via GET, exportação, preview) escapa.
3. **Dois níveis para cinco ações:** `read/write` não distingue editar, aprovar, arquivar — o V7
   expandiu para quatro ações exatamente por isso (ADR-033).
4. **Bypass por identidade fixa:** `config('v2.support.email')` e `super-admin` atravessam tudo
   (`PermissionService.php:73`; `helpers.php:24-27`). No V7 o grupo `Suporte` é **oculto**, não
   onipotente por e-mail.
5. **Aliases à mão:** o mapa slug→PascalCase (`PermissionService.php:33-50`) e a comparação
   normalizada mascaram divergências de nomenclatura — dívida que o V7 evita com token único
   (ADR-004/033).

**Ação recomendada (V7):** permissão derivada do **route name**/grupo declarado no `ModuleProvider`,
deny-by-default para qualquer rota do CMS, matriz de teste rota×verbo×perfil.

## 3. CSRF e canal de avaliação

`app/Http/Middleware/VerifyCsrfToken.php:16-18` isenta `cms/v2/gallery/*`; `:26-28` isenta **qualquer**
requisição quando `cms_ai_token_valid` está em sessão. O canal de IA (token + IP allow-list +
`hash_equals`) é um bypass de CSRF acoplado a um recurso de avaliação. No V7 não há exceção de CSRF:
formulários são `POST` com token e páginas cacheáveis ficam `private` (ADR-022).

## 4. Renderização de HTML e XSS

| Superfície | Evidência | Risco |
|---|---|---|
| Conteúdo rico com HTML livre | `contentRichHtmlFree`; `HtmlFreeWidget` | HTML arbitrário; o V7 descartou o nó (ADR-028 §9) |
| Blade compilado a partir do banco | `V2/Modules/Support/StructuralLandingEditor/Services/BlockRenderer.php:143-230` | **RCE** se o banco/suporte for comprometido |
| Endpoint `render-section` executa tipo `bloco-sle` (Blade do banco) sem permissão além de estar autenticado | `V2/Services/LandingSectionsEditor/Routes/cms.php:8-15`; `Controllers/LandingSectionsEditorController.php:213-255` | qualquer perfil com acesso ao CMS alcança o render |
| Página runtime injeta `content` cru | `BasicPage/Layout/Views/portal/runtime.blade.php:6` | XSS armazenado |
| CSS do CMS injetado cru | `layouts/cms/layouts/app.blade.php:45` (`{!! $cmsThemeCss !!}`) | quebra de CSP; injeção de estilo |
| Blocos do portal renderizados com `{!! !!}` | `layouts/portal/base.blade.php:99-121` | idem |
| `strip_tags` como sanitização | `LandingSectionsEditorRendererService` | remove tag, mantém atributos/URLs perigosas |

**Primitivas boas que valem porte:** `v2_safe_previous()` (anti open-redirect, `helpers.php:337`),
`v2_autolink()` com escape total (`:401-427`) e `SeoScriptSanitizer` com allowlist de tags/hosts e
fail-closed (`V2/Services/SEO/Services/SeoScriptSanitizer.php:24-83`). O V7 deve manter o
*sanitizador de snippets*, trocando a execução de HTML por AST.

## 5. Upload e storage

- `UploadService` valida por **extensão informada pelo cliente** e nomeia com `uniqid`
  (`V2/Core/Services/UploadService.php:39-53`); não há allow-list de MIME/tamanho no serviço — a
  validação fica no controller (ex.: `CertificadoDigitalCmsController.php:126-135`).
- `MediaInputer` roteia por `$file->getMimeType()` e aceita qualquer `image/*`; SVG é aceito **sem
  sanitização** (XSS se servido do mesmo host).
- `StorageService`: falha do S3 cai **silenciosamente** no disco local (`Services/Storage`), dividindo
  os dados entre backends sem sinal — falha de integridade silenciosa.
- `ensureLocalPath` grava em `/tmp` sem TTL.

## 6. Cabeçalhos e CSP

Não há CSP de aplicação; os headers vêm do Nginx (`docker/nginx/nginx.conf:85-87`) e
`X-Frame-Options: ALLOWALL` é liberado para o selo (`app/Http/Middleware/AllowFrameHeaders.php:23-28`).
`HtmlMinify` reescreve toda resposta HTML por regex (`V2/Core/Middleware/HtmlMinify.php:28-107`), com
fallback em erro de backtracking — o V7 rejeita minificação em runtime (ADR-022).

## 7. Dados pessoais e auditoria

- `v2_mail_logs` grava o **corpo completo** do e-mail (PII) e não tem retenção no serviço.
- `ContactFormService` loga dados do contato (`V2/Modules/Institucional/BasicPage/Services/ContactFormService.php:48-52`).
- `v2_audit_logs` é um bom desenho (envelope + `correlation_id` + bulk insert), mas o `V2AuditLogger`
  também loga `before/after` de models — sem política de mascaramento de PII.

## 8. Resumo de severidade (potencial, não explorada)

| Severidade | Achados |
|---|---|
| **Alta** | Blade-em-DB (RCE); fail-open do middleware de permissão; HTML livre em conteúdo/página; SVG sem sanitização; S3→local silencioso |
| **Média** | Login sem throttle; logout GET; CSRF isento por token de IA; `super-admin`/e-mail fixo como bypass; PII em log de e-mail; `app_debug` mutável em runtime |
| **Baixa** | `uniqid` em nome de upload; `strip_tags` como sanitização; `Redis::keys` para limpar cache; `shell_exec` no leitor de log (execução local, mas sem necessidade) |

> **Confirmação pendente.** Nenhum item acima foi reproduzido. Pelo protocolo, severidade “crítica”
> exigiria sonda com request/consulta e saída observada; sem ambiente, o registro fica como
> **potencial** e o `08-GAPS` de cada dossiê marca `[NÃO VERIFICADO EM RUNTIME]`.
