TL;DR
Status: BLOQUEAR LAUNCH até 1 item crítico ser resolvido. Fora isso, projeto está em estado bom — dá pra subir em ~3 dias de polimento focado. Veredicto: Engenharia já amadureceu desde a auditoria de 23/04. Tem RLS em tudo, 67 migrations versionadas, Sentry integrado, páginas legais (LGPD/CDC) muito bem escritas, Stripe live com Edge Functions. Os bloqueadores são pontuais — não estruturais. Metodologia desta auditoria (diferente da anterior):- ✅
npm run buildrodado de verdade (passou, mas com warnings importantes) - ✅
npm run lintrodado (passou com erros não-bloqueantes) - ✅
tsc --noEmitrodado (passou limpo) - ✅ Supabase MCP:
get_advisors security+performance+ listagem de RLS em todas as tabelas - ✅ 4 agentes paralelos investigando segurança, fluxos críticos, prontidão produção, silent failures
🚨 BLOQUEADORES — resolver ANTES do launch
B1. Stripe LIVE secret + webhook secret committed no git history
Onde:SETUP_QUANDO_VOLTAR.md:13-14 — committed no commit 147fb2d (“feat: rebrand pra Pix do Pix + templates de email + paginas legais”)
O que vazou:
https://github.com/gomktia/go-financas-inteligentes retornou 404 — está privado. Mas:
- Está no git history pra sempre (a menos que faça force-push com history rewrite)
- Qualquer colaborador atual ou futuro com acesso ao repo tem a chave
- Se virar público algum dia, vaza
- Backups, forks privados, espelhos — exposição lateral
- Stripe Dashboard → API Keys → roll o secret key live (gera novo, invalida o velho)
- Stripe Dashboard → Webhooks → roll o signing secret do endpoint
- Atualizar os secrets no Supabase (Edge Functions)
- Deletar
SETUP_QUANDO_VOLTAR.mddo filesystem - Criar
SETUP_QUANDO_VOLTAR.mdem formato sanitizado (sem nenhum secret) e versionar - Reescrever git history pra remover o arquivo (
git filter-repoou BFG Repo-Cleaner) — opcional mas recomendado - Confirmar via Stripe Dashboard que a chave antiga está rejeitando requests
Por que isso é bloqueador absoluto: cobrança real envolvida. Quem tiver a chave pode emitir charges, refunds, pegar dados de clientes pagantes — não é hipotético.
⚠️ ALTO — resolver antes do launch
A1. Bug de React Hooks em produção — app/cartoes/[id]/page.tsx
Onde: linhas 87, 92, 108
O que está errado:
Failed to compile. aparece no log mas exit é 0). Já causou bug em produção em outros projetos.
Fix: Mover os 3 useMemo pra ANTES dos early returns, ou refatorar para componente separado.
A2. App /sentry-example-page exposta em produção
Onde: app/sentry-example-page/page.tsx
Risco: Página acessível por qualquer um sem auth. Botões disparam errors reais (client + server actions). Comentário no código diz “Em produção, esta página fica acessível mas só deve ser usada por dev/QA.” — confiar em política, não em código.
Consequências:
- Qualquer um polui Sentry com erros falsos
- Quota Sentry estourada (tem limite na conta gratuita)
- Atacante usa pra reconnaissance (descobre internals)
- DoS no Sentry sink
app/dev/ofx-test/page.tsx está protegida corretamente: if (process.env.NODE_ENV !== 'development') notFound(). Aplicar o mesmo padrão.
Fix: Adicionar no topo do componente (server side, não em useEffect):
A3. Variáveis de ambiente não documentadas (6 críticas)
.env.local.example documenta apenas 2 mas o código usa:
SUPABASE_SERVICE_ROLE_KEY—lib/supabase-admin.ts:8(admin actions)SENTRY_AUTH_TOKEN—next.config.js:11(upload source maps)NEXT_PUBLIC_SENTRY_DSN— Sentry initsNEXT_PUBLIC_SENTRY_DEBUG/SENTRY_DEBUGRESEND_API_KEY—lib/email.tsvia Edge FunctionSTRIPE_SECRET_KEY— admin/integracoes detectionGOOGLE_GENAI_API_KEY— não localizei o uso, mas grep encontrou (verificar se ainda é usado ou remover)
.env.local.example com TODAS as vars + comentário curto explicando origem de cada uma. Adicionar uma checagem de “vars obrigatórias” em instrumentation.ts que loga aviso visível em boot se faltar.
A4. Open Redirect potencial em /auth/callback
Onde: app/auth/callback/page.tsx:51-54
O que existe: Lê ?next= e ?redirect= de query params, valida apenas com next.startsWith('/').
Bypass possível: ?next=//attacker.com/phishing — startsWith('/') é true, mas browser interpreta //domain como protocol-relative URL → vai pra attacker.com.
Fix:
A5. Edge Runtime warnings — middleware pode quebrar
Build log mostra:@supabase/ssr → que importa @supabase/supabase-js → que toca process.version. Em Vercel pode lançar runtime error em request.
Fix: Já é um problema conhecido do @supabase/ssr. Verificar se a versão atual (^0.7.0) tem o fix. Se não, considerar pin em versão que evita ou aceitar risco e monitorar Sentry.
A6. app/error.tsx não reporta para Sentry
Onde: app/error.tsx:14-18
O que faz: console.error apenas em dev. Em produção: silêncio total.
Consequência: Erros que caem em error boundaries de rotas (todas exceto raiz) não chegam ao Sentry. Você vê o user na tela “Algo deu errado” mas não tem stack trace.
Fix:
A7. Sentry sem release e environment configurados
Onde: Todos os sentry.*.config.ts
O que falta:
- Source maps não são associados ao deploy específico → stack traces minified em prod
- Bugs de preview branches misturam com produção no dashboard
- Não dá pra fazer “compare entre releases”
A8. Silent failures críticos
| # | Onde | O quê | Por que importa |
|---|---|---|---|
| 1 | lib/email.ts:27-37 | Email via Edge Function falha → só console.warn, retorna { ok: false } mas ninguém checa o retorno | Resend cair em prod = você descobre só quando user abrir ticket |
| 2 | app/cartoes/_actions/ofx-actions.ts:404 | Assinaturas detectadas durante OFX import — se assinErr, log e segue sem retornar erro | User acha que assinaturas foram criadas mas não foram |
| 3 | app/cartoes/_actions/ofx-actions.ts:137-140 | suggestCategoriesFromHistory() em Promise.all com .catch(() => ({})) | Sugestões silenciosamente quebram |
| 4 | lib/auth.ts (várias) | Catches retornam { error } mas nenhuma chamada reporta pra Sentry | Não dá pra saber se há credential stuffing rolando |
Sentry.captureException(err, { tags: { module: 'email' } }) em cada catch que hoje só loga.
📋 MÉDIO — resolver no primeiro mês pós-launch
M1. PII em logs do Sentry (LGPD-relevante)
Onde:app/cartoes/_actions/ofx-actions.ts linhas 307, 407, 416, 426, 602, 611, 695, 709, 843
Exemplo: Linha 407: console.log('ofx_assinatura_insert_success', { userId, workspaceId, created: inserted?.map((a) => a.nome) ?? [] }) → Sentry captura userId, workspaceId, e nomes “Netflix, Spotify, Claude Pro, Pix do Pix”.
Por que importa para LGPD: A política de privacidade promete “Sem dados de transações em logs públicos.” Esses logs vão pro Sentry US — dado de transação financeira fora do BR sem anonimização. Multa ANPD potencial.
Fix: Substituir userId por hash, valores por contadores ({ count: N }), nomes por categorias agregadas.
M2. CSRF em admin actions
Onde: Todaapp/admin/_actions/*.ts
Problema: Server actions aceitam userId como parâmetro sem validar Origin header. Se admin abrir um link malicioso enquanto logado, JS pode disparar resetPassword(targetUserId) ou deleteUser().
Mitigações já existentes: assertSuperAdmin() em todas as ações + middleware. Mas isso só valida que o caller é admin — não protege contra CSRF onde o admin é a vítima.
Fix: Next 15 server actions já têm proteção CSRF nativa (Origin header check). Confirmar que serverActions.allowedOrigins está configurado e que requests de outros domínios são rejeitados. Adicionar test de regression.
M3. lib/supabase-admin.ts sem 'server only' enforcement
Onde: Importável de qualquer lugar.
Risco: Algum dev acidentalmente importa em arquivo 'use client' → bundle final exposing service role key (catastrofe).
Fix: Adicionar import 'server-only' no topo de lib/supabase-admin.ts. Vai falhar build se importado de client.
M4. Logout só local
Onde:components/auth-provider.tsx:116
supabaseAdmin.auth.admin.signOut(userId, 'global') em background depois do local signOut. Pode demorar, mas user já saiu do app.
M5. Limites por plano: rate limit DB-level ausente
Onde:app/cartoes/_actions/ofx-actions.ts:79-98, app/crypto/_actions/crypto-actions.ts:34-76
Problema: Limite de OFX (1/free/mês) e crypto (3/free) é checado no servidor antes do INSERT, mas sem lock. Race condition possível: 11 requests paralelos com plano free passam pelo check, todas inserem.
Fix: RPC PostgreSQL com SELECT ... FOR UPDATE + insert na mesma transaction. Ou unique constraint UNIQUE(user_id, date_trunc(‘month’, created_at)) onde aplicável.
M6. tabela rate_limit_log com RLS sem policies
Supabase advisor flagged: RLS Enabled No Policy em public.rate_limit_log.
Estado atual: RLS habilitado mas zero policies → ninguém (exceto service role) consegue ler/escrever. Provavelmente intencional (só check_rate_limit RPC mexe), mas é warning.
Fix: Documentar a intenção via policy explícita: CREATE POLICY "deny_all" ON rate_limit_log FOR ALL USING (false); OU adicionar comentário SQL na migration explicando por que está vazio.
M7. PLAN_LIMITS duplicado em 3 lugares
Onde:lib/constants.ts:30— sóOFX_IMPORTS_PER_MONTHhooks/use-subscription.ts:27-61—PLANScompleto (priceBRL, pessoal, familia, etc)app/crypto/_actions/crypto-actions.ts:34—PLAN_LIMITS = { free: 3, pro: 10, business: 100 }hardcoded
lib/plans.ts com tipo PlanKey e função getLimitsFor(plan). Remover duplicações.
M8. pg_net extension em schema public
Supabase advisor flagged: WARN.
Fix: Mover pra schema extensions ou similar. Migration:
pg_net é usado por triggers de mensalidades reminders.)
M9. Leaked Password Protection desabilitado
Supabase advisor flagged: Auth não está checando senhas contra HaveIBeenPwned. Fix: Supabase Dashboard → Auth → Policies → habilitar “Leaked password protection”. Sem custo, sem código.M10. Falta protection no admin route guard durante timeout
Middleware (middleware.ts:46-58) usa Promise.race com timeout 5s na RPC is_super_admin. Se RPC demora, data: null → trata como não-admin → redirect home (fail closed, OK). Mas em lib/admin-auth.ts:15-19, server actions chamam a mesma RPC SEM timeout. Inconsistência: middleware fail-closed em 5s, action espera para sempre.
Fix: Adicionar timeout local na action também (ex: 10s) com fallback para AdminAuthError('FORBIDDEN').
✨ BAIXO — backlog pós-launch
| # | Item | Onde |
|---|---|---|
| 1 | 7 warnings <img> ao invés de <Image /> | login, sidebar, header, legal-layout, auth/callback, auth/reset-password |
| 2 | 4 errors de unescaped entities " | salarios/page.tsx:142, importar-csv-sheet.tsx:525 |
| 3 | 18 console.log em código de produção fora de testes | usar Sentry breadcrumbs ou remover |
| 4 | Sem banner de cookies (LGPD nice-to-have já que só usa cookies necessários) | criar <CookieBanner /> |
| 5 | Sem robots.txt nem app/sitemap.ts | criar app/robots.ts e app/sitemap.ts |
| 6 | app/layout.tsx sem twitter cards, icons, robots no metadata | enriquecer metadata |
| 7 | .gitignore não cobre *.log, .playwright-mcp/, gastos-desktop.png, AUDITORIA_TECNICA.md (root) | adicionar |
| 8 | .vercelignore ignora package-lock.json — risco de deploy não-reproducível | remover linha |
| 9 | README.md ainda fala de localStorage e estrutura antiga | reescrever (já flagged em 23/04) |
| 10 | DATABASE_STRUCTURE.md completamente fora da realidade — descreve schema teórico que não bate com migrations reais | deletar ou reescrever |
| 11 | Pendência declarada em SETUP_QUANDO_VOLTAR.md: emails transacionais não estão sendo chamados nas ações do app | wire lib/email.ts em signup, checkout success, convite |
| 12 | package.json ainda com "next": "latest" | pin em versão específica |
| 13 | Privacidade fala “hash bcrypt” mas Supabase usa scrypt — inconsistência menor | corrigir texto |
| 14 | Privacidade promete “exportação JSON/CSV via app” — implementado? | confirmar ou remover |
| 15 | Privacidade fala “2FA via magic link/OTP” — magic link existe, mas 2FA propriamente dito? | clarificar |
| 16 | Sem npm test script no package.json (testes existem em lib/__tests__/) | adicionar vitest + script |
| 17 | staleTime: 30_000 copiado em ~20 hooks | extrair lib/query-defaults.ts |
| 18 | Magic number .limit(2000) em use-gastos.ts | constante + paginação |
| 19 | Sentry: avaliar replaysOnErrorSampleRate para session replays só em erros (sem custo extra significativo) | optional |
| 20 | Auditoria antiga (23/04) listou A1/A2/A3 de alta severidade — ainda válidos: zero testes automatizados estruturais; service layer; app/page.tsx complexa | continuar plano da Fase 2-4 |
✅ O que está bem — não mexer
| Área | Estado |
|---|---|
| RLS | 100% das 26 tabelas com RLS habilitado |
| Migrations | 67 migrations versionadas no Supabase, com hardening recente (fix_security_definer_search_path, protect_last_owner, rate_limit_infra, perf_composite_indexes) |
| Páginas legais | termos e privacidade excelentes — LGPD completa, Stripe, CDC, foro, DPO email, retenção, base legal por art. 7º |
| Sentry | Bem configurado: tunnel route /monitoring (escapa ad-blockers), sendDefaultPii: false, ignore extensions, tracesSampleRate 0.1 conservador, source maps deletados após upload |
| Admin guard | Middleware + assertSuperAdmin() em ações + audit log em DB. Defesa em camadas. |
| TypeScript strict | tsc --noEmit passou limpo |
| Build | Compila e produz bundle (com warnings, mas não falhou) |
global-error.tsx | Reporta pra Sentry corretamente |
| OFX upload | Validação client-side + dedupe via hash + workspace ownership check |
| Dev pages | /dev/ofx-test corretamente protegida com notFound() em prod |
| Stripe Edge Functions | create-checkout-session, create-portal-session, webhook live configurado |
Domínio pixdopix.com.br | Termos, privacidade, branding consistentes |
Checklist de pre-launch (em ordem)
Hoje (2-3h)
- Rotacionar
STRIPE_SECRET_KEYeSTRIPE_WEBHOOK_SECRETno Stripe Dashboard - Atualizar secrets no Supabase Edge Functions
- Deletar
SETUP_QUANDO_VOLTAR.mdou sanitizar (sem nenhum secret) - Reescrever git history pra remover o arquivo (BFG ou
git filter-repo) - Confirmar via Stripe dashboard que chave antiga foi rejeitada
- Proteger
/sentry-example-pagecom guard server-side - Fix bug useMemo em
app/cartoes/[id]/page.tsx
Amanhã (3-4h)
- Atualizar
.env.local.examplecom todas as 7+ vars - Configurar
releaseeenvironmentno Sentry - Adicionar
Sentry.captureExceptionemapp/error.tsx - Adicionar
import 'server-only'emlib/supabase-admin.ts - Validar redirect em
/auth/callbackcontra//attacker.com - Habilitar Leaked Password Protection no Supabase
- Remover PII (emails, valores, nomes) de console.log nas ofx-actions
Depois de amanhã (2-3h)
- Pin
nextem versão específica no package.json - Atualizar
.gitignore(*.log,.playwright-mcp/,*.pngde teste) - Remover
package-lock.jsondo.vercelignore - Fix 7 warnings
<img>(trocar pornext/imageou suprimir lint regra) - Wire emails transacionais (
lib/email.ts) nas ações: signup, payment confirmed, convite - Smoke test end-to-end em ambiente preview com Stripe test mode
Nos primeiros 7 dias pós-launch
- Migrar para schema
extensionsopg_net - CSRF/Origin validation em admin actions (confirmar
serverActions.allowedOrigins) - Centralizar
PLANSemlib/plans.ts - Setup vitest +
npm testno package.json - Drop dos 45 índices não-usados (após confirmar baseline de uso)
- Banner de cookies LGPD
- Criar
app/sitemap.tseapp/robots.ts
O que evoluiu desde a auditoria de 23/04
A auditoria antiga (auditoria-tecnica.mdx) listou A1/A2/A3 de alta severidade:
- A1 — Zero testes: parcialmente resolvido. Existem 2 arquivos em
lib/__tests__/(bank-brands,competencia-fatura) + outros emlib/ofx/__fixtures__/. Mas ainda sem runner config (vitest/jest) nemnpm test. - A2 — Acoplamento alto: ainda válido, sem mudança estrutural.
- A3 — Dashboard complexa: ainda válido,
app/page.tsxcontinua grande.
- ✅ Sentry integrado (commit
ae08edc,dd506fe) - ✅ Logout corrigido (commit
4b08d68) - ✅ Mobile scroll fix (commit
613669f) - ✅ Admin/integrações com OFX e Sentry card (commit
0c9655f) - ✅ Crypto module completo (
app/crypto/) - ✅ OFX import (cartões + extratos)
- ✅ Rate limit infra no DB (
rate_limit_inframigration) - ✅ Protect last owner do workspace (impede ficar sem dono)
- ✅ Perf composite indexes
- ✅ Cron jobs pra mensalidades reminder