Skip to content

ADR-012 — handleCorsPrelight exige checagem explícita de método pelo caller

  • Status: Accepted
  • Data: 2026-05-09
  • Decisores: backend / segurança
  • Contexto-pai: incidente CORS preflight bypass (docs/security/INCIDENTS.md §2026-05-09)

Contexto

Antes de 2026-05-09, o helper supabase/functions/_shared/cors-helpers.ts:handleCorsPrelight() retornava Response(204) incondicionalmente. Duas funções (resend-webhook e email-unsubscribe) usavam o padrão:

ts
const pre = handleCorsPrelight(req);
if (pre) return pre;

Como handleCorsPrelight SEMPRE retorna Response, toda requisição (POST, PATCH, etc.) era curto-circuitada com 204 ANTES de qualquer validação de método/HMAC/auth.

Isso configurou um bypass real de validação HMAC no webhook Resend.

Decisão

  1. Contrato do helper: o caller é responsável por checar req.method === 'OPTIONS'. O helper retorna 204+CORS sempre — esse é o nome da função (handleCorsPrelight) e o tipo de retorno (Response). Não há "auto-detect".

  2. Padrão único de uso em toda a codebase:

    ts
    if (req.method === 'OPTIONS') return handleCorsPrelight(req);

    Padrões alternativos como const pre = …; if (pre) return pre; são PROIBIDOS.

  3. Guard de regressão automatizado: _shared/__tests__/cors-helpers.test.ts faz scan estático recursivo em supabase/functions/ e falha o CI se detectar o padrão proibido.

Alternativas consideradas

A) Helper retorna Response | null com checagem interna

ts
export function handleCorsPrelight(req: Request): Response | null {
  if (req.method !== 'OPTIONS') return null;
  return new Response(null, { status: 204, headers: getCorsHeaders(req) });
}

Rejeitada: quebra typing em ~38 callsites que retornam diretamente return handleCorsPrelight(req); dentro de if (req.method === 'OPTIONS'). Forçaria refactor massivo (return handleCorsPrelight(req)!;) sem ganho de segurança real (a segurança vem do scan estático, não da checagem interna).

B) Renomear para corsPreflightResponse

Postergada: nome mais explícito, mas exige refactor de 40 arquivos. Marcado como follow-up opcional em docs/security/INCIDENTS.md.

C) Dois helpers (com e sem guard interno)

Rejeitada: duplica API e introduz ambiguidade ("qual eu uso?"). O scan estático resolve sem custo de cognição.

Consequências

Positivas:

  • Padrão único, fácil de revisar.
  • Guard estático bloqueia regressão sem depender de revisão humana.
  • Compatível com 38 callsites pré-existentes — zero refactor desnecessário.

Negativas / riscos remanescentes:

  • Helper continua sendo "trapaceiramente útil" (sempre retorna Response). Mitigado por JSDoc + scan + ADR.
  • Se alguém criar um novo padrão buggy não coberto pelo regex do scan, passa. Mitigação: o regex cobre o exato padrão observado; padrões novos exigem nova análise de incidente.

Referências

  • supabase/functions/_shared/cors-helpers.ts (implementação + JSDoc)
  • supabase/functions/_shared/__tests__/cors-helpers.test.ts (guard)
  • docs/security/INCIDENTS.md §2026-05-09 (incidente)
  • mem://security/cors-preflight-method-guard (memory)