Commit Graph
8 Commits
Author SHA1 Message Date
melgarafaelandClaude Opus 5.5 ca1da775ea build(deps): Sentry 11 com coleta restrita e scrub provado pelo SDK
O Sentry 11 trocou `sendDefaultPii` por `dataCollection`, com default AMPLO
(IP, cookies, corpo de requisição/resposta, entrada/saída de IA, dados de
query, filas). Omitir a opção, que era o conserto óbvio para o TS2353, faria o
DSN da comunidade receber dado de titular de terceiros (issue #100).

- lib/sentry/privacidade.ts: ponto único com `dataCollection` restritivo
  explícito (base "v10" do MIGRATION.md + stackFrameVariables: false) e os
  hooks de scrub; espalhado nos 4 Sentry.init (server, edge, client, worker).
- lib/sentry/scrub.ts: beforeSendSpan no formato streamed (name/attributes),
  limpando também `sentry.segment.name`, headers-credencial/IP, corpo, user.* e
  client.address; beforeSend apaga request.data, request.cookies e user e passa
  a mensagem por scrubUrl (o token do path saía na mensagem do erro);
  beforeSendTransaction removido (no-op no 11, confirmado pelo guia).
- lib/sentry/privacidade.sdk.test.ts: prova pelo envelope do SDK real, com
  transporte de captura e controles positivos; 4 sabotagens ficam vermelhas.
- withSentryConfig de @sentry/nextjs/config; enableLogs removido; AGENTS.md
  cita Sentry 11; fragmento em .changes/.

Substitui #1743.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:57:31 -03:00
melgarafaelandClaude Opus 5.5 c6bee7d6a4 fix(privacidade): o UUID é poupado pela forma, e o CPF grudado no rótulo volta a sair
A borda de letra e hífen posta nos padrões de CPF e de telefone colado
parou de comer pedaço de UUID, mas passou a deixar sair inteiro o número
grudado nos rótulos que alguém digita colado: "cpf12345678909",
"fone11987654321", "tel-11987654321", "lead-123.456.789-09". É o mesmo
texto que vai à TypeSafe, e a tela de aceite promete apagar CPF e
telefone antes de sair.

Agora o UUID é separado do texto pela forma exata (8-4-4-4-12 em
hexadecimal) antes dos padrões numéricos, que voltam a não ter borda de
letra. Medido em 80.000 textos com UUID: 0 alterados (a main alterava
1.988). A borda do telefone de 8 dígitos fica, com o motivo corrigido:
sem ela, 1.390 de 5.000 SHA-1 saíam alterados.

Sabotado nos dois sentidos: sem a separação do UUID, o teste dos 5.000
UUIDs reprova; com as bordas de antes, os dois testes do número grudado
reprovam.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PWGNXXzCQURHMALcEMmkC
2026-09-23 22:21:48 -03:00
melgarafaelandClaude Opus 5.5 bb2469f903 fix(privacidade): o CPF com separador deixa de comer pedaço de UUID
Aceitar hífen entre os blocos do CPF sem borda fora deles fazia o
padrão casar dentro de UUID: em 5.000 UUIDs gerados, 67 casavam no
CPF, e o padrão do telefone colado em texto (anterior a esta frente)
casava em outros 126 — 141 saíam alterados no total. O CPF ganha borda
sem letra, dígito ou hífen vizinho; o telefone colado ganha borda só de
hexadecimal e hífen, os vizinhos possíveis dentro de um UUID, para
seguir pegando o número grudado em palavra ("zap11987654321").

O teste do UUID fixo passava por sorte; agora são 5.000 UUIDs
determinísticos (sha256 do índice), e sem qualquer uma das duas bordas
ele reprova.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PWGNXXzCQURHMALcEMmkC
2026-09-23 21:53:15 -03:00
melgarafaelandClaude Opus 5.5 126efd2e3e fix(privacidade): o scrubMessage apaga telefone e CPF com ponto, hífen ou espaço entre os blocos
O aceite de LGPD do Jev promete que CPF e telefone saem apagados antes de a
mensagem ir à TypeSafe. "11-98765-4321", "11.98765.4321", "123 456 789 09",
"123.456.789.09" e "123-456-789-09" passavam inteiros. O DDD agora aceita
hífen ou ponto, o 9 da frente pode vir solto, e o CPF aceita qualquer
separador entre os blocos. Os casos de UUID, data e hora seguem intactos.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PWGNXXzCQURHMALcEMmkC
2026-09-23 20:39:29 -03:00
melgarafaelandClaude Opus 5.5 625f895a6b fix(privacidade): o scrubMessage apaga o telefone como se escreve no Brasil
O padrão exigia o DDD colado ao número e sem parênteses. "(11) 98765-4321",
"98765-4321" e "3456-7890" passavam inteiros — para o Sentry e para o Jev,
cuja tela promete ao admin, na hora do aceite, que o telefone sai apagado.

- Padrão novo com borda: +55 e DDD opcionais, parênteses opcionais, 8 ou 9
  dígitos com hífen, espaço ou nada. A borda impede comer pedaço de UUID.
- O padrão antigo fica depois do CPF, para o número colado em outro texto.
- E-mail passa a ser apagado antes dos números: o telefone comia dígitos de
  dentro do endereço e deixava o resto passar.

Medido no código antigo: 2 casos novos vermelhos; no novo, verdes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PWGNXXzCQURHMALcEMmkC
2026-09-23 19:05:34 -03:00
melgarafaelandClaude Opus 5.5 ba1377fe02 feat(ia): fundação do Jev — chave cadastrável, cliente fixo, disjuntor e interruptor
Onda 1 da integração do Jev (TypeSafe AI). Nada de worker, tela ou rota
nova ainda; o que entra é o que as próximas ondas consomem.

- Provedores: lista IRMÃ `PROVEDORES_DE_DECISAO` (typesafe) e a união
  `PROVEDORES_COM_CHAVE`/`IDS_COM_CHAVE`. `PROVEDORES` não muda, então
  nenhuma superfície de conversa enxerga o Jev. Só as de CHAVE pedem a
  união (rota de credenciais, diálogo, cartão, lista, rotação, hook,
  validador). A lista de credenciais agrupava só por `PROVEDORES` e
  descartava a chave do Jev; o aviso "sem chave" do painel e o da lista
  contam só chave de quem conversa.
- Catraca testada: o Jev fora do registry, do ensaio, do schema de
  versão, do PUT/PATCH do painel e do passo da chave do onboarding; e
  quem usa a união tem de estar declarado.
- Validador: `GET {base}/v1/models`, que exige a chave e não gasta
  token (401/403 → auth_failed_401; lê `models[].name`).
- Cliente: versão fixada `jev-1.13.0`, teto 1,5 s, eixo `exigeAcao`
  (401/403/402/4xx não mapeado/400/422), `sem_credito`, `retryAfterMs`,
  base em `JEV_API_BASE_URL` (vazio = api.typesafe.ai). O 401 deixa de
  ser "defeito nosso".
- Disjuntor em memória por organização, com relógio injetável.
- Interruptor em `organizations.settings.jev` (Zod, sem migration):
  leitura nunca lança e falha desligada; ligado sem aceite = desligado.
- `chaveDaOrganizacao` real: credencial typesafe ativa e validada mais
  recente da organização, decifrada; nunca lança.
- Preço na tabela única (`pricing.ts`): 4,2 ¢/Mtok de entrada, saída
  grátis, id exato — custo fracionário, fora do Math.ceil.
- `log-invocation`: `typesafe/…` deixa de virar OpenRouter; aceita
  provider e origem_da_escolha explícitos.
- Redação da chave `apikey_…` nos dois redatores.
- Registro: `decisaoRapida` em sentiment_classify, com teste de
  chamador nos dois sentidos.
- Espanhol dos textos novos, e o gate que faltava para `quandoUsar` e
  textos do registro (pegou a DeepSeek e mais cinco textos antigos).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PWGNXXzCQURHMALcEMmkC
2026-09-23 14:30:00 -03:00
Rafael MelgaçoandClaude Opus 5 23f437a231 fix(sentry): scrub deixa de nomear provider — o guard do repo pegou
O lint-channels reprovou lib/sentry/scrub.ts: a doutrina de restrição de canal
(invariante 1) proíbe nomear provider fora de lib/channels/, e o arquivo citava
um nos comentários e na lista de headers. Os três configs do Sentry ficam na
raiz, fora de ROOTS, então nunca foram varridos — mover a lógica para lib/ a
colocou sob o guard. O guard está certo.

As duas correções deixam o código melhor, não só conforme:

- O segmento do canal no CREDENTIAL_PATH virou [^/]+ em vez de uma lista de
  provider. Rota de webhook de canal NOVO ganha a proteção sozinha, em vez de
  vazar token até alguém lembrar de somar o nome à regex.
- Header sensível passa a ser casado por PADRÃO (authorization|cookie|api-key|
  token|secret|password|credential) em vez de lista fechada. Mesma lógica:
  header de integração nova nasce coberto. Tem teste com um header inventado.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C4NxbuaBH2rNizak9HkDpd
2026-08-03 12:59:42 -03:00
Rafael MelgaçoandClaude Opus 5 cde1ebcf37 fix(sentry): telemetria vazava token de webhook pelo canal de tracing
O beforeSend cobria erro e mensagem, e vivia TRIPLICADO verbatim nos três
runtimes. Transação, span e breadcrumb têm hooks próprios — que não existiam
em lugar nenhum. Com tracesSampleRate: 1, o canal sem sanitização era
justamente o de 100% de amostragem.

Medido com um receptor no lugar do Sentry, usando a config exata do repo: a
URL crua saía em transaction, request.url, url.full, http.url, url.path e
http.target. Quatro rotas têm credencial no path, e o token do webhook do WAHA
é a credencial ÚNICA daquela rota na instalação padrão —
WAHA_WEBHOOK_REQUIRE_SIGNATURE nasce false porque o WAHA Core não assina, e o
Caddyfile documenta que a rota por tenant continua pública apoiada em o token
ser imprevisível. A telemetria publicava esse token.

- lib/sentry/scrub.ts: ponto único com os quatro hooks. Redige o token das 4
  rotas em que ele é credencial e o VALOR da query preservando a chave. Os 134
  segmentos [id] são UUID e ficam de fora de propósito: redigir tudo cegamente
  tornaria o Sentry inútil pra depurar, que é o oposto do objetivo.
- No DSN da comunidade agora vai só ERRO: tracesSampleRate 0 e
  replaysSessionSampleRate 0. Quem aponta pro próprio Sentry recebe tudo,
  porque aí o dado não sai da infraestrutura de quem é dono dele.
- install.sh pergunta durante a instalação; em --yes sem SENTRY_DSN, desligado.
- Removidas as rotas de exemplo do assistente do Sentry: sentry-example-api era
  um GET sem auth que lançava de propósito, ou seja, qualquer um podia gerar
  evento no Sentry dos mantenedores à vontade.

Um defeito do próprio scrub só apareceu ao rodar o envelope inteiro: o Sentry
preenche request.query_string com a query CRUA, sem '?' na frente, e a primeira
versão do regex exigia o prefixo — a assinatura sobrevivia nesse campo. Tem
teste de regressão.

Relatado por @integralmarketingmx na issue #100, com diagnóstico próprio e uma
autocorreção sobre os Session Replays.

Refs #100

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C4NxbuaBH2rNizak9HkDpd
2026-08-03 12:50:27 -03:00