O npm audit que ninguém lê, e a versão maior que a correção exige
O npm audit diz dezassete vulnerabilidades, três críticas. Ninguém lê, e há uma razão legítima para isso: o número está quase sempre a medir uma coisa diferente daquela que interessa.
1. Porque é que o número é inútil assim
Duas razões, e as duas são estruturais.
A primeira: o relatório cobre a árvore toda, incluindo as dependências de desenvolvimento. Uma falha num pacote que só corre na sua máquina, durante o build, não é a mesma coisa que uma falha no que está a servir pedidos. A separação faz-se num parâmetro:
npm audit --omit=devA segunda, e a mais importante: a gravidade é a da vulnerabilidade, não da sua exposição. «Crítica» é uma propriedade do pacote, atribuída por quem a reportou, sem saber nada sobre como é que o seu código o usa.
2. A pergunta que o npm audit não responde
É sempre a mesma: o código vulnerável é alcançável a partir do meu?
Uma negação de serviço por expressão regular num analisador que só recebe os seus próprios ficheiros de configuração não tem por onde ser atacada. A mesma falha, no analisador que recebe o que vem no corpo de um pedido, é um incidente. O relatório trata as duas por igual porque não tem como as distinguir.
Daí o hábito saudável: três linhas escritas sobre cada entrada que fica sem correção — o que é, por onde entraria, porque é que não se corrigiu. Não é burocracia; é o que impede reavaliar as mesmas dezassete entradas de zero dentro de seis meses.
3. O fix, e o fix --force
npm audit fix # dentro do semver declarado
npm audit fix --force # aceita versões maioresO primeiro é seguro por construção. O segundo aceita mudanças que quebram compatibilidade, e fá-lo em silêncio, em todas as dependências de uma vez. Correu bem num projeto pequeno; num projeto a sério deixa um diff impossível de revisar.
Nunca em integração contínua. Uma correção automática de segurança que ninguém olhou é uma alteração não revista a entrar em produção pela porta que se abriu para a segurança.
4. Quando a falha está três níveis abaixo
O caso mais comum: a vulnerabilidade está numa dependência de uma dependência, e o pacote intermédio ainda não a actualizou. O overrides no package.json força a versão:
{
"overrides": {
"pacote-vulneravel": "^2.1.4"
}
}Com a consequência dita em voz alta: passou a correr uma combinação que o autor do pacote intermédio nunca testou. É uma troca aceitável e temporária, e convém ter o ficheiro de bloqueio a dizer exactamente o que ficou instalado.
5. A dependência de que o npm audit nunca fala
E é a maior de todas: o runtime. Este projeto declarava Node 20 em cinco sítios. O Node 20 saiu de suporte a 30 de abril de 2026 — deixou de receber correções de segurança, incluindo as do próprio motor de JavaScript e das bibliotecas de TLS que vêm dentro dele.
Nenhum npm audit diz isto. Não é um pacote; é o chão.
A subida para o 24 foram: três linhas no Dockerfile, duas nos workflows, um campo engines que não existia, e os tipos do Node. Zero alterações em código de aplicação.
6. O que torna uma versão maior barata ou caríssima
Antes de decidir, a pergunta certa é: o que é que aqui está compilado contra esta versão? Módulos nativos são o que transforma uma subida de uma tarde numa semana — e quase sempre estão escondidos.
Neste caso fui ver dentro da imagem, e a resposta mudou a dimensão do problema: não há motor nativo nenhum. O projeto fala com o Postgres por um adaptador em JavaScript, portanto não havia binário por recompilar nem variante de libc por acertar. O único módulo nativo era o do processamento de imagens, já na variante correcta.
Saber isto antes é a diferença entre agendar uma tarde e adiar seis meses. E o que a confirma não é a documentação — é abrir a imagem e procurar os ficheiros.
O resumo
Começar com --omit=dev, porque é o que está em produção. Perguntar sempre se o caminho é alcançável, que é o que o relatório não sabe. overrides em vez de --force. E lembrar que a dependência mais desatualizada de um projeto é muitas vezes aquela que nenhuma ferramenta audita: a versão da plataforma onde ele corre.