Skip to content

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:

ts
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çãoRisco
resend-webhookAceitava callbacks de bounce/complaint/delivered sem validar HMAC. Atacante poderia injetar eventos falsos no outbox_email e DLQ.
email-unsubscribeAceitava 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

  1. Helper documentado (_shared/cors-helpers.ts): comentário JSDoc explícito afirmando que o caller DEVE checar req.method === 'OPTIONS' antes de chamar. Tipo de retorno mantido como Response (sempre) para não quebrar 38 callsites válidos. A segurança vem do guard de regressão (item 3) + alinhamento dos callsites quebrados.
  2. Callsites alinhados: resend-webhook e email-unsubscribe agora usam if (req.method === 'OPTIONS') return handleCorsPrelight(req); — padrão único em todo o repositório.
  3. Guard de regressão: _shared/__tests__/cors-helpers.test.ts faz scan estático de todos os index.ts em supabase/functions/ e falha se detectar o padrão const pre = handleCorsPrelight(req); if (pre) return pre;.
  4. ADR: docs/architecture/adrs/0007-cors-helper-strict-method-guard.md.
  5. 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 handleCorsPrelightcorsPreflightResponse para tornar o contrato ainda mais explícito (sem checagem implícita).
  • [x] Memory mem://security/cors-preflight-method-guard criada.
  • [x] Guard de regressão no CI (deno test).