Blog

Ataque via dependência: como reduzir sua superfície.

Faça uma conta desconfortável. Rode du -sh node_modules e compare com o tamanho do seu src.

A proporção costuma ser absurda. O código que você escreveu, revisou e testou é uma fração minúscula do que roda em produção. O resto veio de estranhos, atualiza sozinho e tem acesso a tudo que o seu processo tem.

Não é paranoia teórica: os casos conhecidos de pacote popular comprometido, mantenedor que transfere a conta pra alguém mal-intencionado e biblioteca sabotada pelo próprio autor já são muitos.

As três formas de ataque

Pacote comprometido. O mantenedor perde a conta, ou entrega ela pra alguém, e uma versão nova sai com código malicioso. Como o pacote é legítimo e amplamente usado, ele entra em milhares de projetos numa tarde.

Nome parecido. lodahs em vez de lodash, crossenv em vez de cross-env. Instala-se por erro de digitação ou por copiar de um tutorial ruim.

Confusão de dependência. Você tem um pacote interno chamado @empresa/utils. Alguém publica um pacote público com o mesmo nome e versão mais alta. Dependendo da configuração do seu gerenciador, o público ganha.

Esse terceiro é o mais elegante e o mais esquecido. A defesa é configurar escopo explícito por registro:

# .npmrc
@empresa:registry=https://registro-interno.exemplo.com/
//registro-interno.exemplo.com/:_authToken=${NPM_TOKEN}

O que realmente reduz risco

Em ordem de retorno por esforço.

Lockfile commitado e instalação estrita. Sem isso, nada mais importa, porque a versão instalada em produção pode não ser a que você testou.

pnpm install --frozen-lockfile

No CI, sempre. pnpm install puro em pipeline é aceitar que uma versão nova entre sem revisão.

Desligar script de instalação. Boa parte dos ataques executa no postinstall, no momento da instalação, sem ninguém rodar nada.

# .npmrc
ignore-scripts=true

Isso quebra alguns pacotes que compilam binário. A saída é liberar por exceção, explicitamente. Chato, e é a medida mais eficaz da lista.

Janela de quarentena. Não instale versão publicada há menos de alguns dias. Boa parte dos comprometimentos é detectada e retirada em horas.

{ "minimumReleaseAge": 4320 }

Três dias de atraso custam nada e evitam a categoria inteira de “peguei a versão maliciosa nas primeiras horas”.

Atualização automática com revisão. Renovate ou Dependabot agrupando atualizações e abrindo PR. O ponto não é atualizar sozinho: é que a atualização vire rotina pequena em vez de evento grande e assustador.

Time que atualiza a cada dois anos fica em versão vulnerável por medo da migração, o que é um risco maior que o de atualizar.

Reduzir o número de dependências

A defesa mais subestimada. Cada pacote é um mantenedor a mais em quem você confia.

Antes de adicionar, três perguntas: quantas linhas eu escreveria pra fazer isso? Quantas dependências transitivas ele traz? Está sendo mantido?

Um pacote de vinte linhas que traz sete dependências transitivas é um mau negócio. Escrever as vinte linhas é menos código pra manter e sete relações de confiança a menos.

pnpm why <pacote>       # quem trouxe isso pra cá
pnpm licenses list      # o que entrou junto

SBOM e o que fazer com ele

Uma lista de tudo que compõe o seu artefato, em formato padrão.

npx @cyclonedx/cyclonedx-npm --output-file sbom.json

Gerar é fácil. O valor está em ter isso disponível no dia em que sair uma vulnerabilidade crítica numa biblioteca e a pergunta for “a gente usa?”.

Sem SBOM, essa pergunta leva um dia e a resposta é incerta. Com SBOM versionado junto de cada release, leva um minuto e a resposta é exata, inclusive pra versões antigas ainda em produção.

Guarde o SBOM de cada versão publicada, não só da atual. É a diferença entre saber e achar.

O que auditar de verdade

npm audit gera ruído: aponta vulnerabilidade em dependência de desenvolvimento que nunca vai pra produção, e classifica como crítica coisa que não é explorável no seu contexto.

O filtro que eu uso: a vulnerabilidade está em código que roda em produção? Ela é alcançável a partir de entrada de usuário? Existe correção?

Se as três respostas forem sim, é prioridade. Se a primeira for não, entra na fila normal.

Tratar todo alerta como emergência treina o time a ignorar alertas, que é o pior desfecho possível.

O caso de quem publica

Se você mantém pacote, você é a superfície de ataque de outra pessoa.

Segundo fator obrigatório na conta do registro. Token de publicação com escopo mínimo e prazo curto. Publicação a partir do CI com proveniência, nunca da máquina de alguém. E, se houver mais de um mantenedor, revisão obrigatória antes de publicar.

O ponto que fica

Você não vai ler o código das suas dependências. Ninguém vai.

O que dá pra fazer é reduzir a quantidade delas, atrasar a adoção do que acabou de sair, impedir execução automática na instalação, e saber exatamente o que está lá dentro quando a notícia ruim chegar.

Isso é uma tarde de configuração e vale mais que qualquer ferramenta paga. Sobre o resto do básico, escrevi em como construir aplicações seguras.

← Voltar pro blog