- A conta npm do atool foi comprometida em 19 de maio de 2026, e 637 versões maliciosas foram publicadas automaticamente em 317 pacotes durante cerca de 22 minutos
- O payload era um script Bun ofuscado de 498 KB, usando a mesma estrutura de scanner e as mesmas expressões regulares do Mini Shai-Hulud usado na invasão da SAP
- Os alvos de roubo foram ampliados para incluir credenciais AWS, tokens do Kubernetes, Vault, GitHub PAT, tokens npm, chaves SSH e até segredos locais
- Em ambientes de CI, ele trocava OIDC do GitHub Actions por tokens de publicação do npm e abusava de assinaturas Sigstore e da injeção de workflows maliciosos
- A resposta exige verificar a instalação de versões comprometidas, trocar todas as credenciais que possam ter ficado acessíveis e adotar lockfile e pinagem de dependências, além de inspeção antes da instalação
Visão geral do ataque
- A conta npm do
atool(i@hust.cc) foi comprometida em 19 de maio de 2026, e 637 versões maliciosas foram distribuídas em 317 pacotes durante cerca de 22 minutos - Essa conta mantinha 547 pacotes, e os atacantes fizeram dois bumps de versão em mais de 314 deles
- Entre os pacotes afetados estão
size-sensor(4,2 milhões de downloads mensais),echarts-for-react(3,8 milhões),@antv/scale(2,2 milhões),timeago.js(1,15 milhão) e vários pacotes no escopo@antv - O payload é um script Bun ofuscado de 498 KB e usa a mesma estrutura de scanner, expressões regulares de credenciais e padrões de ofuscação do toolkit Mini Shai-Hulud, usado na invasão da SAP três semanas antes
- Os dados roubados eram enviados por commit como objetos Git em um repositório público no GitHub ou por HTTPS POST criptografado com RSA+AES para
t.m-kosche[.]com
Forma de distribuição e risco de semver
- A primeira onda publicou cerca de 317 versões entre 01:39 e 01:56 UTC de 19 de maio de 2026, e a segunda onda fez cerca de 314 bumps de versão nos mesmos pacotes entre 02:05 e 02:06 UTC
- A maioria dos pacotes, 309 no total, recebeu exatamente 2 versões maliciosas, uma em cada onda
- Quatro pacotes —
size-sensor,echarts-for-react,jest-canvas-mockejest-date-mock— receberam 3 versões, o que indica uso em testes iniciais - Na maioria dos pacotes, os atacantes não moveram a dist-tag
latest, mas a resolução semver do npm escolhe a maior versão compatível com o intervalo independentemente delatest - Por exemplo, mesmo que
latestdeecharts-for-reactpermaneça em3.0.6, um projeto com"echarts-for-react": "^3.0.6"pode resolver para a versão maliciosa3.2.7na próxima instalação limpa
Caminho de execução e payload
- Todas as versões adulteradas adicionaram um bump de versão e
"preinstall": "bun run index.js"aopackage.json - Das 637 versões maliciosas, 630 adicionaram
@antv/setup: github:antvis/G2#<commit-sha>emoptionalDependenciespara baixar uma segunda cópia do payload - O hook
preinstallé executado antes da instalação das dependências e exige o runtime Bun - Mesmo que
preinstallseja bloqueado ou ignorado, o scriptpreparedo commit que se passava por GitHub continua como um segundo caminho de execução index.jsé um bundle Bun ofuscado de 498 KB em uma única linha, com o mesmo requisito de Bun, ofuscação com variáveis hexadecimais, estrutura de scanner com limite de flush de 100 KB e conjunto de expressões regulares de credenciais do payload Mini Shai-Hulud usado na invasão da SAP- A detecção de ambiente de CI verifica variáveis de ambiente de mais de 20 plataformas, incluindo GitHub Actions, Jenkins, GitLab CI, CircleCI, Travis, Buildkite, Drone, TeamCity, AppVeyor, Bitbucket Pipelines, CodeBuild, Azure DevOps, Netlify e Vercel
Alvos de coleta de credenciais
- O payload lê mais de 80 variáveis de ambiente com nomes criptografados, e o conteúdo dos arquivos é varrido com expressões regulares
- Os principais alvos são token do GitHub, token npm, JWT do GitHub Actions, chave AWS, chave Azure, string de conexão de banco de dados, chave Stripe, chave SSH, autenticação do Docker, token do Vault, token do Kubernetes e credencial embutida em URL
- O scanner de arquivos lê locais padrão de credenciais no diretório home, como
.ssh,.aws/credentials,.npmrc,.docker/config.jsone.kube/config - Ele percorre toda a ordem de resolução de credenciais AWS, busca credenciais de role IAM em EC2 IMDSv2 e no endpoint de credenciais de contêiner ECS, e também tenta acessar AWS STS
GetCallerIdentitye o Secrets Manager - Para Vault, verifica arquivos de token e também
VAULT_ADDR,VAULT_TOKEN,VAULT_ROLEetc.; se houver credenciais válidas, tenta enumerar segredos e autenticação AWS e Kubernetes - Em Kubernetes, verifica tokens de service account e
KUBECONFIG; se houver socket do Docker, tenta enumerar contêineres do host e escapar do contêiner
C2 e exfiltração de dados
- A API do GitHub é usada como se fosse C2:
GET /uservalida os tokens GitHub roubados, eGET /user/orgsenumera organizações - Tokens com permissão
repooupublic_reposuficiente são usados para criar repositórios de exfiltração dos atacantes - A descrição do repositório é salva como a string invertida
niagA oG eW ereH :duluH-iahS, que na ordem correta vira “Shai-Hulud: Here We Go Again” - Os nomes dos repositórios combinam duas palavras com tema de Duna e um número, como
harkonnen-melange-742,fremen-sandworm-315egesserit-navigator-508 - Os dados exfiltrados são gravados via Git Data API na ordem blob, tree, commit e update de ref
- Um transmissor HTTPS separado é configurado para se parecer com um endpoint de ingestão de traces OTLP do OpenTelemetry:
hxxps://t.m-kosche[.]com/api/public/otel/v1/traces - O payload HTTPS criptografa JSON em gzip com AES-256-GCM e encapsula a chave AES com RSA-OAEP usando uma chave pública hardcoded
Abuso de CI/CD e da cadeia de confiança
- Em repositórios GitHub acessíveis com os tokens roubados, ele coleta histórico de execução de workflows, artifacts, nomes de secrets, lista de branches e configurações do Claude Code
- A API do GitHub não dá acesso aos valores de secrets, mas os nomes dos secrets revelam quais credenciais existem
- O workflow malicioso é injetado em
.github/workflows/codeql.yml, com o nomeRun Copilote acionado porpush - O workflow coloca todos os secrets do repositório em uma variável de ambiente JSON com
VARIABLE_STORE: ${{ toJSON(secrets) }}, salva isso emformat-results.txte depois envia como artifact - Após a conclusão, ele baixa o zip do artifact e reduz rastros da injeção apagando a execução do workflow e resetando a ref da branch
- Em ambientes de CI com GitHub Actions OIDC, ele tenta trocar por um token de publicação do npm no endpoint
https://registry.npmjs.org/-/npm/…; - O payload inclui implementação de assinatura Sigstore, incluindo os formatos de Fulcio, Rekor e proveniência SLSA, o que permite criar artifacts assinados com uma identidade de CI comprometida
Ambientes de desenvolvimento e infecção de agentes de codificação por IA
- O payload visa ambientes Claude Code, Codex e VS Code por meio de injeção remota via API do GitHub e infecção local por gravação no sistema de arquivos
- Ele faz commit de
.vscode/tasks.json,.claude/index.js,.claude/settings.json,.claude/setup.mjse.vscode/setup.mjsem repositórios acessíveis com os tokens roubados .claude/settings.jsonregistra um hookSessionStartpara executarnode .claude/setup.mjssempre que uma sessão do Claude Code é iniciada.vscode/tasks.jsonaciona a execução quando a pasta do projeto é aberta com"runOn": "folderOpen"setup.mjsbaixa o Bun v1.3.14 do release oficial no GitHub, extrai o binário e depois executa o payload- A rota de infecção local copia o payload para
~/.claude/package/index.jse~/.codex/package/index.js, e injeta hooks ao varrer arquivos de configuração existentes do projeto - Em Macs Apple Silicon, essa rota de infecção local é ignorada; em Linux, Macs Intel e Windows, ela é executada
Persistência e C2 dead-drop no GitHub
- Um daemon de execução persistente disfarçado de
kitty-monitoré instalado como serviço de usuário systemd no Linux e como~/Library/LaunchAgents/com.user.kitty-monitor.plistno macOS - O instalador usa
systemctl --user enable --noweloginctl enable-lingerpara permanecer ativo mesmo após logout - O daemon
cat.pyconsulta a GitHub Search API a cada 1 hora, buscando commits com a palavra-chavefiredalazer - O formato da mensagem de commit é
firedalazer <base64_url>.<base64_signature>, e ele verifica a assinatura RSA-PSS com uma chave pública RSA de 4096 bits embutida antes de executar o código Python da URL - Um daemon separado,
gh-token-monitor, consulta os tokens GitHub roubados em intervalos de 60 segundos, com TTL de 24 horas - O scanner de memória de processos despeja regiões legíveis da memória do processo worker do GitHub Actions runner usando
/proc/pid/maps+memno Linux eReadProcessMemoryno Windows
Commits falsos se passando por antvis/G2
- 630 das 637 versões maliciosas incluem uma entrada
optionalDependenciesque aponta para um commit específico no repositórioantvis/G2{ "optionalDependencies": { "@antv/setup": "github:antvis/G2#1916faa365f2788b6e193514872d51a242876569" } } - Quando o npm resolve uma dependência
github:, ele busca esse commit, procurapackage.jsone então executa os scripts de ciclo de vida - Esse commit contém um
package.jsonque declara@antv/setupe inclui um scriptprepare, além de umindex.jsde 499 KB com o mesmo payload Shai-Hulud novamente ofuscado - O
&& exit 1do scriptpreparefaz a optional dependency falhar, mas o npm não trata a falha de optional dependency como fatal, então a instalação continua - A Git API mostra três SHAs de commit diferentes enviados para
antvis/G2, todos sem estar ligados a qualquer branch - Os três commits compartilham os mesmos metadados: author
huiyu.zjt <Alexzjt@users.noreply.github.com>, commit messageNew Package, zero parents e sem assinatura GPG - O invasor pode deixar commits órfãos com o payload buscáveis por SHA no namespace do repositório original criando um orphan commit em um fork sem permissão de escrita em
antvis/G2e depois apagando o fork - Esse método é do mesmo tipo do problema de commits impostores no GitHub Actions documentado pela Chainguard, aplicado aqui à resolução de dependências
github:do npm
Indicadores de comprometimento
- Pacotes publicados por
atool(i@hust.cc) entre 2026-05-19 01:44 e 02:06 UTC devem ser verificados - O script
preinstallébun run index.js - O SHA256 do payload é
a68dd1e6a6e35ec3771e1f94fe796f55dfe65a2b94560516ff4ac189390dfa1c - Os commits falsos de
antvis/G2são os seguintes1916faa365f2788b6e193514872d51a242876569— 626 versões7cb42f57561c321ecb09b4552802ae0ac55b3a7a— 2 versõesdc3d62a2181beb9f326952a2d212900c94f2e13d— 1 versão, garbage collected
- Os IoCs de rede incluem
hxxps://t.m-kosche[.]com/api/public/otel/v1/traces, metadados EC2 em169.254.169.254e requisições de metadados de contêiner ECS em169.254.170.2 - Os IoCs de repositório incluem a branch
chore/add-codeql-static-analysis, o workflowRun Copilote.github/workflows/codeql.ymlque despejatoJSON(secrets)emformat-results.txt - Os IoCs do ambiente de desenvolvimento incluem o hook
SessionStartem.claude/settings.json,"runOn": "folderOpen"em.vscode/tasks.json,.claude/setup.mjse.vscode/setup.mjs - Os IoCs de persistência incluem
kitty-monitor.service,com.user.kitty-monitor.plist,~/.local/bin/gh-token-monitor.sh,~/.local/share/kitty/cat.pye/var/tmp/.gh_update_state
Principais pacotes a verificar
- A tabela
compromised-packages.csvtem duas colunas, Package e Compromised Versions, e mostra 317 pacotes segundo a tabela - No lockfile, é preciso verificar a presença desses pacotes e de versões maliciosas publicadas em 2026-05-19
- Principais pacotes
@antve versões maliciosas@antv/g2:5.5.8,5.6.8@antv/g6:5.2.1,5.3.1@antv/g:6.4.1,6.5.1@antv/l7:2.26.10,2.27.10@antv/x6:3.2.7,3.3.7@antv/s2:2.8.1,2.9.1@antv/f2:5.15.0,5.16.0
- Pacotes npm gerais e versões maliciosas
echarts-for-react:3.0.7,3.1.7,3.2.7size-sensor:1.0.4,1.1.4,1.2.4jest-canvas-mock:2.5.3,2.6.3,2.7.3jest-date-mock:1.0.11,1.1.11,1.2.11timeago.js:4.1.2,4.2.2timeago-react:3.1.7,3.2.7@lint-md/cli:2.1.0,2.2.0@lint-md/core:2.1.0,2.2.0@lint-md/parser:0.1.14,0.2.14
Resposta e defesa
- Se uma versão comprometida foi instalada, devem ser rotacionados os tokens npm, GitHub PATs, chaves AWS, chaves SSH, credenciais de nuvem, senhas de banco de dados, tokens do Vault, tokens de service account do Kubernetes e segredos de gerenciadores de senhas locais que estavam acessíveis no ambiente de build
t.m-kosche[.]comdeve ser bloqueado nos níveis de rede e DNS- É preciso verificar se foram criados repositórios públicos não autorizados em contas do GitHub com tokens acessíveis no ambiente de build
- Devem ser revisados os logs de publicação de pacotes não autorizados e de troca de tokens npm OIDC nos pipelines de CI
- Devem ser verificados os logs de transparência do Sigstore para identificar artifacts assinados criados por identidades de CI comprometidas
- Em projetos locais de Node.js, é preciso verificar os hooks de
.claude/settings.json, as tarefas de execução automática em.vscode/tasks.json,.claude/setup.mjse.vscode/setup.mjs - Remova o serviço de usuário systemd
kitty-monitore o LaunchAgentcom.user.kitty-monitor, e verifique a existência de~/.local/share/kitty/cat.py,/var/tmp/.gh_update_statee~/.local/bin/gh-token-monitor.sh - As dependências devem ser fixadas por versão ou deve-se usar lockfile para evitar que a resolução de intervalos semver leve a versões maliciosas
- Devem ser auditadas a exposição do socket do Docker e o acesso a metadados do EC2 nos pipelines de CI/CD, considerando também limitar o hop limit do IMDSv2
- Package Manager Guard (pmg) é um proxy de instalação open source que compara pacotes com inteligência de ameaças antes da execução do
preinstall - dependency cooldown rejeita versões publicadas dentro de uma janela de tempo configurável, reduzindo ondas repentinas de distribuição em que um intervalo semver passa a resolver para uma nova release maliciosa
- vet pode detectar atualizações anômalas de pacotes, como hooks
preinstallinesperados, aumento repentino de tamanho e mudança de maintainer, antes que cheguem aos pipelines de CI/CD - O alcance do impacto — 547 pacotes sob uma única conta e mais de 314 pacotes transformados em arma em uma única sessão — expõe fragilidades estruturais no modelo de confiança do npm
Referências
- Shai-Hulud Goes Open Source: Static Analysis of the Framework — Datadog Security Labs
- The Shai-Hulud Code Drop — ReversingLabs
1 comentários
Comentários no Hacker News
Agora os scripts de ciclo de vida do NPM deveriam vir desativados por padrão
Na prática, isso embute execução de código arbitrário em nome da conveniência, e isso também se aplica às dependências transitivas. Todos os ataques em estilo worm do NPM que se espalharam amplamente se propagaram por causa dessa configuração padrão. Não deveria ser possível ativar isso uma vez em um comando específico e permitir que todas as dependências transitivas executem scripts de ciclo de vida; seria preciso marcar explicitamente cada dependência que realmente precisa disso
A esmagadora maioria dos pacotes NPM não depende desses scripts, então, se você ainda não fez isso, vale a pena desativá-los globalmente
Dito isso, o pacote ainda pode executar qualquer lixo que quiser quando for importado pela primeira vez no programa
Dizer “não há como impedir” é o tipo de coisa que só aparece no único gerenciador de pacotes onde isso acontece regularmente
Em algum momento, talvez faça mais sentido desligar o Dependabot e fixar todos os pacotes NPM, inclusive versões minor/patch, em vez de continuar atualizando
Especialmente em pacotes de frontend, hoje em dia parece que correções de segurança relevantes são menos comuns do que ataques à cadeia de suprimentos
É uma situação triste, mas se você transformar seu frontend em uma BOM estática e confiar que o NPM ao menos cumpra corretamente a restrição de “não poder republicar uma versão anterior”, existe algum motivo para não fazer isso?
Você pode abrir exceção para versões que resolvam CVEs conhecidos
A situação está ficando cada vez mais insana. Pessoalmente, eu já removi node, python e todos os gerenciadores de pacotes da minha máquina e agora só uso dentro de devcontainers ou VMs
Mesmo que a comunidade de desenvolvedores crie uma segurança muito mais robusta, temo que daqui a pelo menos um ano a capacidade dos modelos para engenharia social já esteja boa o bastante para continuarmos jogando um jogo perdido
Por exemplo, o esforço investido no hack do XZ foi gigantesco e não podia ser acelerado, porque dependia de desgastar o mantenedor ao longo do tempo. Você pode gerar e enviar as mensagens maliciosas necessárias em segundos, mas a velocidade do humano que as lê não aumenta; se elas chegarem todas de uma vez, isso provavelmente só vai levantar suspeitas
Também há um limite para o quão persuasivo um input pode ser. Você pode pegar uma mensagem maliciosa qualquer enviada ao mantenedor do XZ e torná-la mais cruel, mais precisa e mais alinhada às fraquezas e medos pessoais dele, mas isso a tornaria realmente mais eficaz no todo? Talvez não, ou no máximo só um pouco
Agora que o Zed chegou à versão 1.0, eu queria migrar de vez, mas, pelo que sei, o modelo de segurança é tudo ou nada. Ou eu permito que ele baixe e instale qualquer pacote NPM que eu não conheço, ou desligo todos os recursos de LSP
E aí continuo vendo notícias como esta
Será que o npm não poderia operar um programa que atrasasse automaticamente uploads de pacotes por uns 10 minutos e, nesse intervalo, os distribuísse para um ecossistema terceirizado de empresas de auditoria de código para inspeções automáticas?
Poderiam manter um ranking público de quais auditores detectam problemas com mais rapidez e confiabilidade, ou até oferecer recompensa em dinheiro
Essa lista está incompleta. Pelo menos mais um pacote, a extensão nx-console para VS Code, também foi infectado por esse worm ontem e tem 2,2 milhões de downloads
Se alguém com acesso e conexões estiver lendo isto, talvez valha a pena seguir essa cadeia de dependências também para ver se há mais casos. Referência:
https://github.com/nrwl/nx-console/security/advisories/GHSA-...
PS: Eu postei isso no HN para alertar as pessoas logo após a infecção, mas infelizmente quase não recebeu votos
Considerando o ecossistema como um todo, o TC39 deveria analisar formas de adicionar uma biblioteca padrão melhor ao próprio JS. Isso reduziria a quantidade de pacotes de uma linha só
Concordo. Quando eu trabalhava com Deno, uma das melhores partes era a biblioteca padrão[0] e, no geral, um ambiente de desenvolvimento mais completo. Ter um executor de testes e uma biblioteca de asserções integrados ao runtime faz todo o sentido
0 - https://docs.deno.com/runtime/reference/std/
node:test[0] enode:assert/strict[1] há várias versões LTS. Onode --testpode substituir o Mocha com facilidade, e onode:assert/stricté bom, embora às vezes ochaiseja mais conveniente. É por questões de usabilidade comoexpect. O @std do Deno tem uma biblioteca de asserções no estiloexpectO problema é que o ecossistema Node tem executores de teste demais, e muitos deles não são tão fáceis de substituir quanto o Mocha. Então a migração para o test harness e a biblioteca de asserções incluídos por padrão inevitavelmente vai ser dolorosamente lenta. As pessoas gostam da natureza excessivamente complexa do Jest e do Vitest por vários motivos. Grandes empresas acharam que o Karma era uma boa ideia. Ainda não entendo por que mais desenvolvedores não se incomodaram com a lógica de “você gosta do V8 para testes unitários? Então vamos iniciar mais uma cópia do V8 dentro do seu ambiente V8 existente”
[0] https://nodejs.org/api/test.html
[1] https://nodejs.org/api/assert.html#strict-assertion-mode
Que biblioteca padrão de linguagem inclui um formatador do tipo “há 3 horas”? É isso que o timeago.js faz
O slice.js só oferece indexação negativa ao estilo Python. O TC39 já fez
array.at()earray.slice()lidarem com números negativoshttps://nodejs.org/api/
Estão dizendo que o payload verifica a presença do socket do Docker e, se ele existir, tenta escape de container em três métodos sequenciais
Então, mesmo que você execute isso dentro de um devcontainer ou VM, esse tipo de worm já está tentando sair de lá
É preciso garantir que você esteja usando um mecanismo de VM rootless. Por exemplo, algo como podman em vez de Docker
Isso me lembra os tempos em que se distribuíam contas Linux de baixo privilégio e se confiava que o kernel impediria elevação de privilégio. O Docker é literalmente a mesma coisa, só que com mais etapas. Especialmente hoje, quando parece surgir uma nova vulnerabilidade local de elevação de privilégio no kernel a cada 5 minutos
O Podman é um pouco melhor por não entregar root ao invasor, mas por que dar uma conta em primeiro lugar? Basta usar uma VM de verdade
Existe algo no Linux parecido com o sandbox abrangente que existe no BSD?
Eu queria descer do Mr Bones' Wild Ride agora, mas tenho medo de que isso só vá continuar. Pelo que vi, boa parte das estratégias comerciais de detecção é ajustada ao nível de repositório/dispositivo/desenvolvedor quando o pacote é carregado ou usado
Isso se parece com a forma como se lida com spam de e-mail ou malware em geral. Então quase sempre há alvos valiosos o bastante para que agentes maliciosos continuem tentando. Mas, diferente do e-mail, os gerenciadores de pacotes são autoridades centralizadas, e problemas fora de banda provavelmente acabam sendo empurrados como responsabilidade do desenvolvedor
Sem conhecer profundamente o assunto, olhando de fora parece que talvez fosse melhor abandonar a cultura de releases rápidas e versionamento frouxo e passar a focar em versões estáveis e profundamente inspecionadas no registro. Posso estar errado por causa do efeito de volume e escala, mas ainda assim é sugestivo que linguagens com mais volatilidade pareçam ser afetadas com mais frequência
Seria muito bom ter um texto que tratasse esse panorama de forma abrangente
O nome da montanha-russa no filme era Mr Bonestripper: https://www.youtube.com/watch?v=NEZEgd8GjJc
Na verdade, veio de Roller Coaster Tycoon 2: https://knowyourmeme.com/memes/mr-bones-wild-ride
Seguindo a analogia com spam, em praticamente todos os ambientes de redes de computadores comerciais e sociais acabamos aceitando capturar endereços de e-mail e fazer as pessoas tolerarem spam, revestindo isso de uma aparência de legitimidade. Algo parecido provavelmente vai acontecer aqui também. Talvez na forma de software estilo agente de auditoria de licenças da Oracle combinado com gerenciamento automático de dependências — ou seja, “resolver” malware de cadeia de suprimentos colocando outro malware na lista de permissões