Por que scripts de instalação eram um problema
O mecanismo de scripts de ciclo de vida do npm (preinstall, install, postinstall) foi criado para permitir que pacotes compilem código nativo, gerem arquivos de configuração ou realizem setup necessário após a instalação. Ferramentas como node-gyp (para módulos nativos), sharp (processamento de imagem) e puppeteer (que baixa o Chromium) dependem desse mecanismo para funcionar.
O problema é que esse mesmo mecanismo, durante anos, foi a principal porta de entrada para malwares em projetos JavaScript. Um pacote malicioso publicado no registry conseguia executar código na máquina de qualquer desenvolvedor que rodasse npm install, com as permissões do processo — e, em ambientes de CI/CD, frequentemente com acesso a tokens, segredos e credenciais de nuvem.
A JFrog relatou que esse vetor esteve envolvido em aproximadamente 53% dos ataques maliciosos ao npm observados no último ano. O caso do pacote jscrambler — que rodou um infostealer via preinstall em julho de 2026 — é um exemplo recente: qualquer projeto que rodou npm install naquela semana teve código malicioso executado automaticamente.
O que o npm 12 muda exatamente
A mudança principal é que allowScripts agora é false por padrão. Isso significa que os seguintes tipos de script deixam de executar automaticamente durante npm install:
- Scripts
preinstall,installepostinstallde dependências. - Builds nativos implícitos via
node-gyppara pacotes combinding.gyp, mesmo sem script declarado explicitamente. - Scripts
preparede dependências Git, de arquivo e de link.
Duas outras mudanças complementam o bloqueio de scripts:
--allow-gitagora énonepor padrão, fechando um caminho onde dependências Git podiam sobrescrever o executável Git via.npmrcmesmo com--ignore-scripts.--allow-remotetambém passa paranone, bloqueando dependências de tarballs HTTPS como fonte padrão.
Para gerenciar a lista de permissões, o npm 12 introduz dois novos comandos:
npm approve-scripts: lista e aprova dependências com scripts pendentes, gravando a allowlist nopackage.json.npm deny-scripts: registra uma negação explícita, que sobrevive aapprove --alle impede aprovação acidental futura.
| npm | allowScripts padrão | Scripts executam automaticamente? |
|---|---|---|
| npm 10 e anteriores | on (implícito) | Sim, todos |
| npm 11.16.0+ | on (com warnings) | Sim, mas exibe avisos do que seria bloqueado |
| npm 12 | off por padrão | Não, exceto pacotes explicitamente aprovados |
Compatibilidade com pnpm e Bun: pnpm já havia feito essa mudança na versão 10 (janeiro de 2025) e Bun também bloqueia scripts por padrão. O npm chegou depois, mas fecha uma lacuna importante dado que ainda é o package manager mais usado em projetos Node.js corporativos e em pipelines de CI/CD legados.
Como preparar seu projeto para a migração
O GitHub recomendou atualizar para npm 11.16.0 antes de migrar para o 12, justamente para observar os warnings sem ter builds quebradas. O fluxo prático de migração é:
- Atualize para npm 11.16.0+ e rode
npm installnormalmente. O comando vai listar os scripts que seriam bloqueados no npm 12, sem bloquear ainda. - Execute
npm approve-scripts --allow-scripts-pendingpara ver a lista completa de dependências com scripts de instalação e seus conteúdos. - Avalie cada pacote da lista. Para dependências conhecidas e auditadas (como
sharp,bcrypt,puppeteer), usenpm approve-scripts nome-do-pacote. Para o que você não reconhece ou não precisa, usenpm deny-scripts. - Commit o
package.jsonatualizado com o campoallowScriptsresultante. Esse arquivo agora documenta quais dependências têm permissão explícita para executar código durante a instalação. - Atualize para npm 12 e valide que o CI/CD passa normalmente.
Pacotes populares que quase certamente vão precisar de aprovação: esbuild, sharp, bcrypt, puppeteer, core-js e qualquer módulo nativo que usa node-gyp. Provavelmente representam a maior parte dos projetos que vão quebrar na primeira tentativa de upgrade sem preparação.
O que a mudança não resolve
A limitação mais importante do npm 12 precisa ser entendida claramente: ele bloqueia scripts de instalação, não código malicioso em geral.
O comprometimento da Injective Labs, que aconteceu na mesma semana em que o npm 12 foi lançado, ilustra isso. Nesse caso, o código malicioso estava embutido em uma função JavaScript normal do pacote — não em um script de ciclo de vida. Ele executou quando o código do pacote foi chamado em runtime, não durante a instalação. O npm 12 não teria impedido nada.
Os vetores que o npm 12 não cobre:
- Código malicioso dentro das funções exportadas do pacote (executa em runtime).
- Typosquatting (pacotes com nomes similares a populares).
- Comprometimento de tokens de publicação de mantenedores legítimos.
- Dependências transitivas aprovadas que são comprometidas em versão futura.
Próximos passos práticos
A mudança do npm 12 é necessária, mas insuficiente sozinha. Para uma postura de segurança mais completa na cadeia de dependências:
- Ative o npm 12 e gerencie a allowlist como descrito acima. Prefira aprovações pinadas por versão (
pkg@1.2.3) às aprovações por nome, que valem para versões futuras sem nova revisão. - Use lock files e verifique-os no CI. O
package-lock.jsondeve ser commitado e o CI deve falhar se ele estiver desatualizado. - Implemente uma política de delay de versão. Versões recém-publicadas têm maior risco de comprometimento. Uma política que recusa qualquer versão com menos de 24-48 horas de publicação bloqueia os ataques mais rápidos com baixo custo operacional.
- Considere ferramentas de análise de supply chain como Socket.dev, que faz análise estática de pacotes npm em tempo real e detecta comportamentos suspeitos antes da instalação.
- Revise os segredos expostos em ambientes de CI/CD. Mesmo que um script malicioso rode, o impacto é proporcional ao que estava acessível. Minimize as permissões dos tokens de CI e rotacione credenciais regularmente.
Cuidado com "approve --all": pesquisadores de segurança alertam para a fadiga de aprovação. Se builds quebradas repetidamente levarem desenvolvedores a rodar npm approve-scripts --all sem revisar a lista, a proteção vira ritual sem substância. A allowlist tem valor apenas se os pacotes foram efetivamente avaliados antes de entrar nela.