Security · 40 min

Audite Dependências em Busca de Vulnerabilidades

Pegue um projeto pequeno com dependências propositalmente desatualizadas, rode uma auditoria de vulnerabilidades, leia o relatório como um engenheiro de segurança, corrija os pacotes vulneráveis e confirme que a auditoria volta limpa — exatamente o passo "audit" do Ralph Loop do DARE.

Problema

Toda aplicação que você entrega é construída em cima de centenas de pedaços de código que você não escreveu: suas dependências, e as dependências *delas* (as "transitivas", que você nunca escolheu diretamente). Qualquer uma delas pode carregar uma vulnerabilidade conhecida e publicamente divulgada — um CVE (Common Vulnerabilities and Exposures) — que um atacante pode explorar assim que sua aplicação estiver acessível numa rede. Este não é um risco hipotético. Ataques de supply chain e dependências com vulnerabilidades conhecidas são uma das formas mais comuns de sistemas de produção reais serem comprometidos, precisamente *porque* são invisíveis no dia a dia da revisão de código — ninguém lê o diff de um `package-lock.json` linha por linha. A correção não é "confiar cegamente nas dependências" nem "revisar cada linha de cada biblioteca" (inviável). É a **auditoria automatizada de dependências**: ferramentas que cruzam cada pacote e versão do seu projeto contra um banco de dados de vulnerabilidades e te dizem, em segundos, exatamente quais são perigosos, o quanto, e o que fazer a respeito. Neste lab você vai trabalhar com um projeto de exemplo pequeno que tem algumas dependências fixadas propositalmente em versões antigas e vulneráveis. Você vai rodar uma ferramenta de auditoria, aprender a ler o relatório dela (severidade, identificador CVE, o caminho de dependência que trouxe o pacote vulnerável), corrigir cada achado atualizando ou substituindo a dependência, e rodar a auditoria de novo até ficar limpa. Você vai terminar conectando isso diretamente ao método DARE: audit é o quarto e último portão do **Ralph Loop** (build → test → lint → audit), e uma task nunca está DONE enquanto restar um CVE HIGH/CRITICAL aberto. > Este lab é stack-agnóstico. As instruções são dadas tanto para uma amostra > em Node.js (usando `npm audit`) quanto para uma amostra em Python (usando > `pip-audit`) — escolha a stack que você já tiver instalada. Trabalhe só > contra seu próprio projeto de exemplo local; nunca escaneie ou ataque um > projeto que não é seu.

Objetivos

Ao final deste lab você será capaz de:

Pré-requisitos

O que você vai construir

Um projeto de exemplo minúsculo (Node.js ou Python, à sua escolha) com
algumas dependências fixadas em versões antigas e conhecidamente
vulneráveis. Você vai auditá-lo, corrigi-lo e provar — com saída de
ferramenta, não achismo — que ele está limpo.

Por que "audit" é seu próprio passo do Ralph Loop

O Ralph Loop do DARE é build → test → lint → audit. Os três primeiros
passos te dizem que o seu próprio código funciona. O audit te diz se o
código sobre o qual você está em pé é seguro. São preocupações
ortogonais:

É por isso que a regra do DARE é direta: um CVE classificado como HIGH ou
CRITICAL na sua árvore de dependências significa que a task está FAILED, não
DONE, até ser corrigido.
Achados moderate/low às vezes podem ser aceitos
com uma justificativa escrita (ex.: "só alcançável num caminho de código
exclusivo de dev"), mas HIGH/CRITICAL nunca passa.

Anatomia de um relatório de vulnerabilidade

Toda ferramenta de auditoria reporta aproximadamente o mesmo formato de
informação por achado:

Campo Significado
Pacote O nome da dependência vulnerável
Versão instalada A versão atualmente no seu lockfile
Faixa vulnerável A faixa de versões afetada (ex.: < 4.17.21)
Versão corrigida A primeira versão onde a correção chegou
Severidade low / moderate / high / critical (derivada de uma pontuação CVSS)
Identificador Um CVE ID (ex.: CVE-2021-23337) ou advisory ID (ex.: GHSA-...)
Caminho A cadeia de dependências que trouxe esse pacote — é direto ou transitivo?

O caminho é o campo mais útil para decidir sua estratégia de correção.
Se o seu package.json lista o pacote vulnerável diretamente, muitas vezes
você mesmo pode simplesmente subir a versão. Se ele está três níveis dentro
da dependência de outra pessoa, talvez você precise atualizar o pacote pai
em vez disso, ou esperar o mantenedor lançar uma versão corrigida.

Como trabalhar neste lab

Escolha uma stack (Node.js ou Python) e siga essa trilha pelos cinco
passos — o projeto de exemplo, as dependências vulneráveis e os comandos
exatos variam um pouco por stack, mas o fluxo (auditar → ler → corrigir →
reauditar → conectar ao Ralph Loop) é idêntico nas duas.

Passos

  1. Monte um projeto de exemplo com dependências vulneráveis

    Crie um diretório local novo e fixe algumas dependências em versões
    antigas e conhecidamente vulneráveis de propósito. Escolha sua stack.

    Trilha Node.js

    mkdir audit-lab && cd audit-lab
    npm init -y
    npm install lodash@4.17.15 minimist@1.2.5 axios@0.21.0
    

    Essas três versões antigas específicas são uma escolha deliberada:

    • lodash@4.17.15 — prototype pollution (corrigido na 4.17.21).
    • minimist@1.2.5 — prototype pollution (corrigido na 1.2.6).
    • axios@0.21.0 — Server-Side Request Forgery via tratamento de
      redirect (corrigido em versões posteriores 0.21.x/1.x).

    Trilha Python

    mkdir audit-lab && cd audit-lab
    python -m venv .venv
    source .venv/bin/activate   # Windows: .venv\Scripts\activate
    pip install "requests==2.25.1" "urllib3==1.26.4" "Jinja2==2.11.2"
    pip freeze > requirements.txt
    

    Estas também são propositalmente antigas:

    • requests==2.25.1 e urllib3==1.26.4 — vários CVEs em torno do
      tratamento de proxy/credenciais e comportamento de retry naquela era.
    • Jinja2==2.11.2 — uma classe de vulnerabilidade de escape de
      sandbox, corrigida em versões posteriores 2.11.x/3.x.

    Checkpoint

    Confirme que os pacotes realmente instalaram nas versões antigas
    fixadas:

    # Node
    npm ls lodash minimist axios
    
    # Python
    pip show requests urllib3 Jinja2
    

    Você deve ver exatamente os números de versão antigos que especificou —
    se o npm ou o pip resolveram silenciosamente algo mais novo, refixe
    com a sintaxe de versão exata mostrada acima.

    Entregável deste passo: um diretório de projeto local com um
    lockfile (package-lock.json ou requirements.txt) que fixa essas
    versões antigas e vulneráveis, confirmado pela saída do comando de
    checkpoint.

  2. Rode a auditoria e leia o relatório bruto

    Agora rode a ferramenta de auditoria da sua stack e olhe a saída
    bruta, sem filtro antes de fazer qualquer outra coisa — resista à
    vontade de pular direto para --fix. Você precisa conseguir ler esse
    relatório manualmente.

    Trilha Node.js

    npm audit
    

    Você vai ver, para cada achado, algo como:

    # npm audit report
    
    lodash  <4.17.21
    Severity: high
    Prototype Pollution in lodash - https://github.com/advisories/GHSA-...
    fix available via `npm audit fix`
    node_modules/lodash
    
    axios  <=0.21.1
    Severity: moderate
    Server-Side Request Forgery in axios - https://github.com/advisories/GHSA-...
    fix available via `npm audit fix`
    node_modules/axios
    
    2 vulnerabilities (1 moderate, 1 high)
    

    Para uma versão legível por máquina (útil para CI ou scripting):

    npm audit --json > audit-report.json
    

    Trilha Python

    pip-audit -r requirements.txt
    

    A saída fica assim:

    Name     Version  ID                  Fix Versions
    -------- -------- ------------------- ------------
    requests 2.25.1   PYSEC-2021-136      2.25.1... (veja fix versions)
    urllib3  1.26.4   PYSEC-2021-108      1.26.5
    Jinja2   2.11.2   PYSEC-2021-66       2.11.3
    

    Para saída em JSON:

    pip-audit -r requirements.txt -f json > audit-report.json
    

    Leia cada coluna antes de tocar em qualquer coisa

    Para cada achado do seu relatório, anote (no papel ou num arquivo
    de rascunho) estas quatro coisas:

    1. Pacote + versão instalada — o que você realmente tem.
    2. Severidade — low / moderate / high / critical.
    3. Identificador — o CVE ou GHSA/PYSEC ID, para você poder buscar o
      advisory completo se precisasse de mais detalhe.
    4. É direto ou transitivo? Confira seu package.json/requirements.txt
      — se o pacote está listado lá pelo nome, é direto; se ele só aparece
      no lockfile trazido por outra coisa, é transitivo.

    Checkpoint

    Você deve conseguir responder, sem rodar a ferramenta de novo: "Qual
    achado é o mais severo, e é um pacote que eu instalei diretamente ou um
    que veio de carona?" Se você não conseguir responder isso a partir das
    suas notas, releia o relatório.

    Entregável deste passo: uma lista curta escrita de cada achado
    (pacote, severidade, ID, direto/transitivo) mais o arquivo de relatório
    JSON salvo.

  3. Corrija cada achado — atualize, auto-corrija ou substitua

    Existem três estratégias de correção, em ordem de preferência. Tente
    nesta ordem para cada achado.

    Estratégia 1 — corretor automatizado

    As ferramentas de auditoria trazem um corretor que sobe para a menor
    versão corrigida que satisfaz sua faixa semver existente, e reinstala.

    # Node
    npm audit fix
    
    # Python não tem equivalente direto; atualize manualmente (Estratégia 2)
    

    npm audit fix é seguro para bumps patch/minor dentro da sua faixa
    declarada. Ele não vai cruzar um bump de versão major sozinho (isso
    precisa de npm audit fix --force, que você deve tratar como uma
    atualização de verdade — leia o changelog antes, pode ser uma mudança
    que quebra compatibilidade).

    Estratégia 2 — bump manual de versão

    Quando o corretor não alcança longe o suficiente, ou você está em
    Python, suba a versão explicitamente para (ou além de) a versão
    corrigida do relatório.

    # Node — sobe uma dependência direta além da versão corrigida
    npm install lodash@^4.17.21 axios@^0.21.4
    
    # Python — edite requirements.txt, depois reinstale
    pip install --upgrade "requests>=2.31.0" "urllib3>=1.26.18" "Jinja2>=3.1.3"
    pip freeze > requirements.txt
    

    Sempre releia o código real do seu projeto depois de um bump de versão
    — especialmente através de uma versão major — para checar mudanças
    de API que quebram compatibilidade. É aqui que sua suíte de testes se
    justifica (veja o checkpoint abaixo).

    Estratégia 3 — substitua a dependência

    Às vezes não existe versão corrigida (o pacote foi abandonado) ou a
    versão corrigida exige um runtime que você ainda não pode adotar. Nesse
    caso:

    • Procure um fork ativamente mantido ou um pacote alternativo que cubra
      a mesma funcionalidade.
    • Se o caminho de código vulnerável não é realmente alcançável a partir
      da sua aplicação (ex.: uma ferramenta exclusiva de dev), você pode
      fazer vendor de um patch ou aceitar o risco com uma justificativa
      escrita
      — nunca silenciosamente.

    Corrija os achados do seu lab agora

    Aplique o corretor/atualização para cada pacote que você auditou no
    Passo 2. Para os pacotes específicos deste lab:

    # Node
    npm audit fix
    npm install lodash@^4.17.21   # se o audit fix não alcançou
    
    # Python
    pip install --upgrade "requests>=2.31.0" "urllib3>=1.26.18" "Jinja2>=3.1.3"
    pip freeze > requirements.txt
    

    Checkpoint

    Depois de cada correção, rode o que servir de "testes" no seu projeto
    de exemplo (mesmo um script de smoke trivial que importa/requer cada
    pacote e chama uma função) para confirmar que nada quebrou:

    # Node
    node -e "console.log(require('lodash').chunk([1,2,3,4],2))"
    
    # Python
    python -c "import requests, jinja2; print('ok')"
    

    Uma auditoria limpa que quebrou sua aplicação não está de fato pronta —
    ela só moveu o problema. É exatamente por isso que "audit" fica depois
    de "test" no Ralph Loop, não antes: você roda test de novo depois de
    cada correção, não só no final.

    Entregável deste passo: lockfile/requirements.txt atualizado com
    versões corrigidas, mais a confirmação de que o smoke check ainda passa.

  4. Rode a auditoria de novo e confirme um relatório limpo

    Rode exatamente o mesmo comando de auditoria do Passo 2 de novo. Este é
    o passo de verificação — você não está pronto porque acha que
    corrigiu, está pronto porque a ferramenta diz que sim.

    # Node
    npm audit
    
    # Python
    pip-audit -r requirements.txt
    

    Saída limpa esperada:

    # npm audit report
    found 0 vulnerabilities
    
    No known vulnerabilities found
    

    Se algum achado permanecer

    • Recheque a severidade. Se for HIGH/CRITICAL, volte ao Passo 3 —
      isso não é opcional sob a regra do Ralph Loop do DARE.
    • Se for moderate/low e ainda não existir correção, este é o único
      caso em que você pode prosseguir — mas só com uma nota escrita e
      datada
      explicando: qual pacote, qual CVE, por que não é alcançável
      ou por que o risco é aceito, e um lembrete para reverificar na próxima
      vez que mexer nas dependências. Nunca deixe uma nota de risco aceito
      sem escrever — um "resolvo depois" não documentado é como
      vulnerabilidades sobrevivem por anos.

    Configure o gate de CI de verdade

    Num projeto real, essa checagem deveria rodar automaticamente e falhar
    o build em HIGH/CRITICAL, para que ninguém consiga mesclar uma
    dependência recém-vulnerável por acidente:

    # Node — sai com código diferente de zero se achar HIGH ou acima
    npm audit --audit-level=high
    
    # Python — pip-audit sai com código diferente de zero em qualquer achado por padrão
    pip-audit -r requirements.txt
    

    Tente rodar npm audit --audit-level=high você mesmo agora e confira o
    código de saída:

    npm audit --audit-level=high; echo "exit code: $?"
    

    Um código de saída 0 significa limpo; qualquer outra coisa significa
    que o gate de auditoria bloquearia um pipeline de CI — exatamente o
    comportamento que você quer.

    Checkpoint

    Você deve ter agora:

    • Um relatório "antes" (do Passo 2) mostrando pelo menos um achado
      HIGH/moderate.
    • Um relatório "depois" mostrando zero achados HIGH/CRITICAL (ou uma
      nota documentada de risco aceito para o que restar).
    • Um comando (npm audit --audit-level=high ou pip-audit) que sai
      com 0 — o comando exato que você conectaria ao CI.

    Entregável deste passo: a saída limpa da auditoria, salva lado a
    lado com o relatório "antes" do Passo 2, mais o comando de gate de CI e
    seu código de saída.

  5. Conecte o audit ao Ralph Loop do DARE (submissão)

    Você praticou o fluxo completo manualmente. Agora formalize-o como o
    quarto passo do seu Ralph Loop, do jeito que uma task DARE real o
    definiria na sua Definition of Done.

    Escreva a sequência de comandos do Ralph Loop

    Para o seu projeto de exemplo, escreva os quatro comandos que a DoD de
    uma task exigiria, em ordem — é exatamente o que uma IA executando uma
    task DARE rodaria antes de marcar qualquer coisa como DONE:

    # 1. Build
    # (Node) — nenhum passo de compilação necessário para JS puro, ou: npm run build
    # (Python) — python -m py_compile *.py, ou o passo de build do seu framework
    
    # 2. Test
    # (Node) — npm test
    # (Python) — python -m pytest
    
    # 3. Lint
    # (Node) — npx eslint .
    # (Python) — ruff check .
    
    # 4. Audit  <-- o que você praticou neste lab
    npm audit --audit-level=high
    # ou
    pip-audit -r requirements.txt
    

    Escreva a regra da DoD em linguagem simples

    Adicione esta linha a um NOTES.md (ou o TASKS.md do seu projeto, se
    tiver um) — é a regra que você vai aplicar a partir de agora em toda
    task futura:

    ## Definition of Done — auditoria de dependências
    
    Uma task está DONE somente se:
    - `npm audit --audit-level=high` (ou `pip-audit`) sai com código 0, E
    - Qualquer achado moderate/low restante tem uma justificativa escrita
      e datada explicando por que não é alcançável ou por que o risco é
      aceito.
    
    Um achado HIGH ou CRITICAL em qualquer lugar da árvore de dependências
    significa que a task está FAILED, não importa quão completo esteja o
    código da feature.
    

    Por que isso vai no fim do loop, não no início

    O audit roda por último porque deveria funcionar como portão para
    tudo o resto — não faz sentido auditar dependências antes mesmo do
    seu código buildar. Mas ele ainda precisa rodar em toda task que
    mexe em dependências, não só uma vez na configuração do projeto: um
    novo PR que adiciona um único pacote novo é uma nova superfície de
    auditoria, e pular isso é como dependências vulneráveis entram em
    bases de código "confiáveis".

    Critério de submissão

    Submeta quando tudo o que segue for verdade:

    1. Um par de relatórios antes/depois de auditoria do seu projeto de
      exemplo (Passo 2 e Passo 4), mostrando pelo menos um achado
      HIGH ou moderate corrigido.
    2. O comando exato de gate de CI para sua stack
      (npm audit --audit-level=high ou pip-audit -r requirements.txt)
      com um código de saída 0 confirmado.
    3. Um NOTES.md curto com a sequência de quatro comandos do Ralph Loop
      e a regra da Definition of Done para auditorias de dependências,
      escrita com suas próprias palavras.
    4. Se algum achado permanecer sem resolução, uma justificativa escrita
      e datada para ele — nunca um achado silenciosamente ignorado.