Malware in 42 @tanstack/* packages exfiltrates cloud credentials, GitHub tokens, and SSH keys
Priorize a correção. Ela está sob exploração confirmada pelo CISA e tem prova de conceito pública.
Apply mitigations per vendor instructions, follow applicable BOD 22-01 guidance for cloud services, or discontinue use of the product if mitigations are unavailable.
Resumo
Não é uma falha de código nos pacotes @tanstack/*, mas um comprometimento pontual da cadeia de publicação: em 11/05/2026, um atacante conseguiu publicar 84 versões maliciosas de 42 pacotes @tanstack/* no npm, autenticadas via o mecanismo legítimo de trusted-publisher OIDC do GitHub Actions do repositório TanStack/router. Quem instalou uma dessas versões específicas — publicadas numa janela de ~6 minutos — executou um payload que rouba credenciais de nuvem, tokens do GitHub e chaves SSH. O impacto é real e severo para quem instalou nessa janela exata, mas irrelevante para qualquer instalação fora dela: não há brecha residual explorável no código do TanStack hoje.
Detalhamento técnico
O ataque encadeou três classes de falha conhecidas em pipelines de CI/CD do GitHub Actions. Primeiro, um 'Pwn Request' (CWE-829, execução de código de esfera não confiável): o atacante abriu um PR de um fork renomeado (github.com/zblgg/configuration) contendo um commit malicioso com payload JS bundlado. Workflows configurados com pull_request_target (bundle-size.yml, labeler.yml) rodam automaticamente para PRs de forks, sem exigir aprovação de first-time contributor — diferente do workflow pr.yml, que usa pull_request e ficou bloqueado. Um force-push no PR fez o job de benchmark checkar out o merge do PR e executar pnpm install, disparando o payload do fork.
Segundo, envenenamento do cache do GitHub Actions através da fronteira de confiança fork↔base: a execução do payload durante o job do PR salvou uma entrada de cache pnpm-store com uma chave que coincidia com a que o workflow de release (release.yml) do branch main buscaria depois. O PR foi então revertido para um estado inócuo e fechado, mas o cache envenenado permaneceu no repositório.
Terceiro, extração de memória em runtime do token OIDC do processo do runner: quando um release legítimo do TanStack/router foi disparado (merge de PRs não relacionados), o release.yml restaurou o cache envenenado, executando o malware novamente — agora dentro de um workflow com permissão id-token: write. O malware minerou o token OIDC do processo do runner em memória e usou-o para publicar pacotes diretamente via POST para registry.npmjs.org, contornando o step 'Publish Packages' definido no workflow (que sequer chegou a rodar, pois os testes falharam). Nenhum token de npm foi roubado e o workflow de publish em si não foi alterado — a autenticação usada era genuína.
O payload em si (router_init.js, ~2,3 MB, obfuscado) é entregue via uma dependência opcional inexistente no registro (@tanstack/setup) que resolve para um commit órfão num fork do repositório — o GitHub serve commits de toda a rede de forks para dependências git-URL, então o atacante não precisou de acesso de escrita ao repositório oficial.
Como é explorada
Não há exploração 'remota' no sentido tradicional de um atacante mirando uma vítima específica: o vetor é a cadeia de suprimentos do npm. Qualquer ambiente — dev local ou CI — que executou npm install, pnpm install ou yarn install contra uma das versões maliciosas exatas publicadas entre 19:20 e 19:26 UTC de 11/05/2026 disparou o lifecycle script 'prepare' da dependência opcional falsa, que baixou e executou o payload automaticamente, sem interação além do próprio comando de instalação (refletido no UI:R do vetor CVSS).
O payload varre e exfiltra: metadados de instância AWS (IMDS) e Secrets Manager, metadata service GCP, tokens de service account Kubernetes, tokens do HashiCorp Vault, ~/.npmrc, tokens do GitHub (variáveis de ambiente, config do gh CLI, .git-credentials) e chaves privadas SSH em ~/.ssh/. A exfiltração ocorre pela rede de upload de arquivos do Session/Oxen messenger (filev2.getsession.org, seed{1,2,3}.getsession.org), ponta a ponta criptografada e sem C2 controlado diretamente pelo atacante — o que elimina bloqueio por IP/domínio como controle eficaz de conteúdo, embora ainda sirva como indicador. Adicionalmente, o malware se autopropaga: enumera outros pacotes que a vítima mantém via a API de busca do npm (maintainer:) e republica-os com a mesma injeção, criando um efeito cascata na cadeia de suprimentos.
A falha está confirmada no catálogo KEV da CISA como exploração ativa (adicionada 2026-05-27, prazo 2026-06-10) e há PoC pública documentando o fingerprint do ataque. O EPSS baixo (0,023) reflete que, encerrado o incidente e removidas as versões do registro, o risco prospectivo de exploração é mínimo — o dano relevante já ocorreu na janela específica.
Versões
Como se proteger
Atualizar imediatamente qualquer um dos 42 pacotes @tanstack/* afetados para a versão corrigida (patch) publicada na sequência de cada par malicioso — por exemplo @tanstack/react-router 1.169.5/1.169.8 (maliciosas) → 1.169.9 (corrigida); @tanstack/history 1.161.9/1.161.12 → 1.161.13; @tanstack/eslint-plugin-router 1.161.9/1.161.12 → 1.161.13; @tanstack/eslint-plugin-start 0.0.4/0.0.7 → 0.0.8; @tanstack/nitro-v2-vite-plugin 1.154.12/1.154.15 → 1.154.16; @tanstack/react-router-devtools 1.166.16/1.166.19 → 1.166.20; @tanstack/arktype-adapter e @tanstack/valibot-adapter e @tanstack/zod-adapter 1.166.12/1.166.15 → 1.166.16 (ver advisório GHSA-g7cv-rxg3-hmpx para a tabela completa das 42 famílias). Pacotes fora dessa lista — Query, Table, Form, Virtual, Store, o meta-pacote @tanstack/start — não foram afetados segundo o fornecedor.
Se o ambiente instalou uma versão maliciosa na janela de 2026-05-11 19:20–19:26 UTC, atualizar o pacote não é suficiente: o host de instalação deve ser tratado como comprometido. É necessário rotacionar toda credencial acessível ao processo de instalação — credenciais AWS e GCP, tokens de service account Kubernetes, tokens Vault, tokens npm (~/.npmrc), tokens e credenciais GitHub (env vars, gh CLI, .git-credentials) e chaves SSH privadas — e revisar logs de auditoria de nuvem por atividade originada dos hosts afetados durante e após a janela de instalação. Bloquear filev2.getsession.org e seed{1,2,3}.getsession.org é o único controle de rede aplicável, mas não substitui a rotação de credenciais: a exfiltração é ponta a ponta criptografada e pode já ter concluído antes de qualquer bloqueio surtir efeito.
O que não funciona como mitigação isolada: apenas fixar (pin) uma versão anterior à data do incidente sem verificar se ela é uma das duas versões maliciosas publicadas; confiar em lockfiles gerados durante a janela do ataque sem regenerá-los contra versões corrigidas; e assumir que, por a autenticação de publish ter sido legítima (OIDC trusted-publisher), o conteúdo publicado também é confiável — o mecanismo de confiança foi abusado, não falsificado.
Como detectar
O fingerprint mais confiável é o manifesto (package.json) da versão instalada: as versões maliciosas contêm a entrada exata optionalDependencies: {"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"} — um pacote que não existe no registro npm. Para inspecionar sem rodar scripts de lifecycle, baixe o tarball com npm pack @tanstack/@ (não executa install scripts), extraia e verifique o package.json e a presença de router_init.js (~2,3 MB) na raiz do pacote — arquivo deliberadamente omitido do array "files", então não aparece no conteúdo documentado do pacote.
Em tráfego de rede, indicadores de exfiltração são conexões para filev2.getsession.org e os hosts seed1/seed2/seed3.getsession.org (rede de upload do Session/Oxen messenger) originadas de hosts de build/CI ou estações de desenvolvimento — mas como o canal é E2E criptografado sem C2 fixo do atacante, a ausência desse tráfego não garante ausência de comprometimento caso outro canal tenha sido usado. Em logs de nuvem, procure atividade anômala (chamadas de API, criação de chaves de acesso) originada de hosts que rodaram instalação de pacotes @tanstack/* durante ou pouco depois da janela 2026-05-11 19:20–19:26 UTC.