Como conduzir uma revisão de segurança de código em pull requests

1. Fundamentos da Revisão de Segurança em PRs

1.1. Diferença entre revisão funcional e revisão de segurança

A revisão funcional verifica se o código atende aos requisitos de negócio, enquanto a revisão de segurança foca em identificar vulnerabilidades que possam comprometer a confidencialidade, integridade ou disponibilidade do sistema. Enquanto a primeira pergunta "o código funciona?", a segunda pergunta "o código pode ser explorado?".

1.2. O papel do desenvolvedor como primeira linha de defesa

Desenvolvedores são a primeira barreira contra vulnerabilidades. Cada PR representa uma oportunidade de prevenir falhas antes que cheguem à produção. A mentalidade de segurança deve ser incorporada desde a escrita do código, não apenas na revisão final.

1.3. Checklist mínimo de segurança para todo PR

Checklist de Segurança para PR:
□ Dados de entrada são validados e sanitizados?
□ Consultas ao banco usam parâmetros (não concatenação)?
□ Senhas e tokens não estão hardcoded?
□ Novas dependências são de fontes confiáveis?
□ Logs não expõem dados sensíveis?
□ Permissões foram verificadas para novos endpoints?
□ Variáveis de ambiente não têm valores sensíveis hardcoded?

2. Identificação de Vulnerabilidades Comuns em Pull Requests

2.1. Injeção de SQL, comandos e XSS em alterações de entrada/saída

Exemplo de SQL Injection (ERRADO):

// Código vulnerável
const query = `SELECT * FROM users WHERE username = '${req.body.username}'`;
db.execute(query);

Exemplo corrigido:

// Código seguro com parâmetros
const query = 'SELECT * FROM users WHERE username = ?';
db.execute(query, [req.body.username]);

Para XSS, verifique se a saída está escapada:

// Em templates (exemplo com React)
// ERRADO: <div>{userInput}</div>
// CORRETO: <div>{escapeHtml(userInput)}</div>

2.2. Exposição acidental de segredos, tokens e chaves de API

Procure por padrões suspeitos no diff:

// Padrões a serem verificados no PR
- "api_key", "apikey", "secret", "password", "token"
- Strings com aparência de base64, JWT ou hashes
- Comentários com credenciais de teste
- Arquivos .env commitados acidentalmente

2.3. Uso inseguro de bibliotecas e dependências adicionadas no PR

Verifique se novas dependências:
- São de mantenedores conhecidos
- Têm versão específica (não latest)
- Não possuem vulnerabilidades conhecidas (CVE)
- São necessárias ou podem ser substituídas por funcionalidades nativas

3. Análise de Fluxos de Dados e Controle de Acesso

3.1. Verificação de autenticação e autorização em novas rotas/endpoints

Exemplo de rota sem autenticação (ERRADO):

// app.js
app.get('/api/admin/users', getUsers); // Sem middleware de autenticação!

Exemplo corrigido:

// app.js
app.get('/api/admin/users', authenticate, authorize('admin'), getUsers);

3.2. Validação de permissões em alterações de lógica de negócio

Em PRs que alteram lógica de negócio, verifique:

// Verificar se o usuário só acessa seus próprios recursos
// ERRADO: const order = await Order.findById(orderId);
// CORRETO: const order = await Order.findOne({ _id: orderId, userId: req.user.id });

3.3. Rastreamento de dados sensíveis em logs e respostas da API

Exemplo de log expondo dados sensíveis:

// ERRADO
console.log('Usuário logado:', user);
// Pode expor: { _id: ..., email: 'user@email.com', passwordHash: '...', cpf: '...' }

// CORRETO
console.log('Usuário logado:', { userId: user._id, email: user.email });

4. Revisão de Configurações e Infraestrutura como Código

4.1. Análise de arquivos Dockerfile, docker-compose e Kubernetes manifests

Verifique no Dockerfile:

# ERRADO: expor portas desnecessárias
EXPOSE 22 3306 6379

# CORRETO: expor apenas a porta da aplicação
EXPOSE 3000

4.2. Verificação de permissões de rede e exposição de portas desnecessárias

No docker-compose:

# ERRADO: expondo banco de dados para o mundo
services:
  db:
    ports:
      - "0.0.0.0:3306:3306"

# CORRETO: apenas rede interna
  db:
    expose:
      - "3306"

4.3. Configuração de variáveis de ambiente e defaults inseguros

# ERRADO: default com valor sensível
DATABASE_URL=postgres://admin:admin123@localhost:5432/db

# CORRETO: sem default ou com placeholder
DATABASE_URL=${DATABASE_URL}

5. Ferramentas Automatizadas e Integração Contínua

5.1. Configuração de SAST (Static Application Security Testing) no pipeline

Exemplo de configuração para GitHub Actions:

# .github/workflows/security.yml
name: Security Scan
on: [pull_request]
jobs:
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Semgrep
        uses: semgrep/semgrep-action@v1

5.2. Uso de linters de segurança como Bandit, Semgrep ou ESLint plugins

Para Python com Bandit:

# Comando no pipeline
bandit -r src/ -f json -o bandit-report.json

Para JavaScript com ESLint security plugin:

# .eslintrc.json
{
  "plugins": ["security"],
  "extends": ["plugin:security/recommended"]
}

5.3. Integração de scanners de dependências (Dependabot, Snyk, Trivy)

Exemplo com Dependabot:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

6. Técnicas de Revisão Manual e Análise de Contexto

6.1. Leitura orientada a padrões de ataque (OWASP Top 10)

Ao revisar, mantenha em mente:
- A01: Broken Access Control
- A03: Injection
- A05: Security Misconfiguration
- A06: Vulnerable and Outdated Components

6.2. Revisão de mudanças em lógicas de criptografia e hash

// ERRADO: hash personalizado ou algoritmo fraco
function hashPassword(password) {
  return md5(password); // MD5 é inseguro!
}

// CORRETO: usar bcrypt
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12);

6.3. Identificação de backdoors intencionais ou acidentais

Procure por:

// Padrões suspeitos
- if (user.role === 'superadmin') // bypass não documentado
- // TODO: remover depois dos testes
- debug = true em produção
- Comentários em idioma diferente do projeto

7. Comunicação e Cultura de Segurança no Time

7.1. Como reportar vulnerabilidades de forma construtiva no PR

Exemplo de comentário construtivo:

"Identifiquei que esta consulta SQL está usando concatenação direta (linha 42).
Isso pode permitir SQL Injection. Sugiro usar parâmetros preparados como em:
db.query('SELECT * FROM users WHERE id = ?', [userId])"

7.2. Criação de templates de PR com checklist de segurança

# Template de PR
## Checklist de Segurança
- [ ] Validação de entrada implementada
- [ ] Saída escapada corretamente
- [ ] Sem credenciais hardcoded
- [ ] Dependências verificadas
- [ ] Permissões validadas
- [ ] Logs sem dados sensíveis

7.3. Estabelecimento de gatilhos para revisão de segurança obrigatória

Gatilhos para revisão de segurança obrigatória:
- Mudanças em autenticação/autorização
- Alterações em criptografia/hashing
- Adição de novas dependências
- Mudanças em configurações de rede
- Modificações em pipelines de CI/CD

Conclusão

Conduzir uma revisão de segurança em pull requests é uma habilidade essencial que combina conhecimento técnico, atenção a detalhes e comunicação eficaz. Ao adotar uma abordagem sistemática — desde checklists básicos até ferramentas automatizadas — você transforma cada PR em uma oportunidade para fortalecer a postura de segurança do seu projeto.

Lembre-se: a segurança não é um destino, mas um processo contínuo. Cada revisão é uma chance de aprender, ensinar e construir software mais robusto.

Referências