Como montar um checklist de revisão de código focado em segurança
Todos nós já vimos o checklist genérico de revisão de código focado em segurança. Ele tem itens como “Verificar SQL Injection” e “Prevenir Cross-Site Scripting”. A gente coloca o link dele nos templates de PR, e aí, em uma semana corrida, passa direto por ele. Fica parecendo mais uma etapa burocrática do que algo que realmente ajuda a evitar problema.
O problema é que essas listas não têm nada a ver com o que a gente realmente está rodando. Elas não consideram nossos frameworks, os serviços legados, nem a lógica de negócio que acaba gerando vulnerabilidades mais difíceis de perceber no dia a dia. No fim, o processo de revisão pega só o básico, coisas que um linter já apontaria, e deixa passar bugs mais críticos que nascem da própria complexidade do sistema..
O que eu tenho visto é que os mesmos erros de segurança tendem a reaparecer. Um tipo específico de bypass de autorização é corrigido em um serviço e volta a aparecer em outro seis meses depois. A gente deixa passar vulnerabilidades ligadas às regras de negócio, como uma race condition em um endpoint que concede promoções, porque elas não estão em nenhuma lista genérica do OWASP Top 10. O resultado é um processo que cria uma falsa sensação de segurança.
A única forma de resolver isso é construir um checklist de revisão de código focado em segurança que reflita a realidade do seu codebase.
Quando o checklist genérico de revisão de código de segurança não é suficiente
Por que as listas padrão não pegam problemas reais
Checklists genéricos falham por alguns motivos previsíveis. Eles parecem desconectados do código que escrevemos no dia a dia. Um item como “Validar todas as entradas” está correto, mas não ajuda. O que isso significa para um serviço em Go que recebe protobufs via gRPC? Como isso se aplica a um front end em Next.js que conversa com uma API GraphQL? O conselho é tão genérico que perde o sentido durante uma revisão concreta.
Essas listas também não refletem o nosso modelo de ameaça específico. Um dashboard interno B2B tem riscos diferentes de uma API pública de processamento de pagamentos. Um checklist genérico trata todos os sistemas como se tivessem a mesma superfície de ataque, o que simplesmente não é verdade.
Durante momentos de pressão, essa falta de especificidade é o que faz com que elas sejam ignoradas. Quando um desenvolvedor tem cinco PRs para revisar antes do fim do dia, uma lista vaga com 50 itens é a primeira coisa a ser pulada. Não é falta de preocupação com segurança; é porque a ferramenta não está ajudando a fazer o trabalho.
Como construir um checklist de revisão de código de segurança que funcione para o seu time
Um bom checklist nasce da história e da arquitetura do seu próprio sistema. É menos um documento estático e mais um registro vivo das lições que o time aprendeu na prática.
Focando a revisão de segurança em riscos do mundo real
A melhor fonte de verdade sobre o que colocar no seu checklist é o seu próprio histórico de falhas. Vá até seu issue tracker e busque por termos como security, vulnerability, bug bounty, CVE, ou post-mortem. Procure padrões.
- Os desenvolvedores esquecem repetidamente de verificar permissões de tenant em um sistema multi-tenant?
- Vocês continuam encontrando lugares onde conteúdo gerado pelo usuário é renderizado sem escaping adequado?
Esses problemas recorrentes são os itens de maior prioridade no checklist. Eles representam os pontos cegos específicos do seu time. Um item como “Verificar se o ID da organização do usuário corresponde ao ID da organização do recurso em todas as rotas /:orgId/” é mil vezes mais útil do que “Verificar problemas de autorização.”
Depois, conecte os itens do checklist aos seus frameworks e bibliotecas reais. Se o seu time usa Django, o checklist deve mencionar riscos específicos como middleware mal configurado ou uso incorreto do decorator @permission_classes. Se você usa Express.js, pode ter uma verificação para ausência do middleware helmet(). Isso torna a orientação imediatamente relevante para quem está revisando.
Como deixar cada item do checklist de segurança claro e acionável
Para um checklist ser útil, cada item precisa passar em um teste simples: um desenvolvedor consegue determinar rapidamente se o código passa ou falha nesse ponto? Itens vagos falham nesse teste. Itens acionáveis apontam para padrões específicos de código.
Inclua exemplos de código bons e ruins diretamente no checklist. Isso remove ambiguidade e acelera o processo de revisão. Em vez de apenas dizer o que fazer, mostre.
Item ruim no checklist:
- Garantir queries SQL seguras.
Item bom no checklist:
- SQL Injection: Confirmar que todas as queries ao banco são construídas usando o query builder do ORM e não SQL raw com interpolação de variáveis.
- Ruim:
db.raw(f"SELECT * FROM users WHERE email = '{user_email}'") - Bom:
db.query(User).filter(User.email == user_email).first()
- Ruim:
Os itens devem ser curtos. Quem revisa precisa entender o ponto em poucos segundos. Se precisar de mais contexto, linke para um documento mais detalhado, mas mantenha o checklist direto e limpo.
Adicionando o checklist ao seu fluxo
Um checklist longo e genérico só cria ruído. Você precisa mostrar as verificações certas na hora certa. Dá para dividir o checklist em seções e aplicar de forma condicional.
- Um checklist principal (5–7 itens) para todo pull request. Deve cobrir os erros mais comuns e críticos do time.
- Checklists específicos por contexto para mudanças relacionadas a partes específicas do código. Por exemplo, um PR que altera um serviço de autenticação pode acionar um
auth-checklist.md. Uma mudança em um endpoint de pagamento pode acionar umpayments-checklist.md.
Isso pode ser automatizado com regras de code ownership ou filtros de caminho no seu sistema de CI/CD. Por exemplo, uma GitHub Action pode postar automaticamente o checklist relevante como comentário em um PR quando arquivos no diretório src/auth/ forem modificados.
Isso transforma o checklist de uma tarefa manual em uma parte integrada do fluxo de desenvolvimento, funcionando como um assistente útil em vez de um gatekeeper.
Evoluindo seu checklist de revisão de segurança
O checklist nunca deve ser considerado “pronto”. É um documento que evolui junto com o sistema e o time.
Ajustando com base no que você aprende
O processo de atualização do checklist é tão importante quanto o checklist em si.
- Quando acontece um incidente de segurança ou um bug crítico é corrigido, o post-mortem deve ter uma ação obrigatória: “O que podemos adicionar ao checklist de revisão de segurança para evitar que esse tipo de problema aconteça de novo?”
- Se um item do checklist sempre passa sem discussão e nunca encontra nada, pode ser só ruído. Considere remover ou tornar mais específico.
- Peça feedback ativamente ao time de engenharia. O checklist está atrapalhando? Algum item está confuso? Ele realmente ajuda a encontrar bugs? Use esse feedback para ajustar o documento.
Esse loop contínuo de feedback ajuda o checklist a se manter como uma ferramenta de alto sinal e baixo ruído, que realmente melhora a segurança do sistema.
Um checklist de revisão de segurança
Esse é um template para ser adaptado ao seu contexto, ajustado e preenchido com os detalhes específicos da sua arquitetura, frameworks e lógica de negócio. Use como ponto de partida.
Security Code Review Checklist
Download the security code review checklist as a Markdown file and add it to your repository to ensure deep understanding of your project security.
Checklist principal (para todos os pull requests)
- Logging: Essa mudança registra dados sensíveis (PII, credenciais, tokens brutos) em texto puro?
- [Customizar: Link para a política de classificação de dados da sua empresa.]
- Dependências: Alguma nova dependência de terceiros está sendo adicionada? Se sim, foi verificada quanto a vulnerabilidades conhecidas e compatibilidade de licença?
- [Customizar: Link para os resultados da sua ferramenta de análise de dependências, como Snyk ou Dependabot.]
- Tratamento de erros: As mensagens de erro mostradas para usuários vazam detalhes internos do sistema (como stack traces, códigos de erro de banco de dados, IPs internos)?
- Segredos hardcoded: Existe algum novo segredo (API key, senha, certificado) hardcoded no código?
- Ruim:
API_KEY = "sk_live_..." - Bom:
os.getenv("STRIPE_API_KEY") - [Customizar: Link para sua ferramenta de gerenciamento de segredos, como Vault ou AWS Secrets Manager.]
- Ruim:
Checklist de autenticação
- Tratamento de senha: Senhas estão sendo manipuladas em texto puro em algum momento? Estão sendo hasheadas com um algoritmo moderno e forte (como Argon2, bcrypt)?
- Validação de token: Para JWTs, verificar a assinatura, checar o claim de expiração (
exp) e confirmar o algoritmo para evitar ataques comalg: none. - Proteção contra força bruta: Existe rate limiting em endpoints de login, reset de senha ou MFA?
- Gerenciamento de sessão: Identificadores de sessão são gerados com um gerador de números aleatórios criptograficamente seguro? Sessões são invalidadas corretamente em logout e mudança de senha?
Checklist de autorização
- Verificação de ownership: Para qualquer endpoint que acessa um recurso por ID (como
/api/documents/{doc_id}), existe uma verificação garantindo que o usuário autenticado pode acessar esse recurso específico?- [Customizar: Fornecer um snippet de código com a função padrão de verificação de autorização.]
- Multi-tenancy: Em sistemas multi-tenant, toda query de banco e chamada de API está filtrada pelo
tenant_idouorganization_iddo usuário atual?- Ruim:
db.query(Invoice).filter(Invoice.id == invoice_id).first() - Bom:
db.query(Invoice).filter(Invoice.id == invoice_id, Invoice.tenant_id == current_user.tenant_id).first()
- Ruim:
- Verificação de roles: As verificações de acesso por role seguem uma lógica de deny-by-default? Existe validação para ações administrativas ou privilegiadas?
Validação de entrada e encoding de saída
- SQL Injection: Todas as queries ao banco estão parametrizadas ou construídas com um query builder seguro? Existe uso de formatação de string com SQL raw?
- Cross-Site Scripting (XSS): Todos os dados fornecidos pelo usuário estão sendo corretamente codificados ou escapados antes de serem renderizados na UI?
- [Customizar: Especificar a biblioteca/função correta de encoding para o seu template engine, por exemplo, “Usar o encoding padrão de JSX do React, não
dangerouslySetInnerHTML.”]
- [Customizar: Especificar a biblioteca/função correta de encoding para o seu template engine, por exemplo, “Usar o encoding padrão de JSX do React, não
- Server-Side Request Forgery (SSRF): Se o servidor faz requisições HTTP para uma URL fornecida pelo usuário, essa URL é validada contra uma allowlist de domínios ou ranges de IP?
- Upload de arquivos: Arquivos enviados são validados quanto a tipo, tamanho e nome? São armazenados fora do web root e servidos com um header
Content-Typeseguro?
Checklist de segurança de API
- Mass Assignment: Ao atualizar um model a partir do body de uma requisição, você está especificando explicitamente quais campos podem ser atualizados, ou está passando o body inteiro sem filtro?
- Ruim (ex: Rails):
user.update(params[:user]) - Bom:
user.update(params.require(:user).permit(:name, :email))
- Ruim (ex: Rails):
- Rate Limiting: Endpoints sensíveis ou com alto custo computacional estão protegidos por rate limiting para evitar abuso?
- Versionamento de API: Se essa mudança altera um endpoint existente, ela quebra compatibilidade com clientes atuais? Deve ser criada uma nova versão?