O comando npm install parece uma operação de download. Na prática, ele resolve versões, monta uma árvore de dependências e pode executar scripts definidos por pacotes que você nunca escolheu diretamente. É uma diferença importante: instalar não significa apenas colocar arquivos em node_modules. Em determinadas configurações, significa permitir que código de terceiros rode na máquina.
Isso não transforma todo postinstall em malware. Pacotes usam scripts para compilar módulos nativos, baixar binários ou preparar artefatos. O problema é a confiança implícita, especialmente quando uma dependência transitiva consegue herdar o mesmo ambiente que contém tokens, chaves SSH, credenciais de registry e acesso ao filesystem.
A árvore é maior do que o package.json
Um projeto com vinte dependências diretas pode instalar centenas ou milhares de pacotes transitivos. O arquivo de lock registra versões e integridade dos artefatos, o que é essencial para repetibilidade, mas não afirma que aquele código é seguro. Um pacote malicioso pode ter hash perfeitamente válido; o lock apenas garante que você recebeu exatamente o conteúdo registrado.
npm audit também resolve outro problema. Ele compara a árvore com vulnerabilidades conhecidas. Isso ajuda a encontrar versões afetadas, mas não é um detector geral de malware, abandono, troca suspeita de mantenedor ou script inesperado recém-publicado.
Comece sem executar scripts
Quando você está avaliando uma dependência nova ou revisando uma atualização relevante, instalar primeiro com scripts desativados reduz a superfície imediata. A opção existe na CLI e pode ser adotada como política do projeto.
npm install --ignore-scripts
npm audit
npm ls --depth=0
Depois, verifique se algum pacote realmente depende de uma etapa de instalação. A documentação atual do npm também oferece controles explícitos para permitir ou negar scripts por dependência. A ideia é trocar “todo mundo pode executar” por uma lista consciente do que precisa dessa permissão.
Atrasar novidade pode ser uma defesa
A opção min-release-age impede que a resolução escolha versões publicadas há menos de determinado número de dias. Não é uma cura, mas cria uma janela para que versões comprometidas sejam denunciadas ou removidas antes de chegarem automaticamente ao seu CI. Correções urgentes podem precisar de exceção, e a própria CLI avisa quando a janela bloqueia uma versão que corrigiria uma vulnerabilidade.
# .npmrc
min-release-age=2
ignore-scripts=true
Dois dias não são um número mágico. Um time pode escolher uma janela maior para aplicações estáveis e abrir exceções específicas quando necessário. O valor está em impedir que “publicado agora” vire “executado em produção agora” sem nenhuma pausa humana.
Revise a mudança, não só o nome
- Leia o diff do lockfile e confirme por que novas dependências entraram.
- Inspecione scripts de lifecycle, binários e arquivos publicados no pacote.
- Observe mudanças de mantenedor, repositório e padrão de releases.
- Prefira versões exatas e use
npm cino CI para respeitar o lockfile. - Execute builds de pull requests sem segredos de produção e com permissões mínimas.
Proveniência e publicação confiável ajudam a verificar de onde um pacote veio, mas não substituem a análise do que ele faz. Uma release legitimamente publicada por uma conta comprometida ainda pode ser perigosa. Segurança de supply chain funciona em camadas: identidade, revisão, atraso, isolamento e capacidade de responder.
O CI não deve carregar as chaves da cidade
Mesmo com todas as verificações, trate a instalação como execução de código não totalmente confiável. Separe o job que instala e testa de etapas com credenciais de deploy, restrinja tokens por escopo e duração e evite expor segredos a contribuições externas. Se um pacote escapar da triagem, o estrago possível deve continuar limitado.
O objetivo não é auditar manualmente cada linha de cada dependência. Isso seria impraticável. É retirar a confiança automática dos pontos de maior risco e tornar exceções visíveis. Um npm install seguro continua sendo conveniente, só deixa de ser uma caixa-preta com passe livre.
