Histórico de Incidentes de Segurança
Este documento registra incidentes de segurança identificados e suas correções. Toda entrada deve ser forward-only (não editar entradas passadas, apenas adicionar notas/follow-ups com data nova).
2026-05-09 — CORS preflight bypass em resend-webhook e email-unsubscribe
Severidade: 🔴 Crítica (bypass de validação HMAC em webhook) Detectado por: auditoria @audit test da Fase B do triage de testes Resolvido em: 2026-05-09 (commit do PR #B3)
Resumo
A função utilitária supabase/functions/_shared/cors-helpers.ts:handleCorsPrelight() retornava Response(204) incondicionalmente, sem checar req.method. Quando uma Edge Function usava o padrão:
const pre = handleCorsPrelight(req);
if (pre) return pre;pre era sempre truthy → toda requisição (incluindo POST) recebia 204 antes de executar qualquer validação posterior (HMAC, auth, body parsing).
Funções afetadas
| Função | Risco |
|---|---|
resend-webhook | Aceitava callbacks de bounce/complaint/delivered sem validar HMAC. Atacante poderia injetar eventos falsos no outbox_email e DLQ. |
email-unsubscribe | Aceitava unsubscribe sem validar token. Risco de DoS de listagem ou unsubscribe não-autorizado. |
As demais 38 funções que importavam handleCorsPrelight usavam o padrão correto if (req.method === 'OPTIONS') return handleCorsPrelight(req); — não foram afetadas.
Comprovação
POST /functions/v1/resend-webhook com body {} antes do fix:
- HTTP 204, body vazio, sem entrada em
outbox_email_eventos.
Após fix:
- HTTP 401
{"success":false,"message":"missing_svix_headers"}— validação roda.
Causa raiz
Nome da função (handleCorsPrelight) sugere "trata preflight", mas a implementação não checava o método. O padrão if (pre) return pre; foi copiado em outras 2 funções acreditando que o helper já encapsulava a checagem.
Correção aplicada
- Helper documentado (
_shared/cors-helpers.ts): comentário JSDoc explícito afirmando que o caller DEVE checarreq.method === 'OPTIONS'antes de chamar. Tipo de retorno mantido comoResponse(sempre) para não quebrar 38 callsites válidos. A segurança vem do guard de regressão (item 3) + alinhamento dos callsites quebrados. - Callsites alinhados:
resend-webhookeemail-unsubscribeagora usamif (req.method === 'OPTIONS') return handleCorsPrelight(req);— padrão único em todo o repositório. - Guard de regressão:
_shared/__tests__/cors-helpers.test.tsfaz scan estático de todos osindex.tsemsupabase/functions/e falha se detectar o padrãoconst pre = handleCorsPrelight(req); if (pre) return pre;. - ADR:
docs/architecture/adrs/0007-cors-helper-strict-method-guard.md. - Memory:
mem://security/cors-preflight-method-guard.
Impacto observado
Auditoria de logs em outbox_email_eventos e tabela email-unsubscribe no período 2026-04-01 a 2026-05-09: nenhum evento anômalo identificado. Dado que o webhook Resend só era chamado pelo serviço Resend (que sempre envia headers svix), o risco de exploração in-the-wild é baixo a nulo — porém o bypass era real e exploitável por qualquer ator que conhecesse a URL pública da função.
Follow-ups
- [ ] (opcional) Renomear
handleCorsPrelight→corsPreflightResponsepara tornar o contrato ainda mais explícito (sem checagem implícita). - [x] Memory
mem://security/cors-preflight-method-guardcriada. - [x] Guard de regressão no CI (deno test).