ChainDrop: worm no npm infecta mais de 400 pacotes
Worm autopropagante infectou mais de 400 pacotes do npm, lê segredos direto da memória de executores de CI e busca o endereço do servidor de controle em um contrato Ethereum. Em um caso, publicou pacote adulterado com proveniência criptográfica legítima.
A Unit 42, divisão de pesquisa da Palo Alto Networks, publicou em 6 de agosto de 2026 a análise técnica do ChainDrop, um worm autopropagante que infectou mais de 400 pacotes do repositório npm — incluindo bibliotecas amplamente usadas como keyv e cacheable-request, que somadas acumulam centenas de milhões de downloads por semana.
O que distingue o ChainDrop de um pacote malicioso comum é a combinação de três características: ele se replica sozinho usando credenciais roubadas de publicação, resolve o endereço do servidor de comando e controle consultando um contrato inteligente na rede Ethereum e, no caso mais sofisticado, é capaz de publicar um pacote adulterado com proveniência criptográfica legítima — não falsificada.
Em 4 de agosto de 2026, os pesquisadores observaram o operador trocar toda a infraestrutura de C2 do worm por meio de uma única transação na blockchain, sem precisar atualizar uma linha sequer do código já instalado nas máquinas infectadas.
Contexto: a linhagem Shai-Hulud
O ChainDrop pertence à linhagem de código Shai-Hulud, nome de origem literária (é o verme das areias do romance Duna) que já batizou ondas anteriores de ataques à cadeia de suprimentos do npm. A Unit 42 lista vários indicadores que apontam para essa origem: o decodificador de strings baseado em PBKDF2, a fixação da versão 1.3.13 do runtime Bun, o padrão de relançamento em segundo plano com uma variável de ambiente dedicada, o marcador autoaplicado “Shai-Hulud: Here We Go Again” e a arquitetura de autopropagação via npm com exfiltração pelo GitHub.
A empresa é explícita quanto ao limite dessa inferência: como o código-fonte do Shai-Hulud foi publicado em maio de 2026, qualquer pessoa pode reutilizá-lo. A Unit 42 afirma não ser possível dizer, no momento, se a operação é conduzida pelo grupo conhecido como TeamPCP ou por outro ator que adaptou o material publicado.
Esta amostra mistura características que não coincidem com nenhuma variante anterior documentada: repositórios públicos de exfiltração criados na conta da própria vítima com nomes temáticos de Duna, resolução de domínio via Ethereum, uma verificação que exclui máquinas com idioma russo configurado, um caminho de publicação confiável no npm restrito a um repositório específico e entrega por hook preinstall, em vez da técnica de binding.gyp relatada em ondas anteriores.
Como a infecção pelo ChainDrop começa
Um pacote infectado é enganosamente discreto. Ele mantém todo o código legítimo, as dependências e a documentação que o desenvolvedor espera. As únicas adições são dois arquivos no nível superior e uma linha no package.json: um hook preinstall que aponta para um arquivo setup.mjs.
Esse arquivo verifica se o runtime Bun está disponível no sistema. Se não estiver, baixa a versão 1.3.13 diretamente do repositório oficial do projeto no GitHub — a Unit 42 faz questão de esclarecer que o Bun não está comprometido; o atacante apenas usa um runtime legítimo como veículo de execução portátil. Em seguida, entrega ao Bun um payload JavaScript ofuscado de 727 KB comprimido em duas linhas de código.
O payload cria um processo em segundo plano desacoplado, define uma variável de ambiente para impedir relançamento recursivo e deixa a instalação terminar normalmente. Nenhum erro, nenhum aviso — e o worm já está em execução.
Há dois comportamentos de triagem notáveis. Quando detecta um ambiente de integração contínua, o worm roda em linha no próprio job, o que faz sua saída de depuração aparecer no log do fluxo de trabalho (um detalhe útil para defensores, já que o malware é verborrágico nesse cenário). E antes de qualquer coleta, uma verificação de idioma encerra a execução limpamente em máquinas configuradas em russo.
O que o worm coleta
A varredura de credenciais é ampla. Segundo a Unit 42, o ChainDrop procura por:
- Credenciais de nuvem de múltiplas plataformas, consultando endpoints de metadados e de token tanto em instâncias de computação quanto em serviços de contêiner, o que inclui credenciais temporárias de papéis de IAM e tokens de identidade de curta duração usados por executores automatizados.
- Ferramentas de desenvolvimento: configurações de Docker e Helm, credenciais do Git, tokens de npm e GitHub, credenciais de Poetry e PyPI, tokens do RubyGems, chaves SSH, estado do Terraform e tokens do Vault.
- Ferramentas de IA: configurações e artefatos de autenticação de assistentes de codificação e de plataformas de desenvolvimento em nuvem.
- Outros artefatos: arquivos
.enve.netrc, arquivos de carteira Bitcoin e Electrum, material de credencial cifrado do Jenkins, tokens de conta de serviço do Kubernetes, kubeconfigs e históricos de shell.
Leitura da memória do executor de CI
Um dos achados mais relevantes para times de engenharia é um auxiliar em Python, escondido dentro de um bloco criptografado do payload, que localiza o processo do executor do GitHub Actions, abre os arquivos de mapeamento e de memória desse processo no sistema de arquivos virtual do Linux e varre a memória viva em busca de tokens OpenID Connect e segredos do executor.
Em vez de esperar que um segredo seja escrito em disco, o worm o captura em memória — inclusive segredos projetados para desaparecer quando o job termina. A implicação prática é direta: executores de CI devem ser tratados como alvos de credencial, e uma investigação limitada a arquivos em disco pode não encontrar a exposição real.
Persistência disfarçada de configuração de ferramenta
O worm grava cinco arquivos que se apoiam mutuamente em dois diretórios que quase ninguém audita:
.vscode/tasks.json— cria uma tarefa rotulada “Environment Setup” configurada para rodar na abertura da pasta, ou seja, sempre que o desenvolvedor abrir o projeto no editor..claude/settings.json— registra um hook de início de sessão que executa sempre que o assistente de codificação inicia uma sessão no projeto..claude/math_init.js,.claude/setup.mjse.vscode/setup.mjs— cópias do dropper e do payload.
O truque é cruzado: cada arquivo de configuração chama a cópia do dropper que está no diretório da outra ferramenta, o que faz cada artefato parecer pertencer à ferramenta vizinha. A Unit 42 observa que, nesta versão, apenas o caminho do editor chega efetivamente a um payload, e recomenda a remoção dos cinco arquivos para desativar o worm com segurança.
A amostra também carrega um instalador para persistência no nível do sistema operacional (um LaunchAgent no macOS e um serviço de usuário systemd no Linux, ambos nomeados gh-token-monitor), mas o instalador foi decifrado e nunca invocado — a rotina que o executaria não tem ponto de chamada. Trata-se, portanto, de capacidade latente, não de comportamento observado.
Propagação e o caso do OpenSearch
Com um token do npm em mãos, o worm enumera todos os pacotes que a conta pode publicar, reconstrói cada um, adiciona o hook preinstall, grava o dropper e o payload, incrementa a versão de correção e republica. O pacote continua funcionando; o código original permanece intacto.
Há, porém, um segundo caminho de propagação muito mais silencioso, e ele só se ativa em um lugar. O worm verifica três variáveis de ambiente e, se o job estiver rodando no repositório opensearch-js dentro de um fluxo de trabalho específico de release, executa uma rotina completamente diferente e não coleta nada. Se estiver no repositório certo mas no fluxo errado, encerra sem roubar nada — permanecendo silencioso justamente nas execuções que um mantenedor tem mais chance de estar lendo.
Nesse caminho, o worm não precisa de token roubado: ele solicita ao executor um token OIDC com o público do registro do npm e o troca no próprio endpoint de publicação confiável da plataforma por uma credencial real. A identidade legítima de release do repositório passa a ser a do atacante. Em seguida, em vez de adicionar um hook, o worm insere uma única linha no package.json: uma dependência cujo nome imita o escopo oficial do projeto e aponta para um commit fixado do próprio repositório. Em um diff, aquilo se lê como um auxiliar interno.
Proveniência legítima para um pacote malicioso
O passo final desse caminho merece atenção especial de qualquer time que use assinatura de artefatos como controle. Antes de publicar, o worm solicita um segundo token OIDC, obtém um certificado Fulcio, monta uma declaração de proveniência in-toto SLSA v1 sobre o hash do pacote, assina com uma chave efêmera P-256, envia a entrada ao log público de transparência Rekor e anexa o pacote de assinatura à publicação.
Isso não é proveniência falsificada. A atestação afirma que o pacote foi construído naquele repositório por aquele fluxo de trabalho — e isso é verdade. Como resume a Unit 42, uma assinatura de proveniência válida no npm não significa que o pacote esteja limpo; significa apenas que ele saiu do fluxo de trabalho indicado no certificado. Se esse fluxo estiver executando código do atacante, a proveniência válida é exatamente o que se deve esperar. A recomendação é pivotar sobre o índice do log Rekor e a identidade do fluxo dentro do certificado, e não sobre o fato de a assinatura conferir.
A Unit 42 registra que não observou esse caminho ser executado e que ele não pode rodar fora daquele repositório específico — mas está inteiramente implementado e é alcançável a partir do ponto de entrada principal do payload.
O roteador em blockchain
O ChainDrop não traz domínio de C2 codificado. Ele consulta um contrato inteligente Ethereum no endereço 0xE1f2395ee43e45A1556EC6438a88c31B83493103, um pequeno armazenamento de lista de strings com três funções: devolver os domínios, devolver o proprietário e um setter restrito ao proprietário. O contrato não emite eventos, o que torna a rotação de domínio uma escrita de estado silenciosa. Para não depender de um único provedor, o worm percorre cerca de 60 endpoints RPC públicos da rede Ethereum até um responder.
A linha do tempo reconstruída pelos pesquisadores é precisa. Os três domínios iniciais foram registrados no mesmo registrador em um intervalo de oito segundos, em 22 de maio de 2026. Quatorze minutos depois do último registro, uma corretora de câmbio transferiu cerca de 0,018 ETH para a carteira do operador. Três dias depois, em 25 de maio, a carteira publicou o contrato resolvedor, gravou os três domínios e, duas horas e 35 minutos mais tarde, reduziu a lista a um só.
Em 4 de agosto de 2026, a mesma carteira executou uma transação que trocou o domínio ativo por um recém-registrado com aparência de saída de algoritmo de geração de domínios. O novo domínio foi registrado às 15h15 UTC e já estava operacional em menos de uma hora — a primeira conexão observada ocorreu às 16h10 UTC. Em cerca de 19 horas, a Unit 42 registrou tráfego para ambientes de vítimas em quatro continentes.
Canais alternativos e o marcador em mensagens de commit
Se os domínios resolvidos pelo contrato falharem, o worm procura em commits do GitHub por um marcador de texto à espera de um registro assinado com um domínio reserva. Durante a análise, esse mecanismo estava construído mas não armado.
O comportamento mais peculiar, porém, é outro: quando o remetente baseado em GitHub carrega um token roubado, o worm codifica esse token duas vezes em Base64 e usa o resultado como mensagem de commit, prefixada por um marcador longo e ostensivo que alega, falsamente, que bloquear a chave derrubaria servidores de produção. Uma rotina separada no mesmo payload busca esse marcador na API de commits do GitHub, decodifica cada correspondência e reaproveita os tokens válidos. Na prática, as credenciais roubadas de uma vítima viram recurso utilizável por todas as outras cópias do worm em execução.
Escala observada e limitações do que se sabe
Em 4 de agosto, a Unit 42 encontrou 453 repositórios públicos em cinco contas correspondendo ao marcador de exfiltração, com o mais antigo criado em 11 de maio e o mais recente cerca de 25 minutos antes da consulta. A empresa é cuidadosa ao qualificar o dado: essas contas são candidatas a vítimas, não vítimas confirmadas, e o número pode ser apenas um ponto de partida, já que repositórios privados também podem ter sido comprometidos. Levantamentos independentes publicados por outras equipes de pesquisa chegaram a números maiores de pacotes afetados, o que reforça que o alcance total ainda está sendo medido.
Um alerta operacional relevante fecha a análise: a Unit 42 encontrou um sistema previamente comprometido em que a etiqueta latest foi revertida para uma versão limpa — o que corrigiu o problema de etiquetagem, mas não os arquivos de bloqueio, caches, espelhos e pacotes já dentro de uma imagem de CI. Máquinas que instalaram o pacote durante o período de comprometimento não recebem a correção automaticamente, porque o arquivo de bloqueio retém a versão infectada.
Recomendações
- Identifique e remova as versões afetadas dos pacotes e limpe arquivos de bloqueio, caches, espelhos e imagens de CI — atualizar a etiqueta latest não basta.
- Rotacione credenciais de npm, tokens pessoais e chaves de implantação do GitHub, credenciais de nuvem, tokens de conta de serviço do Kubernetes, tokens do Vault, chaves SSH e credenciais de provedores de IA acessíveis a hosts infectados. Trate executores de CI como potencialmente comprometidos.
- Procure nos repositórios por
.vscode/tasks.jsonchamando.claude/setup.mjs,.claude/settings.jsonchamando.vscode/setup.mjs, fluxos de trabalho que serializem o objeto de segredos do GitHub Actions e pelos arquivosmath_init.js,setup.mjserouter_runtime.js. - Bloqueie os dois canais. Adicione os domínios de C2 a listas de bloqueio de DNS e SNI, prefira sinkhole a página de bloqueio HTTP (o worm interpreta 400 e 404 como resposta saudável) e monitore o contrato resolvedor por novas chamadas de escrita. Bloquear apenas o domínio não impede a exfiltração pelo GitHub.
- Audite o histórico de commits em busca dos marcadores de texto usados pelo worm; uma correspondência indica credencial exposta publicamente e que precisa ser revogada imediatamente.
- Endureça o pipeline: use executores de CI efêmeros de uso único, vincule a autenticação à carga de trabalho (mTLS com identidade SPIFFE, papéis de IAM ou tokens de conta de serviço projetados com claim de público) em vez de segredos portadores em arquivos, aplique filtragem de saída restrita nos executores e plante credenciais-isca em caminhos como
~/.aws/credentialse~/.npmrccom alerta em qualquer uso.
Conclusão
O ChainDrop consolida uma mudança incômoda para quem defende cadeias de suprimentos de software. A infraestrutura de comando deixou de ser um domínio que se bloqueia e passou a ser um estado em blockchain que o operador reescreve em segundos. E o controle de proveniência criptográfica, que muitos times adotaram justamente como resposta a ataques anteriores no npm, mostrou-se insuficiente quando o atacante consegue executar código dentro do fluxo de trabalho legítimo — a assinatura continua válida porque, tecnicamente, ela está dizendo a verdade.
Vale acompanhar duas coisas nos próximos meses: se o mecanismo de reserva baseado em busca de commits, hoje desarmado, chegará a ser ativado; e se o caminho de publicação confiável demonstrado no caso do OpenSearch será replicado contra outros repositórios de alto alcance. Se isso acontecer, a discussão sobre atestação de proveniência precisará avançar da pergunta “a assinatura é válida?” para “quem controlava o fluxo de trabalho que assinou?”.
Fontes e referências
- Unit 42, Palo Alto Networks — ChainDrop: Inside a Self-Propagating npm Worm: unit42.paloaltonetworks.com
- JFrog Security Research — Major Shai Hulud campaign strikes npm again, affecting keyv and 400+ packages: research.jfrog.com
- Elastic Security Labs — Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages: elastic.co
- Datadog Security Labs — Worm compromises hundreds of popular npm packages: securitylabs.datadoghq.com
- Wiz — keyv and cacheable npm Package Hijacked in Supply Chain Attack: wiz.io
- VentureBeat — The Shai-Hulud npm worm didn’t fake its security check — it earned a legitimate one: venturebeat.com