ADR-009 — Sanitização de Erro em 2 Camadas
| Campo | Valor |
|---|---|
| Status | ✅ Aceito |
| Data | 2026-04-30 |
| Autores | Equipe OLP |
| Implementação | src/lib/error-helpers.ts (frontend), _shared/auth-helpers.ts + handlers (backend) |
Contexto
Erros não tratados vazam:
- Stack traces com nomes de tabelas, colunas, paths internos
- Mensagens do Postgres ("violates foreign key constraint
fk_aluno_escola") indecifráveis para usuário - Detalhes de implementação que ajudam atacantes (ex.: "user not found" vs "wrong password")
Ao mesmo tempo, logs internos precisam do detalhe completo para debug.
Decisão
Erros passam por duas camadas obrigatórias antes de chegar ao toast:
Camada 1 — Backend (Edge Function)
Toda Edge Function tem try/catch global. No catch:
- Loga internamente o erro completo via
console.error+registrarLog(severidadeerro) - Retorna ao cliente apenas:
- HTTP status semântico (401/403/400/404/409/500)
{ success: false, message: "<mensagem amigável>", code?: "<código semântico>" }
- Nunca propaga
error.stack,error.detail, código Postgres bruto, nome de tabela - Mapeamento de erros Postgres comuns → códigos semânticos:
23505(unique violation) →code: 'duplicate', message PT23503(foreign key) →code: 'fk_violation'42501(insufficient privilege) → 403
- Auth errors usam classe tipada
AuthError(message, 401|403)para evitar heurística por substring.
Memória: backend-error-sanitization-contextual-standard, error-helper-postgres-mapping-standard.
Camada 2 — Frontend (React Query onError)
Toda mutação que pode falhar usa:
onError: (error) => {
olpToast.error(getUserFriendlyError(error));
}getUserFriendlyError em src/lib/error-helpers.ts:
- Reconhece o formato
EdgeResponse(já sanitizado pelo backend) - Mapeia
codesemântico → mensagem PT contextualizada - Para erros de rede (
_transient: true): "Falha na conexão. Tente novamente." - Para erros desconhecidos: mensagem genérica + log em DEV
- Nunca exibe
error.messagecru
Alternativas consideradas
A. Camada única no frontend (parsing de qualquer erro)
Pros: backend mais simples. Cons: stack traces vazam; dependência da disciplina do front; testes complicados. Veredicto: rejeitado — defesa em profundidade.
B. Camada única no backend
Pros: front trivial. Cons: erros de rede e timeout do navegador continuam crus; UX inconsistente. Veredicto: rejeitado.
C. Bibliotecas de error tracking (Sentry) só, sem sanitização
Pros: telemetria rica. Cons: tracking ≠ UX; usuário ainda vê stack. Veredicto: complementar, não substituto.
Consequências
Positivas
- Usuário vê sempre mensagem em PT, contextual
- Atacante não obtém estrutura de tabelas via mensagens de erro
- Logs internos mantêm detalhe completo para debug
- Toast unificado (
olpToast) — proibidoalert()ousonnerdireto
Negativas
- 2 lugares para mapear erros novos (mitigado por catálogo central de códigos)
- Mensagem genérica pode ser "menos útil" — mitigado por
codesemântico que dá pista
Trade-offs
| Eixo | Custo | Benefício |
|---|---|---|
| Defesa em profundidade | 2 camadas | Resistente a erro de uma camada |
| Curadoria de mensagens | Tradução PT manual | Tom de voz consistente |
| Debug | Detalhe vai para log, não toast | Privacidade e segurança |
Conformidade
Como verificar:
# Toast nunca recebe error.message direto
rg "olpToast\.error\(.*error\.message" src --type ts
# Toast nunca recebe error.toString
rg "olpToast.*error\.toString" src --type ts
# Edge Function não retorna error.stack
rg "error\.stack" supabase/functions --type tsSinais de violação:
toast(error.message)semgetUserFriendlyError- Edge Function retornando
{ error: err }(objeto cru) alert(...)em qualquer lugar- Import direto de
sonnerem componente (deve serolpToast)
Débito técnico conhecido
- Cobertura de mapeamento PT em
getUserFriendlyErrorcresce sob demanda — quando código novo aparece em prod, é adicionado ao mapa.
Referências
src/lib/error-helpers.tssupabase/functions/_shared/auth-helpers.tsmem://architecture/backend-error-sanitization-contextual-standardmem://architecture/error-helper-postgres-mapping-standardmem://architecture/auth-and-idor-status-code-semanticsmem://guidelines/silent-save-prevention- ADR-003 — formato de resposta
- ADR-007 — onde é consumido