Score
54/100
54/100 · high risk
Azarias · auditoria técnica · amostra anonimizada
AZR-2026-09-LOGIX · confidencial · pre-seed
Leitura do repositório logixpay/app (commit b4f81c9) e do caminho de produção: Vercel → Worker → Supabase → Stripe/OpenAI. Coleta 02/09/2026. Não é pitch: é o estado da TI hoje. Nomes, repositório e valores são fictícios; o formato é o da auditoria.
Score
54/100
P0 / P1 / P2
3/5/8
Pronto p/ prod?
Não
Desperdício
~US$ 1.840/mês
O split e a fatura funcionam no demo. Em produção, duas falhas de dinheiro (secret no browser + webhook sem assinatura) e um banco que trava com 40 usuários simultâneos. Due diligence de VC para neste estado.
O App Router mistura server e client no mesmo “lib”. Prisma aponta para a porta de sessão do PgBouncer. O agente GPT-4o roda no GET da invoice, sem cache e com retry cego.
48h nas P0 (sobe para ~62). Semana 1 no ORM/pool (~72). Semana 2 em cache e rate limit (~78). 78 é o piso que usamos para “dá para mostrar o repo”.
Inventário — o que existe. Mapa — por onde passa um pagamento e uma fatura. Registro — os 16 achados. Depois as quatro profundidades e o plano com dono e prazo.
Levantamento do commit b4f81c9 + contas ligadas ao preview e ao “prod” (ainda no mesmo projeto Vercel).
| Peça | Onde | Papel | Risco |
|---|---|---|---|
| logixpay/app | GitHub · Vercel | Next.js 14 App Router — UI + API | Secrets no bundle |
| workers/split | Cloudflare | Roteia webhook Stripe (30s wall) | Retry + timeout |
| Prisma 5.16 | schema.prisma | 12 models · Invoice, Split, Tenant | N+1 · sem índice |
| Postgres 15 | Supabase · sa-east-1 | DB único preview+prod | Mesmo projeto |
| Stripe | live + test no mesmo .env | Checkout, invoice, Connect split | sk_live no client |
| OpenAI GPT-4o | SDK no request path | Conciliação e classificação de NF | Sem cache / teto |
| Nome | URL / conta | Observação |
|---|---|---|
| local | localhost:3000 | .env.local com sk_live copiada do Vercel |
| preview | logixpay-*.vercel.app | Source map ligado · mesmo Supabase do “prod” |
| prod (de facto) | app.logixpay.io | Mesmo projeto Vercel. Sem isolamento de tenant no RLS. |
| Conta | Plano / região | Acesso |
|---|---|---|
| GitHub org logixpay | Free · 1 repo privado | Founder + 2 contractors (admin) |
| Vercel | Hobby no cartão pessoal | 1 owner · sem SSO · env compartilhado |
| Supabase | Pro · sa-east-1 | service_role no Vercel e no laptop do contractor |
| Stripe | Standard · live + test | sk_live no .env.local de 3 máquinas |
| OpenAI | Pay-as-you-go · sem org limit | 1 key, sem project isolation |
| Cloudflare | Free · DNS + Worker | API token no GitHub secret (ok) |
| Pacote | Versão no lock | Nota |
|---|---|---|
| next | 14.2.5 | App Router · server/client misturados em lib/ |
| prisma | 5.16.1 | Client instanciado em 4 arquivos |
| stripe | 16.2.0 | Importado no client via stripe.ts |
| openai | 4.52.7 | Sem timeout no SDK |
| next-auth | 4.24.7 | Credentials · cookie sem __Host- |
app/
api/v1/invoices/route.ts N+1
api/webhooks/route.ts P0 webhook
(dashboard)/admin/ fora do matcher
src/lib/stripe.ts NEXT_PUBLIC_ secret
src/lib/ai.ts key no client
src/lib/db.ts pool sessão
src/agents/reconcile.ts embedding sem cache
src/agents/invoice-gpt.ts retry cego
prisma/schema.prisma sem index tenant+created_at
middleware.ts ignora /admin
next.config.mjs env inlined
workers/split/src/index.ts proxy Stripe, 30s
Dois caminhos quentes. Os achados estão nestes hops — não em “a arquitetura no geral”.
Qualquer POST no passo 4 libera comissão. O Worker só encaminha; não valida assinatura.
Carga de 40 sessões: p99 3.2s, pool cheio, Worker 502. É o caminho que a campanha vai martelar.
P0 bloqueia go-live. P1 quebra sob carga ou come margem. P2 é dívida com dono — não “um dia”.
3 · P0 5 · P1 8 · P2
| ID | Onde | Achado | Horizonte |
|---|---|---|---|
| P0-01 | lib/stripe.ts · ai.ts | sk_live e sk-proj no bundle | 0–48h |
| P0-02 | api/webhooks/route.ts | invoice.paid sem assinatura | 0–48h |
| P0-03 | middleware.ts | /admin público · CORS * | 0–48h |
| P1-01 | lib/db.ts · Supabase | Pool sessão + leak serverless | Semana 1 |
| P1-02 | api/v1/invoices | N+1 · p99 3.2s / 40 users | Semana 1 |
| P1-03 | agents/reconcile.ts | ~40k embeddings/semana sem cache | Semana 2 |
| P1-04 | invoice-gpt.ts · Worker | Retry ×5 sem timeout | Semana 2 |
| P1-05 | Supabase RLS | RLS off · tenant só no app | Semana 1 |
| P2-01–08 | logs, maps, cookie, CI… | Source map prod, .env no git, sem teto por tenant | Backlog com dono |
Detalhe dos P0 em . P1 de banco em . IA em .
src/lib/stripe.ts:6 · src/lib/ai.ts:4 · next.config.mjs:11
O modelo prefixou NEXT_PUBLIC_ nas secrets. O bundle (e o source map de preview) leva sk_live_ e sk-proj-. Git history ainda tem a versão antiga.
Chargeback, gasto OpenAI, fatura falsa. Rotacionar as duas keys no mesmo dia.
// hoje
new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY!)
// correção — só server, sem PUBLIC
new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: "2024-06-20" })
app/api/webhooks/route.ts:18–41
Lê JSON e faz invoice.update(paid). Comentário no arquivo: “verify later — shipped for demo”. O Worker da CF só faz proxy.
// hoje
const body = await req.json()
if (body.type === "invoice.paid") { /* update */ }
// correção
const event = stripe.webhooks.constructEvent(raw, sig, process.env.STRIPE_WEBHOOK_SECRET)
// persistir só depois; idempotência em event.id
middleware.ts · rotas de mutação
middleware.ts matcher não inclui /admin. Rotas de mutação respondem Access-Control-Allow-Origin: *.
Fechar na mesma janela das keys: matcher inclui /admin e CORS fechado em mutação.
Um cluster Supabase (sa-east-1), 12 models, sem RLS. Preview e prod escrevem no mesmo banco.
lib/db.ts · DATABASE_URL :5432
PgBouncer em session segura a conexão até o client cair. A function da Vercel nem sempre chama $disconnect no erro. Pico: “remaining connection slots” e 502.
URL :6543 (transaction) + ?pgbouncer=true, singleton por isolate, connection_limit baixo, timeout 8s.
route.ts:27–52 · falta index (tenant_id, created_at) e (invoice_id)
const invoices = await prisma.invoice.findMany({ where: { tenantId } })
for (const inv of invoices) {
inv.items = await prisma.invoiceItem.findMany({ where: { invoiceId: inv.id } })
}
include: { items: true }, take: 50, orderBy: { createdAt: "desc" }
80 faturas = 81 round-trips. Meta semana 1: p99 < 400ms com 80 concurrent no staging.
Supabase RLS
O app filtra tenantId. O banco não. Service role no server. Uma query esquecida no admin lista invoice de todos os clientes.
Ligar RLS por tenant_id na semana 1, mesmo que o app já filtre.
Dois agentes no caminho do GET. Sem fila, sem cache, sem teto por tenant. Volume 25–31/08: base da conta de ~US$ 1.840/mês.
| Fonte | Volume | Estimativa |
|---|---|---|
| Embeddings repetidos | ~40.000/sem · 78% hit se cacheasse | ~US$ 1.1k/mês |
| GPT-4o retried / cobrado 2× | timeout Worker 30s vs request viva | ~US$ 740/mês |
src/agents/reconcile.ts
reconcile.ts chama text-embedding-3-small a cada listagem. Não há hash do texto.
SHA-256 normalizado → tabela embedding_cache; API só em miss.
src/agents/invoice-gpt.ts · workers/split
for (let i = 0; i < 5; i++) {
try { return await openai.chat.completions.create({ model: "gpt-4o", messages }) }
catch { /* imediato, sem AbortSignal */ }
}
Worker morre; a OpenAI às vezes cobra. Cliente retenta. Correção: AbortSignal.timeout(12000), no máximo 2 retries com backoff, job fora do GET, teto por tenant.
| Camada | Hoje | Furo |
|---|---|---|
| Edge / DNS | Cloudflare + Vercel | Sem WAF rule em /api/webhooks |
| App | Vercel Hobby → Pro no cartão do founder | Preview = prod (mesmo env) |
| Worker | CF Workers, 30s, sem fila | Retry OpenAI estoura wall |
| Dados | Supabase, backup 7d, sem PITR | Sem staging DB |
| Segredos | Vercel env + .env no git (P2) | Sem vault, sem rotação |
| Observabilidade | console.log + Vercel logs 1h | Sem trace no N+1 nem no GPT |
| CI | GitHub Actions: lint só | Não bloqueia NEXT_PUBLIC_ secret |
Quem faz, até quando, o que muda no score. Sem isso o relatório vira PDF.
| Quando | ID | Fazer | Dono |
|---|---|---|---|
| 0–48h | P0-01 | Rotacionar Stripe + OpenAI. Tirar PUBLIC. Varrer git. | Founder |
| 0–48h | P0-02 | constructEvent + deny default + event.id | Tech lead |
| 0–48h | P0-03 | Matcher /admin. CORS fechado em mutação. | Tech lead |
| Sem. 1 | P1-01/02/05 | Pool transaction, include Prisma, índices, RLS | Backend |
| Sem. 2 | P1-03/04 | Cache embedding, timeout, rate limit IP/tenant | IA + infra |
Este recorte é o formato de uma auditoria fechada (nomes anonimizados). O relatório de vocês vem com os arquivos e as linhas do repo de vocês.
Agendar diagnóstico →