No dia 31 de março de 2026, entre 00:21 e por volta das 03:15 UTC, duas versões do axios estiveram publicadas no npm com uma dependência a mais no package.json: plain-crypto-js@4.2.1. Essa dependência não era usada em runtime. Ela existia só para o npm resolver, instalar e disparar um script postinstall que baixava e executava um RAT (Remote Access Trojan, malware que dá ao atacante acesso remoto e persistente à máquina do alvo) multiplataforma no ato do npm install. As versões comprometidas foram axios@1.14.1 e axios@0.30.4, publicadas a partir da conta do mantenedor principal, que tinha sido tomada por um ator ligado à Coreia do Norte.

O que esse caso mostra não é que o npm é inseguro. É onde a confiança no npm realmente mora. Quando o time instala axios, a decisão que ele está tomando não é “confio nessa biblioteca”. É “confio em quem publica ela, em todos os maintainers que tocam ela, em todas as dependências transitivas dela e nos scripts de install que qualquer uma delas vier a trazer”. Este post é sobre o que dá para aprender desse incidente em particular e quais defesas operacionais fazem diferença num time pequeno, sem virar teatro de segurança.

O que aconteceu, em ordem

A cronologia importa porque ela explica por que tantos ambientes foram expostos sem ninguém perceber.

  1. Em 30 de março, o atacante publicou plain-crypto-js@4.2.0, uma versão limpa que imitava o nome do crypto-js legítimo. Ficou de molho.
  2. No fim do mesmo dia, publicou plain-crypto-js@4.2.1, agora com postinstall malicioso.
  3. Em 31 de março, às 00:21 UTC, publicou axios@1.14.1 a partir da conta comprometida do mantenedor. Cerca de 39 minutos depois, publicou axios@0.30.4.
  4. Os dois releases do axios traziam "plain-crypto-js": "^4.2.1" no package.json. Na prática, qualquer npm install que resolvesse uma dessas versões puxava o payload.
  5. Por volta das 03:15 UTC, o npm removeu as duas versões maliciosas do axios. A janela total ficou em cerca de três horas.
  6. A conta do mantenedor teve o e-mail trocado para um endereço controlado pelo atacante antes dos publishes, e o acesso veio de uma combinação de engenharia social com um RAT na máquina pessoal dele.

Três coisas merecem atenção nessa linha do tempo. A primeira é que o pacote malicioso transitivo foi plantado antes, pra parecer uma dependência comum. A segunda é que o vetor não foi typosquat: o pacote comprometido foi o axios oficial, pela conta oficial do mantenedor principal. A terceira é que três horas no npm são suficientes para atingir muita gente, porque pipelines de CI rodam o tempo todo e instalam dependências transitivas sem curadoria.

Por que ninguém viu a tempo

Existe uma leitura confortável do incidente que diz “é só revisar suas dependências”. Ela não sobrevive ao detalhe técnico.

A dependência maliciosa não aparecia no código do axios. Nenhum arquivo .js do axios fazia require('plain-crypto-js'). Ela estava só no package.json da versão nova, e o payload não rodava quando o axios era usado em runtime. Rodava quando o npm install resolvia a árvore e executava o postinstall do pacote transitivo. Quem lê o código do axios não encontra o RAT. Quem lê o package.json do axios encontra uma dependência a mais com nome plausível.

Some a isso três hábitos que quase todo time tem:

  • ^1.14.0 no package.json, que aceita qualquer minor/patch novo automaticamente.
  • Ambientes que rodam npm install em vez de npm ci, o que permite resolver uma versão mais nova mesmo com lockfile.
  • CI que roda scripts de install por padrão, sem --ignore-scripts.

Com essas três coisas juntas, o atacante não precisa que o alvo faça absolutamente nada fora do fluxo normal. Basta publicar a versão nova, e a próxima build captura.

Onde a confiança no npm realmente mora

Vale nomear isso, porque o nome muda o que o time decide monitorar.

Quando um projeto depende de axios, a superfície de confiança real envolve, no mínimo:

  • a conta npm de cada mantenedor com direito de publicar
  • o e-mail e o 2FA atrelados a essas contas
  • o pipeline interno do próprio projeto para publicar no npm
  • cada dependência direta declarada no package.json dele
  • cada dependência transitiva que entra recursivamente
  • todo script postinstall, preinstall e install que qualquer pacote na árvore rodar

Nenhum desses pontos aparece no import axios from 'axios'. Todos eles são assumidos como confiáveis por padrão. O caso de março de 2026 atacou dois deles ao mesmo tempo: a conta do mantenedor e uma dependência transitiva com postinstall.

O que times poderiam ter feito antes

Essa é a parte útil. Nenhuma das defesas abaixo é exótica, e nenhuma delas depende de ferramenta cara. Elas só precisam estar ligadas de verdade.

DefesaO que ela travaCusto prático
npm ci em vez de npm install em CIResolver versão fora do lockfileNenhum, é o comportamento correto para builds reproduzíveis
--ignore-scripts em CIExecutar postinstall, preinstall e install de dependênciasRequer auditar pacotes que realmente precisam de script (raros)
Lockfile commitado e revisado em PRTrocar dependência transitiva sem revisãoPrecisa de hábito de olhar o diff do lockfile
Versões pinadas (sem ^ e ~) nas dependências sensíveisUpgrade automático silenciosoMais PRs de bump, idealmente automatizados e revisados
Política de idade mínima do release (ex.: 7 dias)Consumir uma versão comprometida nas primeiras horasAlgumas ferramentas de SCA (software composition analysis) oferecem isso nativamente
npm overrides para forçar versão segura conhecidaSubir silenciosamente via dependência transitivaBaixo, entra no package.json do projeto
2FA forte e hardware key em contas npm de maintainersAccount takeover por roubo de credencialBaixo para o time, alto retorno para o ecossistema

A regra geral: quanto mais o pipeline depende de decisões automáticas do npm, mais superfície de ataque ele tem. Cada item da tabela acima reduz uma dessas decisões automáticas.

O que fazer agora, se você rodou alguma build naquele dia

Se o seu time tem qualquer chance de ter resolvido axios@1.14.1 ou axios@0.30.4 entre 31 de março 00:21 UTC e cerca de 03:15 UTC, a resposta operacional razoável é esta:

  1. Buscar nos lockfiles e histórico de CI por axios@1.14.1, axios@0.30.4 ou qualquer ocorrência de plain-crypto-js.
  2. Verificar, nas máquinas envolvidas, se existe ou existiu o diretório node_modules/plain-crypto-js. A presença dele é sinal de execução do install.
  3. Procurar conexões de saída para sfrclak.com ou 142.11.206.73 na porta 8000 em logs de rede das últimas semanas.
  4. Em máquinas onde houver qualquer evidência, assumir comprometimento e rotacionar tokens do npm, chaves SSH, credenciais de cloud, secrets de CI e qualquer segredo acessível pelo usuário afetado.
  5. Voltar para versões seguras: axios@1.14.0 na linha 1.x e axios@0.30.3 na linha 0.x.
  6. Opcionalmente, adicionar "overrides": { "axios": "1.14.0" } no package.json enquanto o rollback se consolida.

Esse passo a passo é o mínimo defensável. Em ambientes sensíveis, reformatar a máquina do desenvolvedor afetado é a recomendação padrão, porque o payload era um RAT com persistência.

A lição geral sobre confiar no npm

O caso do axios não é uma exceção bizarra. É um lembrete de que a confiança no npm é uma cadeia, não um selo. Mantenedor, conta, 2FA, máquina pessoal do mantenedor, dependências diretas, dependências transitivas e scripts de install: cada elo pode falhar sozinho e derrubar os outros.

O remédio também não é heroico. É operacional e um pouco chato: npm ci, --ignore-scripts no CI, lockfile revisado, dependências pinadas onde importa, overrides para conter surpresas transitivas, e uma janela de carência antes de adotar release novo. Essas cinco ou seis decisões, somadas, teriam reduzido drasticamente o raio de explosão do incidente para a maior parte dos times.

Se você só tem tempo para uma ação hoje, essa é a ação: troque npm install por npm ci --ignore-scripts no seu CI e revise o diff do lockfile no próximo PR. As duas coisas juntas já cortam a maior parte da superfície que o caso do axios explorou.

Referências