Covil do Mineiro — Docs

Segurança

O modelo de segurança do Covil é de defesa em profundidade: cada operação sensível é protegida em mais de uma camada, de modo que uma falha isolada não vira brecha. Este documento descreve o modelo e as regras que não podem ser violadas.

As duas camadas

  1. Guarda na aplicação — Server Actions e páginas admin verificam autenticação e autorização (papel + seção) antes de qualquer efeito.
  2. RLS no Postgres — mesmo que a camada 1 falhe ou seja contornada, as policies do banco barram leitura/escrita indevida. A RLS é o anteparo final.

A lógica crítica (associação, pontos, cupons, carteirinha) fica em funções SECURITY DEFINER no banco, que validam o papel por dentro — ver banco-de-dados.md.

Regras invioláveis

1. O cliente nunca é confiável

createCheckoutAction (checkout-actions.ts) rebusca cada produto/ingresso no banco, reaplica o preço de sócio, revalida o cupom no servidor (cupom pessoal só vale para o dono) e recomputa o total. O preço/total vindo do cliente serve só para exibição. Estoque e limite por pedido de ingresso também são validados no servidor.

2. Toda ação admin tem guarda de papel e seção

Escritas admin passam por getAdminClient/getPrivilegedClient, que chamam requireSectionAccess (papel tem acesso àquela seção?) antes de tocar o banco. O mapa SECTION_BY_PATH em admin-actions.ts é fail-closed: se um path não estiver mapeado, o acesso é negado (não liberado para "qualquer admin").

3. Service role isolado no servidor

supabase/admin.ts usa a service role key, que ignora a RLS. Ele tem import 'server-only' e só deve ser usado no webhook e em escritas de pedido no servidor. Nunca o importe em Client Component — o next build quebra de propósito se isso acontecer.

4. Webhook de pagamento confirmado por reconsulta autoritativa

webhook/pix/route.ts:

  • Não há assinatura HMAC nem segredo de webhook. A garantia é a reconsulta autoritativa da cobrança por txid na API do Sicoob (getPixCharge): o pedido só vira paid se a cobrança estiver CONCLUIDA na própria API.
  • Por isso um corpo forjado não confirma pagamento — a fonte da verdade é a API do Sicoob, não o corpo recebido no webhook.
  • O processamento é idempotente: se o status efetivo não muda, sai cedo; earn_points/reverse_purchase_points não duplicam efeito.

5. Segredos nunca vazam para o cliente

Só variáveis NEXT_PUBLIC_* chegam ao browser. Segredos — SUPABASE_SERVICE_ROLE_KEY, SICOOB_*, GMAIL_*, CARD_SIGNING_SECRETnão têm esse prefixo e são lidos apenas no servidor (env.ts). Nunca commite .env.local.

6. Dados sensíveis do perfil são imutáveis pelo cliente

Triggers no banco tornam role, status, points_balance e os campos membership_* somente-leitura via cliente (guard_*). Alterações passam por funções SECURITY DEFINER (ex.: assign_membership_card, adjust_points).

7. Ledger de pontos imutável

Saldo de pepitas = soma de loyalty_transactions (append-only). Correção é uma nova transação (adjust/reversal), nunca UPDATE/DELETE. O profiles.points_balance é só um cache reconciliável.

8. Verificação pública da carteirinha sem PII

verify_membership_card(token) (executável por anon) devolve apenas nome, foto, número, status, período e plano — nunca e-mail, telefone ou RA. O código de autenticidade é um HMAC (membership-card.ts) reexibido para conferência visual.

Privacidade / open redirect

  • normalizeNextPath barra open redirect no login (rejeita //host, /\host).
  • Reset de senha responde de forma uniforme (anti-enumeração de e-mail).
  • Não coloque dados pessoais em URL/query string.

Histórico de auditoria

  • 2026-08-07 — auditoria de segurança. Corrigidos no código + migration 20260807120000_security_hardening.sql: remoção de auto-escalada a super_admin; activate_membership/redeem_coupon deixaram de ser executáveis por public; separação de is_admin(); cupons não legíveis por anon; storage com escopo de dono; correção de open redirect.
  • 2026-08-11 — correções de robustez: validação de estoque/limite de ingresso no checkout (anti-oversell), unificação do cálculo de pepitas no webhook e autorização admin fail-closed.

⚠️ Pendências operacionais: confirme em produção que a migration 20260807120000_security_hardening.sql foi aplicada e que os segredos estão configurados (CARD_SIGNING_SECRET, SICOOB_*). Ver deploy.md.

On this page