Segurança no CI: scan de imagens Docker e dependências

Cada imagem Docker que você faz deploy pode conter vulnerabilidades conhecidas. Integrar scanners de segurança no pipeline de CI detecta CVEs críticos antes que cheguem à produção, sem custo adicional, usando ferramentas open-source que rodam diretamente no GitHub Actions.

Trivy: scanner de imagens Docker no CI

Detectar CVEs em imagens Docker com Trivy no GitHub Actions:

yaml
# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # scan semanal às 6h toda segunda

jobs:
  scan-docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build imagem para scan
        run: docker build -t scan-target:latest .

      - name: Scan com Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: scan-target:latest
          format: 'sarif'        # formato para GitHub Security tab
          output: trivy-results.sarif
          severity: 'CRITICAL,HIGH'
          exit-code: '1'         # falhar CI se encontrar CRITICAL ou HIGH

      - name: Upload resultados para GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

      # Relatório no PR:
      - name: Scan com saída em tabela (para comentário)
        if: github.event_name == 'pull_request'
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: scan-target:latest
          format: 'table'
          severity: 'CRITICAL,HIGH,MEDIUM'
          exit-code: '0'  # não falhar neste step (já falhou acima)

Scan de dependências npm com npm audit

Detectar vulnerabilidades em dependências antes do deploy:

yaml
# .github/workflows/security.yml — adicionar job:
  scan-npm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'

      - run: npm ci

      # npm audit nativo — falhar para vulnerabilidades críticas:
      - name: npm audit
        run: npm audit --audit-level=high
        # --audit-level: low, moderate, high, critical

      # Relatório detalhado:
      - name: npm audit report
        if: failure()
        run: npm audit --json | jq '.vulnerabilities | keys[] as $k | .[$k] | select(.severity == "high" or .severity == "critical") | {name: $k, severity: .severity, via: .via[0]}'

      # Para ignorar vulnerabilidades conhecidas sem correção:
      # npm audit --audit-level=high --production
      # (ignora devDependencies)

      # Alternativa mais rica: audit-ci
      # npm install -g audit-ci
      - name: audit-ci (com allowlist)
        run: |
          npx audit-ci --high \
            --allowlist GHSA-xxxx-yyyy-zzzz  # ignorar CVE específico com justificativa

Dependabot: atualizações automáticas de segurança

Configurar Dependabot para criar PRs de atualização automáticos:

yaml
# .github/dependabot.yml — na raiz do repositório:
version: 2
updates:
  # Dependências npm:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "06:00"
      timezone: "America/Sao_Paulo"
    # Criar PRs separados para cada dependência (não agrupar):
    open-pull-requests-limit: 10
    # Reviewer automático:
    reviewers:
      - "meu-usuario"
    # Labels para fácil filtragem:
    labels:
      - "dependabot"
      - "security"
    # Agrupar atualizações de patch (menos PRs):
    groups:
      patch-updates:
        update-types:
          - "patch"

  # Dependências do GitHub Actions:
  - package-ecosystem: "github-actions"
    directory: "/.github/workflows"
    schedule:
      interval: "weekly"
    labels:
      - "ci"

  # Dockerfile base images:
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"

Scan de secrets no código com Gitleaks

Detectar senhas e tokens acidentalmente commitados:

yaml
# .github/workflows/security.yml — scan de secrets:
  scan-secrets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # histórico completo para escanear todos os commits

      - name: Scan de secrets com Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}  # gratuito para repositórios públicos

# .gitleaks.toml — configuração de regras customizadas:
title = "Gitleaks Config"

[extend]
useDefault = true

[[rules]]
description = "Variável customizada de produção"
id = "prod-db-url"
regex = '''DATABASE_URL=postgresql://[^s]+'''
tags = ["secret", "database"]

[[allowlist]]
description = "Ignorar arquivos de teste"
paths = ['''test/.*''', '''.test.ts
#x27;''] [[allowlist]] description = "Ignorar exemplos de código" regexes = ['''exemplo|placeholder|your-''']

Policy as code com OPA/Conftest

Validar que o Dockerfile segue políticas de segurança:

rego
# policy/docker.rego — políticas de segurança para Dockerfiles:
package main

# Proibir rodar como root:
deny[msg] {
    input[i].Cmd == "cmd"
    # Sem USER definido = root por padrão:
    not any([c | c := input[_]; c.Cmd == "user"])
    msg := "Dockerfile não define USER não-root"
}

# Exigir HEALTHCHECK:
deny[msg] {
    not any([c | c := input[_]; c.Cmd == "healthcheck"])
    msg := "Dockerfile não tem HEALTHCHECK definido"
}

# Exigir versão pinada na imagem base (não :latest):
deny[msg] {
    input[i].Cmd == "from"
    image := input[i].Value[0]
    endswith(image, ":latest")
    msg := sprintf("Imagem base usa :latest: %s", [image])
}

# .github/workflows/security.yml — validar com Conftest:
  check-dockerfile:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Instalar Conftest
        run: |
          curl -L https://github.com/open-policy-agent/conftest/releases/download/v0.46.0/conftest_0.46.0_Linux_x86_64.tar.gz | tar xz
          mv conftest /usr/local/bin/
      - name: Validar Dockerfile
        run: conftest test Dockerfile --policy policy/

Não quer configurar manualmente?

Implante o VPS para Docker em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Trivy: open-source, zero configuração, integração nativa com GitHub Actions, escaneia imagens Docker + código + packages npm. Snyk: gratuito para projetos open-source, relatórios mais detalhados, auto-fix PRs, mas freemium para privados. Grype: alternativa open-source ao Trivy, banco de dados de CVEs diferente. Recomendação: comece com Trivy, gratuito, rápido, cobre a maioria dos casos sem conta.

Verificar se há patch: trivy image node:22-alpine lista os CVEs. Se existe versão mais nova sem o CVE: atualizar a imagem base no Dockerfile. Se não há patch ainda: (1) verificar se o vetor de ataque afeta seu caso de uso, (2) documentar a aceitação do risco com data de revisão, (3) adicionar ao .trivyignore com comentário explicativo. Nunca ignore CVEs críticos sem análise.

Vulnerabilidades em devDependencies (jest, eslint) geralmente não chegam à produção, use --production para ignorá-las. Para vulnerabilidades em prodDependencies sem correção disponível: verifique se o vetor de ataque é relevante (ex: vulnerabilidade de "prototype pollution" em lib que você nunca usa para input de usuário). Use audit-ci com allowlist para documentar os CVEs aceitos com justificativa.

Use grupos para agrupar atualizações de patch em um único PR (muito menos ruído). Configure open-pull-requests-limit: 5 para limitar PRs simultâneos. Agende para segunda-feira cedo, o time revisa no início da semana e fecha antes do fim. Automerge para patches de devDependencies com teste verde: GitHub → Repo → Settings → Branch rules → Allow auto-merge + adicionar workflow que faz merge automático de PRs do Dependabot com label "patch".

Para CVEs críticos: sim, bloquear é a decisão certa, código com vulnerabilidade crítica não deve chegar à produção. Para high: bloquear é razoável com escape hatch (allowlist para falsos positivos documentados). Para medium/low: reportar como anotações no PR sem bloquear. Configure exit-code: "1" apenas para CRITICAL,HIGH no Trivy, e configure o check como obrigatório no branch protection.