Skip to content

Auditoria PII em Logs — LGPD (2026-05-08)

Status: ✅ FECHADO (2026-05-08) — Etapas 0/1/2/3/4/5/6 concluídas. Lint de CI (scripts/audit/pii-helpers-coverage.ts) ativo no job lint-and-build. Sentinelas Deno verdes (36/36). Memória mem://security/pii-log-masking-standard persistida. Backlog docs/index.md removido. Plano-fonte: /lgpd/PII_LOG_MASKING_PLAN.

Histórico de revisões do plano

DataMudançaMotivo resumido
2026-05-08Etapa 3 (alertas ntfy): especificação trocada de usuario_id para ${papel} @ ${escola_nome} (com fallback ${papel} @ escola:${escola_id_8c}…).UUID é opaco para triagem operacional; papel + escola_nome identifica conta e contexto sem expor PF (PJ não é PII). Enquanto ntfy rodar em terceiro (ntfy.sh), nenhum dado de PF deve sair do domínio.
2026-05-08Replanejamento formal: cada etapa expandida para formato autossuficiente (Status / Pré-req / Reversibilidade / Objetivo / Arquivos / Testes / Aceite / Docs / Volume / Notas).Permitir execução ou replanejamento por etapa, em qualquer interação futura, sem depender de contexto anterior.

Escopo: registros de log que contêm CPF, telefone, e-mail, código (CPF/INEP/CNPJ) e JID WhatsApp em texto plano. Superfícies cobertas: Edge Functions (supabase/functions/), Frontend (src/), Cloudflare Worker (workers/docs-auth), schema (logs_transacoes, RLS), alertas push (ntfy), pipeline de e-mail (email_outbox), exportações. Restrições: nenhuma funcionalidade de listagem/edição/gestão será alterada; o audit trail (logs_transacoes) deve continuar permitindo rastreabilidade operacional e LGPD.


1. Resumo executivo

Foram identificados três grupos de exposição de PII:

  1. registrarLog()logs_transacoes.detalhes (JSONB persistido): 8 sites com PII em claro no resumo/detalhes e 3 sites com vazamento via gerarAlteracoes() (campos email, telefone, codigo/CPF, telefone_secundario faltando no exclude).
  2. console.* em Edge Functions (Supabase Edge Logs, retidos por dias): 7 sites — destaque para send-otp que loga telefone E.164 cru a cada OTP (alto volume).
  3. enviarAlertaPush() para ntfy.sh (terceiro, fora do nosso domínio): 4 sites adicionais (não cobertos pelo scan inicial) que enviam nome_completo do usuário em mensagens de alerta de incidente — verify-password, verify-otp, send-otp (fallback), auth-helpers (token revogado reusado). Risco análogo a console.*, mas com agravante de saída para terceiro.

Não foram encontrados vazamentos:

  • No frontend (src/) — nenhum console.* com CPF, telefone, e-mail ou código.
  • No Worker docs-auth — só registra cookie_names (não valores), user-agent truncado e metadados de host. Sem PII.
  • Nos webhooks Wasender/Resend — payloads incoming não persistem corpo.

Achado de schema: logs_transacoes tem RLS por linha (admin OLP vê tudo; diretor/gestor vêem logs de usuários da própria escola), sem column-level security sobre o JSONB detalhes. Qualquer campo de PII gravado lá é legível por qualquer admin OLP e por diretor/gestor da mesma escola — agravando a importância de mascarar na escrita.


2. Achados (consolidado: scan anterior + extensão)

Legenda: R Remover · M Mascarar · K Manter (justificado) · D Revisar com DPO.

2.1 registrarLog()detalhes JSONB persistido em logs_transacoes

#Arquivo:linhaAçãoDadoJustificativa operacionalClasse
1_shared/gestao-usuarios-helpers.ts:187usuario.createresumo.emailIdentificar qual usuário foi criado✅ CORRIGIDO (Etapa 5, 2026-05-08) — email_dominio+email_hash+codigo_mascarado+telefone_mascarado.
2cadastro-escola-publica/index.ts:231escola.trial_signupemail + cnpjInvestigar fraude de cadastro✅ CORRIGIDO (Etapa 5, 2026-05-08) — email mascarado; CNPJ mantido (PJ não é PII).
3admin-usuarios/index.ts:564usuario.hard_deleteemail + codigo (CPF/INEP/CNPJ)Audit trail: prova de "quem foi deletado"✅ CORRIGIDO (Etapa 5, 2026-05-08) — codigo_mascarado via maskCodigo + email_dominio+email_hash.
4admin-usuarios-escola/index.ts:439idemidemidem✅ CORRIGIDO (Etapa 5, 2026-05-08).
5gestao-usuarios-escola/index.ts:1068idemidemidem✅ CORRIGIDO (Etapa 5, 2026-05-08) — em usuarioExcluido.
6email-unsubscribe/index.ts:89email.unsubscribeemailSaber qual destinatário descadastrou✅ CORRIGIDO (Etapa 5, 2026-05-08) — email_dominio+email_hash.
7process-email-outbox/index.ts:86email.bloqueadoto_emailDiagnosticar por que e-mail foi bloqueado✅ CORRIGIDO (Etapa 5, 2026-05-08) — to_email_dominio+to_email_hash.
8gestao-responsaveis/index.ts:382responsavel.createtelefone cruSuporte por telefone após criação✅ CORRIGIDO (Etapa 5, 2026-05-08) — telefone_mascarado + codigo_mascarado (substituiu cpf.substring(...) ad-hoc).

2.2 gerarAlteracoes() — diff vazando PII (insert no mesmo JSONB)

#Arquivo:linhaCampos vazadosClasse
9user-profile/index.ts:188email, telefone (exclude vazio)✅ CORRIGIDO (Etapa 4, 2026-05-08) — gerarAlteracoes(..., [...PII_CONTATO_FIELDS]) + evento perfil.contato_alterado com campos_alterados (sem valores).
10gestao-usuarios-escola/index.ts:834email, codigo, telefone✅ CORRIGIDO (Etapa 4, 2026-05-08) — exclude estendido + evento usuario.contato_alterado.
11gestao-responsaveis/index.ts:499email, telefone, telefone_secundario (diff manual)✅ CORRIGIDO (Etapa 4, 2026-05-08) — diff manual substituído por gerarAlteracoes + PII_CONTATO_FIELDS no exclude + evento responsavel.contato_alterado.

2.3 console.* — Supabase Edge Logs

#Arquivo:linhaDadoVolumeClasse
12gestao-responsaveis/index.ts:319,467CPF + nomebaixo (criação)CORRIGIDO (Etapa 2, 2026-05-08) — maskCPF + nome→id
13send-otp/index.ts:460,461,466telefone E.164 crualto (toda solicitação OTP)CORRIGIDO (Etapa 1, 2026-05-08) — 4 console.log de debug removidos
14_shared/portal-login-responsavel.ts:121telefone cru (formato inválido)baixoCORRIGIDO (Etapa 2, 2026-05-08) — telefone removido, mantido só comprimento
15_shared/portal-login-aluno.ts:334telefone crubaixoCORRIGIDO (Etapa 2, 2026-05-08) — idem
16gestao-usuarios-escola/index.ts:968email + nome (HARD_DELETE)baixoCORRIGIDO (Etapa 2, 2026-05-08) — console.log redundante removido
17_shared/wasender-whatsapp.ts:102,211errorBody (pode conter JID)baixoCORRIGIDO (Etapa 2, 2026-05-08) — maskTextoComTelefones aplicado

2.4 NOVO — enviarAlertaPush() (ntfy.sh, fora do domínio)

#Arquivo:linhaDadoClasse
18verify-password/index.ts:326nome_completo no corpo do alertaCORRIGIDO (Etapa 3, 2026-05-08) — formatAlertSubject({ papel: tipoCodigo, escola_id })
19verify-otp/index.ts:244nome_completo + IPCORRIGIDO (Etapa 3, 2026-05-08) — formatAlertSubject({ papel: tipoCodigo, escola_id }) + IP mantido
20send-otp/index.ts:500nome_completo (alerta de falha SMS)CORRIGIDO (Etapa 3, 2026-05-08) — formatAlertSubject({ papel: tipoCodigo, escola_id })
21_shared/auth-helpers.ts:121nome_completo (token revogado reusado)CORRIGIDO (Etapa 3, 2026-05-08) — formatAlertSubject({ papel: payload.principal_role, escola_id: payload.escola_id }) + jti curto
22cadastro-escola-publica/index.ts:744nome da escola + cidade (PJ)K — dado de PJ para alerta operacional, não é PII de pessoa física
23_shared/jwt-portal.ts:122 (ALERTA token portal)jti truncado, sem PIIK já OK

2.5 Schema — logs_transacoes

  • RLS atual (3 policies SELECT):
    • logs_admin_select: principal_role = 'administrador' → admin OLP vê tudo (incluindo PII em detalhes).
    • logs_diretor_select_own_school: diretor vê logs de usuários da sua escola.
    • logs_gestor_select_own_school: gestor escola idem.
  • Sem column-level security sobre o JSONB detalhes. Mascarar na escrita é a única defesa eficaz.
  • Classe: K — manter o modelo de RLS por linha; o investimento vai em mascaramento de origem.

2.6 E-mails transacionais e exportações

  • process-email-outbox loga to_email em email.bloqueado (item 7) — coberto.
  • Exportações (*-export-*, geração de planilhas) — varredura não encontrou função que registre o conteúdo do arquivo gerado em log. Apenas inscricao.generate_file com inscricao_id. Sem achado novo.

3. Notas operacionais

  • Princípio aplicado: mascarar na escrita (origem), não na leitura. Único caminho que protege simultaneamente logs_transacoes, Edge Logs e ntfy.
  • Padrão de mascaramento (a ser implementado em _shared/pii-helpers.ts):
    • maskEmail("a@b.com")"a***@b.com" + email_hash (SHA-256 hex truncado em 16) + email_dominio.
    • maskCPF("12345678901")"***.***.***-01".
    • maskTelefone("+5511987654321")"+55 (11) ****-4321".
    • maskCodigo(codigo, tipo): CPF como acima; CNPJ → "**.***.***/****-99"; INEP → 4 últimos.
  • Audit trail preservado: todo log mantém usuario_id (FK), escola_id, acao, criado_em — rastreabilidade de "quem fez o quê" intacta.

4. Próximos passos

  1. Decisão final sobre achados marcados D (nenhum nesta auditoria — todos são R/M/K).
  2. Execução do plano em /lgpd/PII_LOG_MASKING_PLAN, uma etapa por interação.
  3. Releitura desta auditoria após cada etapa para virar status de cada linha.

Referências cruzadas: