Comando: cat projects/business-logic-review.md
Revisão de segurança da lógica de negócio de um sistema meu
Revisei a lógica de negócio de um sistema meu, com pagamento, créditos, ranking e conteúdo pago, e corrigi treze classes de falha, cada uma com teste de regressão. O processo foi uma revisão adversarial sem atacar produção: cada hipótese reproduzida num teste que falha e corrigida na fronteira mais estreita. As falhas mais caras eram as que scanner e checklist genérico não pegam.
- Papel
- Autor do processo e responsável pelas decisões
- Período
- ago/2026 – set/2026
- O que eu fiz
Eu: Modelo de ameaça, definição do que é crítico, desenho do processo de revisão, decisões de trade-off e aprovação final.
Projeto privado, descrito sem identificar cliente, produto ou empresa.
Diagrama
Toda a revisão roda em ambiente local, com dados sintéticos; a produção fica fora do escopo. Uma revisão de segurança e uma de lógica de negócio levantam hipóteses, e um validador tenta derrubar cada uma: a derrubada sai, e a confirmada vira um teste que falha, com conexões reais ao banco. A correção entra na fronteira mais estreita, passando por um portão de revisão, e o teste fica como regressão, com uma guarda de CI.
Contexto
Revisei um sistema meu com checkout, créditos, ranking, prova cronometrada, 2FA e exportação de dados pessoais, entre agosto e setembro de 2026. As revisões rodaram sobre código local e Postgres com dados sintéticos. O sistema ainda não estava com venda aberta.
Isto é uma revisão de segurança de código próprio. Não é um pentest profissional nem afirma que o sistema está livre de falhas.
O que mais pesou
- Falsos positivos. O validador derrubou várias sugestões dos revisores automáticos que não tinham caminho de ataque real. Só achados confirmados seguiram para correção.
- Revisões externas parciais. Pareceres que estouraram tempo ou não trouxeram nada útil foram registrados como indisponíveis, e não como aprovação.
- Contagem honesta. Execuções de teste sobrepostas não foram somadas, e uma rodada vermelha por contenção de disco não foi apresentada como verde.
- O padrão que voltava. Concessão de direito sem o caminho de revogação no mesmo diff quebrou cinco vezes antes de virar item obrigatório de checagem.
O que eu aprendi
As falhas mais caras não estavam em nenhuma lista de vulnerabilidades conhecidas. Estavam na distância entre o que a regra de negócio promete e o que duas requisições ao mesmo tempo conseguem fazer. É esse raciocínio de construtor que levo para os testes de segurança: entender o sistema a ponto de saber onde ele cede.
Restrições
- Nunca atacar o serviço no ar; só código, ambiente local e dados sintéticos.
- Toda hipótese tem de ser falsificável e reproduzida num teste que falha antes da correção.
- Opinião não conta como prova: só um teste que reproduz a falha.
- Mock não prova isolamento de banco; corridas precisam de conexões reais.
- Proibido reduzir cobertura ou afrouxar asserção para o teste passar.
Relatório de segurança
Escopo e autorização
Sistema meu, revisado em ambiente local com dados sintéticos. Nenhum teste contra produção.
Modelo de ameaça
- Ativos: conteúdo pago, dinheiro e créditos, ranking, conta administrativa, dados pessoais.
- Atores: usuário curioso, usuário malicioso com várias contas, bot, concorrente fazendo scraping, insider.
- Superfícies: Server Actions tratadas como endpoints públicos, webhooks, crons, links assinados e formulários.
Método
- Investigação em dois papéis: segurança (autorização, 2FA, injeção, segredos, headers) e lógica de negócio (concorrência, valor, etapas puladas, máquina de estados).
- Refutação: um validador tenta derrubar cada achado. O código leva mesmo ao efeito? O atacante alcança o ponto com o privilégio que tem? O impacto está inflado?
- Correção na fronteira mais estreita ou na função compartilhada, com um portão de revisão obrigatório, que pode bloquear a mudança, sobre regras de dinheiro e acesso.
- Purple: a reprodução vira teste de regressão; a causa raiz vira guarda de CI quando o padrão pode renascer em outra tela.
Classes de falha encontradas e corrigidas
| CWE | Classes de falha encontradas e corrigidas | Efeito reproduzido |
|---|---|---|
| CWE-362 | Corrida em credencial de uso único | O mesmo código de backup de 2FA era aceito em dois logins concorrentes. |
| CWE-613 | Fronteira do segundo fator | Uma sessão aberta antes da ativação do 2FA ganhava acesso administrativo depois. |
| CWE-362 | Reuso de token | Um token de redefinição de senha podia ser consumido duas vezes em paralelo. |
| CWE-367 | TOCTOU em saldo | Dois resgates concorrentes debitavam créditos duas vezes. |
| CWE-362 | Dupla contagem | Duas provas simultâneas geravam dois créditos de ranking. |
| CWE-362 | Idempotência de webhook | Duas entregas simultâneas do mesmo evento rodavam o processamento duas vezes. |
| CWE-269 | Privilégio SQL excessivo | Um papel anônimo podia executar TRUNCATE numa tabela protegida por RLS, porque RLS não cobre TRUNCATE. |
| CWE-359 | Exposição de dados pessoais | A exportação de dados do titular incluía IP e sinais antifraude de terceiros. |
| CWE-200 | Vazamento no payload | A URL de um arquivo pago era serializada para o cliente. |
| CWE-636 | Falha aberta por configuração ausente | Sem uma variável de configuração, um arquivo privado era tratado como externo. |
| CWE-841 | Revogação incompleta | Um chargeback mantinha o acesso; um estorno não cancelava a assinatura remota. |
| CWE-840 | Oferta sem entrega | O checkout vendia um item sem conteúdo publicado. |
| CWE-20 | Validação de entrada | Um preço digitado no painel era multiplicado por 100. |
Correções
- Consumo condicional atômico (compare-and-set) para códigos de uso único e tokens.
- Prova de segundo fator na sessão e novo login após ativar o 2FA.
- Trava transacional por recurso para créditos de ranking; releitura dentro da transação para saldo.
- Claim atômico do evento de webhook.
- Migração de REVOKE nos papéis anônimos, além da RLS.
- Projeção por lista permitida na exportação de dados do titular.
- Link assinado por requisição, sem URL pública no payload.
- Falha fechada quando a configuração falta.
- Revogação e cancelamento remoto no mesmo fluxo de estorno e chargeback.
- Predicado único de oferta entregável e parser de preço sem ponto flutuante.
Validação
- Corridas reproduzidas com duas conexões reais ao Postgres e uma barreira antes do insert.
- Cada correção acompanhada de um teste que falhava antes dela.
- Privilégios de banco conferidos com testes que trocam de papel (SET ROLE).
- Guardas de CI que reprovam filtro de acesso escrito à mão fora do helper canônico.
Stack e por quê
- PostgreSQL
- Travas consultivas, RLS, grants e SKIP LOCKED como primitivas de concorrência e isolamento.
- Vitest com schemas Postgres isolados
- Testes de concorrência e de privilégio contra banco real.
- Playwright
- Verificação de CSP e fluxos HTTP.
- GitHub Actions
- Auditoria de dependências e guardas de regressão derivadas da revisão.
Resultados
Treze classes de falha reproduzidas e corrigidas, cada uma com teste de regressão.
repositório privado auditadoRelatórios de revisão do projeto(Documentação do projeto (não reexecutada nesta auditoria), set/2026)Cerca de 16 arquivos de teste criam schema isolado em Postgres real e cerca de 31 tratam concorrência.
repositório privado auditadoContagem aproximada por busca no repositório(Auditoria do repositório privado, set/2026)Job de auditoria de dependências e job de guardas de regressão no CI.
repositório privado auditadoEstrutura dos workflows(Auditoria do repositório privado, set/2026)
Evidências
- repositório privado auditadoRepositório privado auditado(Auditoria do repositório privado, set/2026)