carlos@cesaints: ~/projects/business-logic-review.md — zsh

Comando: cat projects/business-logic-review.md

projects/business-logic-review.md · 1,4 KB

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

Da hipótese ao teste de regressão, sem tocar a produção

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

  1. 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).
  2. 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?
  3. 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.
  4. 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

Classes de falha encontradas e corrigidas
CWEClasses de falha encontradas e corrigidasEfeito reproduzido
CWE-362Corrida em credencial de uso únicoO mesmo código de backup de 2FA era aceito em dois logins concorrentes.
CWE-613Fronteira do segundo fatorUma sessão aberta antes da ativação do 2FA ganhava acesso administrativo depois.
CWE-362Reuso de tokenUm token de redefinição de senha podia ser consumido duas vezes em paralelo.
CWE-367TOCTOU em saldoDois resgates concorrentes debitavam créditos duas vezes.
CWE-362Dupla contagemDuas provas simultâneas geravam dois créditos de ranking.
CWE-362Idempotência de webhookDuas entregas simultâneas do mesmo evento rodavam o processamento duas vezes.
CWE-269Privilégio SQL excessivoUm papel anônimo podia executar TRUNCATE numa tabela protegida por RLS, porque RLS não cobre TRUNCATE.
CWE-359Exposição de dados pessoaisA exportação de dados do titular incluía IP e sinais antifraude de terceiros.
CWE-200Vazamento no payloadA URL de um arquivo pago era serializada para o cliente.
CWE-636Falha aberta por configuração ausenteSem uma variável de configuração, um arquivo privado era tratado como externo.
CWE-841Revogação incompletaUm chargeback mantinha o acesso; um estorno não cancelava a assinatura remota.
CWE-840Oferta sem entregaO checkout vendia um item sem conteúdo publicado.
CWE-20Validação de entradaUm 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)