Mini Shai-Hulud Worm Hits npm: TanStack and Mistral Among 160+ Packages Compromised in Massive Supply Chain Attack

The CyberSec Guru

Updated on:

Mini Shai-Hulud npm Attack

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the main content 100% free for learners worldwide, Writeup Access: Get complete in-depth writeup with scripts access within 12 hours of machine drop.

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

TL;DR

A sophisticated self-propagating worm, dubbed “Mini Shai-Hulud” by the threat group TeamPCP, has successfully compromised over 169 npm packages, including industry-standard tools from TanStack and Mistral AI. The malware utilizes a high-level cache-poisoning technique to hijack legitimate GitHub Actions workflows, allowing it to publish malicious versions with valid SLSA Build Level 3 provenance. Unlike traditional typosquatting, this attack subverts the “Trusted Publishing” mechanism itself by leveraging short-lived OIDC tokens. If you have updated dependencies in the last 24 hours, you must audit your lockfiles immediately and treat all environment variables as potentially compromised.

The Scale of the Compromise

What began as a targeted campaign against SAP-related packages in April has escalated into a broad-spectrum supply chain disaster. As of May 12, 2026, security researchers have identified 373 malicious package-version entries across 169 distinct npm package names.

The “Mini Shai-Hulud” worm is a true self-propagating entity – a “force multiplier” in the world of malware. Once it gains a foothold in a developer’s machine or a CI/CD runner, it doesn’t just sit idle. It actively harvests GitHub and npm tokens to map out the victim’s entire ecosystem. By identifying every other package the victim has maintainer permissions for, the worm automatically clones those repositories, injects its malicious payload, increments the version number, and republishes them.

This creates an exponential infection curve. The attack has rapidly moved from specialized aviation data packages (@squawk) to core AI infrastructure (@mistralai) and essential web routing tools (@tanstack). The impact is particularly severe because the compromised packages are often “transitive dependencies” meaning you may be running this malware even if you haven’t explicitly installed a TanStack package, simply because one of your other tools relies on it. In the modern web ecosystem, where a single npm install can pull in thousands of sub-dependencies, the blast radius of a compromised root-level package like TanStack is nearly immeasurable.

Comprehensive List of Affected Packages

Below is the full list of packages and versions currently identified as compromised. If your project relies on any of these, assume the environment is exposed and secrets are compromised.

Yes — this version includes everything from the pasted file, deduplicated by package name, with a Registry column showing whether each package is from npm or PyPI. Source:

PyPI Packages

RegistryPackage NameCompromised Versions
PyPIguardrails-ai0.10.1
PyPIlightning2.6.2, 2.6.3
PyPImistralai2.4.6

TanStack Ecosystem – npm

RegistryPackage NameCompromised Versions
npm@tanstack/arktype-adapter1.166.12, 1.166.15
npm@tanstack/eslint-plugin-router1.161.9, 1.161.12
npm@tanstack/eslint-plugin-start0.0.4, 0.0.7
npm@tanstack/history1.161.9, 1.161.12
npm@tanstack/nitro-v2-vite-plugin1.154.12, 1.154.15
npm@tanstack/react-router1.169.5, 1.169.8
npm@tanstack/react-router-devtools1.166.16, 1.166.19
npm@tanstack/react-router-ssr-query1.166.15, 1.166.18
npm@tanstack/react-start1.167.68, 1.167.71
npm@tanstack/react-start-client1.166.51, 1.166.54
npm@tanstack/react-start-rsc0.0.47, 0.0.50
npm@tanstack/react-start-server1.166.55, 1.166.58
npm@tanstack/router-cli1.166.46, 1.166.49
npm@tanstack/router-core1.169.5, 1.169.8
npm@tanstack/router-devtools1.166.16, 1.166.19
npm@tanstack/router-devtools-core1.167.6, 1.167.9
npm@tanstack/router-generator1.166.45, 1.166.48
npm@tanstack/router-plugin1.167.38, 1.167.41
npm@tanstack/router-ssr-query-core1.168.3, 1.168.6
npm@tanstack/router-utils1.161.11, 1.161.14
npm@tanstack/router-vite-plugin1.166.53, 1.166.56
npm@tanstack/solid-router1.169.5, 1.169.8
npm@tanstack/solid-router-devtools1.166.16, 1.166.19
npm@tanstack/solid-router-ssr-query1.166.15, 1.166.18
npm@tanstack/solid-start1.167.65, 1.167.68
npm@tanstack/solid-start-client1.166.50, 1.166.53
npm@tanstack/solid-start-server1.166.54, 1.166.57
npm@tanstack/start-client-core1.168.5, 1.168.8
npm@tanstack/start-fn-stubs1.161.9, 1.161.12
npm@tanstack/start-plugin-core1.169.23, 1.169.26
npm@tanstack/start-server-core1.167.33, 1.167.36
npm@tanstack/start-static-server-functions1.166.44, 1.166.47
npm@tanstack/start-storage-context1.166.38, 1.166.41
npm@tanstack/valibot-adapter1.166.12, 1.166.15
npm@tanstack/virtual-file-routes1.161.10, 1.161.13
npm@tanstack/vue-router1.169.5, 1.169.8
npm@tanstack/vue-router-devtools1.166.16, 1.166.19
npm@tanstack/vue-router-ssr-query1.166.15, 1.166.18
npm@tanstack/vue-start1.167.61, 1.167.64
npm@tanstack/vue-start-client1.166.46, 1.166.49
npm@tanstack/vue-start-server1.166.50, 1.166.53
npm@tanstack/zod-adapter1.166.12, 1.166.15

Mistral AI – npm

RegistryPackage NameCompromised Versions
npm@mistralai/mistralai2.2.2, 2.2.3, 2.2.4
npm@mistralai/mistralai-azure1.7.1, 1.7.2, 1.7.3
npm@mistralai/mistralai-gcp1.7.1, 1.7.2, 1.7.3

UiPath – npm

RegistryPackage NameCompromised Versions
npm@uipath/access-policy-sdk0.3.1
npm@uipath/access-policy-tool0.3.1
npm@uipath/admin-tool0.1.1
npm@uipath/agent-sdk1.0.2
npm@uipath/agent-tool1.0.1
npm@uipath/agent.sdk0.0.18
npm@uipath/aops-policy-tool0.3.1
npm@uipath/ap-chat1.5.7
npm@uipath/api-workflow-tool1.0.1
npm@uipath/apollo-core5.9.2
npm@uipath/apollo-react4.24.5
npm@uipath/apollo-wind2.16.2
npm@uipath/auth1.0.1
npm@uipath/case-tool1.0.1
npm@uipath/cli1.0.1
npm@uipath/codedagent-tool1.0.1
npm@uipath/codedagents-tool0.1.12
npm@uipath/codedapp-tool1.0.1
npm@uipath/common1.0.1
npm@uipath/context-grounding-tool0.1.1
npm@uipath/data-fabric-tool1.0.2
npm@uipath/docsai-tool1.0.1
npm@uipath/filesystem1.0.1
npm@uipath/flow-tool1.0.2
npm@uipath/functions-tool1.0.1
npm@uipath/gov-tool0.3.1
npm@uipath/identity-tool0.1.1
npm@uipath/insights-sdk1.0.1
npm@uipath/insights-tool1.0.1
npm@uipath/integrationservice-sdk1.0.2
npm@uipath/integrationservice-tool1.0.2
npm@uipath/llmgw-tool1.0.1
npm@uipath/maestro-sdk1.0.1
npm@uipath/maestro-tool1.0.1
npm@uipath/orchestrator-tool1.0.1
npm@uipath/packager-tool-apiworkflow0.0.19
npm@uipath/packager-tool-bpmn0.0.9
npm@uipath/packager-tool-case0.0.9
npm@uipath/packager-tool-connector0.0.19
npm@uipath/packager-tool-flow0.0.19
npm@uipath/packager-tool-functions0.1.1
npm@uipath/packager-tool-webapp1.0.6
npm@uipath/packager-tool-workflowcompiler0.0.16
npm@uipath/packager-tool-workflowcompiler-browser0.0.34
npm@uipath/platform-tool1.0.1
npm@uipath/project-packager1.1.16
npm@uipath/resource-tool1.0.1
npm@uipath/resourcecatalog-tool0.1.1
npm@uipath/resources-tool0.1.11
npm@uipath/robot1.3.4
npm@uipath/rpa-legacy-tool1.0.1
npm@uipath/rpa-tool0.9.5
npm@uipath/solution-packager0.0.35
npm@uipath/solution-tool1.0.1
npm@uipath/solutionpackager-sdk1.0.11
npm@uipath/solutionpackager-tool-core0.0.34
npm@uipath/tasks-tool1.0.1
npm@uipath/telemetry0.0.7
npm@uipath/test-manager-tool1.0.2
npm@uipath/tool-workflowcompiler0.0.12
npm@uipath/traces-tool1.0.1
npm@uipath/ui-widgets-multi-file-upload1.0.1
npm@uipath/uipath-python-bridge1.0.1
npm@uipath/vertical-solutions-tool1.0.1
npm@uipath/vss0.1.6
npm@uipath/widget.sdk1.2.3

Squawk Aviation – npm

RegistryPackage NameCompromised Versions
npm@squawk/airport-data0.7.4, 0.7.5, 0.7.6, 0.7.7, 0.7.8
npm@squawk/airports0.6.2, 0.6.3, 0.6.4, 0.6.5, 0.6.6
npm@squawk/airspace0.8.1, 0.8.2, 0.8.3, 0.8.4, 0.8.5
npm@squawk/airspace-data0.5.3, 0.5.4, 0.5.5, 0.5.6, 0.5.7
npm@squawk/airway-data0.5.4, 0.5.5, 0.5.6, 0.5.7, 0.5.8
npm@squawk/airways0.4.2, 0.4.3, 0.4.4, 0.4.5, 0.4.6
npm@squawk/fix-data0.6.4, 0.6.5, 0.6.6, 0.6.7, 0.6.8
npm@squawk/fixes0.3.2, 0.3.3, 0.3.4, 0.3.5, 0.3.6
npm@squawk/flight-math0.5.4, 0.5.5, 0.5.6, 0.5.7, 0.5.8
npm@squawk/flightplan0.5.2, 0.5.3, 0.5.4, 0.5.5, 0.5.6
npm@squawk/geo0.4.4, 0.4.5, 0.4.6, 0.4.7, 0.4.8
npm@squawk/icao-registry0.5.2, 0.5.3, 0.5.4, 0.5.5, 0.5.6
npm@squawk/icao-registry-data0.8.4, 0.8.5, 0.8.6, 0.8.7, 0.8.8
npm@squawk/mcp0.9.1, 0.9.2, 0.9.3, 0.9.4, 0.9.5
npm@squawk/navaid-data0.6.4, 0.6.5, 0.6.6, 0.6.7, 0.6.8
npm@squawk/navaids0.4.2, 0.4.3, 0.4.4, 0.4.5, 0.4.6
npm@squawk/notams0.3.6, 0.3.7, 0.3.8, 0.3.9, 0.3.10
npm@squawk/procedure-data0.7.3, 0.7.4, 0.7.5, 0.7.6, 0.7.7
npm@squawk/procedures0.5.2, 0.5.3, 0.5.4, 0.5.5, 0.5.6
npm@squawk/types0.8.1, 0.8.2, 0.8.3, 0.8.4, 0.8.5
npm@squawk/units0.4.3, 0.4.4, 0.4.5, 0.4.6, 0.4.7
npm@squawk/weather0.5.6, 0.5.7, 0.5.8, 0.5.9, 0.5.10

TallyUI – npm

RegistryPackage NameCompromised Versions
npm@tallyui/components1.0.1, 1.0.2, 1.0.3
npm@tallyui/connector-medusa1.0.1, 1.0.2, 1.0.3
npm@tallyui/connector-shopify1.0.1, 1.0.2, 1.0.3
npm@tallyui/connector-vendure1.0.1, 1.0.2, 1.0.3
npm@tallyui/connector-woocommerce1.0.1, 1.0.2, 1.0.3
npm@tallyui/core0.2.1, 0.2.2, 0.2.3
npm@tallyui/database1.0.1, 1.0.2, 1.0.3
npm@tallyui/pos0.1.1, 0.1.2, 0.1.3
npm@tallyui/storage-sqlite0.2.1, 0.2.2, 0.2.3
npm@tallyui/theme0.2.1, 0.2.2, 0.2.3

DraftLab / DraftAuth – npm

RegistryPackage NameCompromised Versions
npm@draftauth/client0.2.1, 0.2.2
npm@draftauth/core0.13.1, 0.13.2
npm@draftlab/auth0.24.1, 0.24.2
npm@draftlab/auth-router0.5.1, 0.5.2
npm@draftlab/db0.16.1, 0.16.2

ML Toolkit – npm

RegistryPackage NameCompromised Versions
npm@ml-toolkit-ts/preprocessing1.0.2, 1.0.3
npm@ml-toolkit-ts/xgboost1.0.3, 1.0.4
npmml-toolkit-ts1.0.4, 1.0.5

SuperSurkhet – npm

RegistryPackage NameCompromised Versions
npm@supersurkhet/cli0.0.2, 0.0.3, 0.0.4, 0.0.5, 0.0.6, 0.0.7
npm@supersurkhet/sdk0.0.2, 0.0.3, 0.0.4, 0.0.5, 0.0.6, 0.0.7

SAP CAP / MBT – npm

RegistryPackage NameCompromised Versions
npm@cap-js/db-service2.10.1
npm@cap-js/postgres2.2.2
npm@cap-js/sqlite2.2.2
npmmbt1.2.48

MesaDev / Dirigible AI – npm

RegistryPackage NameCompromised Versions
npm@dirigible-ai/sdk0.6.2, 0.6.3
npm@mesadev/rest0.28.3
npm@mesadev/saguaro0.4.22
npm@mesadev/sdk0.28.3

Other npm Packages

RegistryPackage NameCompromised Versions
npm@beproduct/nestjs-auth0.1.2, 0.1.3, 0.1.4, 0.1.5, 0.1.6, 0.1.7, 0.1.8, 0.1.9, 0.1.10, 0.1.11, 0.1.12, 0.1.13, 0.1.14, 0.1.15, 0.1.16, 0.1.17, 0.1.18, 0.1.19
npm@opensearch-project/opensearch3.5.3, 3.6.2, 3.7.0, 3.8.0
npm@taskflow-corp/cli0.1.24, 0.1.25, 0.1.26, 0.1.27, 0.1.28, 0.1.29
npm@tolka/cli1.0.2, 1.0.3, 1.0.4, 1.0.5, 1.0.6
npmagentwork-cli0.1.4, 0.1.5
npmcmux-agent-mcp0.1.3, 0.1.4, 0.1.5, 0.1.6, 0.1.7, 0.1.8
npmcross-stitch1.1.3, 1.1.4, 1.1.5, 1.1.6, 1.1.7
npmgit-branch-selector1.3.3, 1.3.4, 1.3.5, 1.3.6, 1.3.7
npmgit-git-git1.0.8, 1.0.9, 1.0.10, 1.0.11, 1.0.12
npmintercom-client7.0.4
npmnextmove-mcp0.1.3, 0.1.4, 0.1.5, 0.1.6, 0.1.7
npmsafe-action0.8.3, 0.8.4
npmts-dna3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.0.5
npmwot-api0.8.1, 0.8.2, 0.8.3, 0.8.4

The Attack Methodology

Attack Methodology
Attack Methodology

The GitHub Actions Cache Poisoning

The attack on TanStack was a masterclass in CI/CD exploitation, revealing a critical weakness in how modern developers manage shared build environments. Rather than attempting to guess a maintainer’s password or bypass complex multi-factor authentication (MFA), the attacker (using the handle voicproducoes) submitted a seemingly routine and helpful Pull Request (#7378).

In standard GitHub Actions workflows, PRs trigger a suite of tests. Within the context of this specific PR, malicious code executed and stole the GitHub Actions Cache write token. This token, which is usually ambiently available to build scripts, is intended to let developers save build artifacts (like compiled binaries, minified JS, or linting results) to speed up future runs. This cache is “scoped” but often shared across different branches or workflows for the same project to ensure efficiency.

The attacker used this token to upload a “poisoned” build cache. This cache contained malicious files disguised as legitimate build outputs, specifically targeting the dist/ and src/ directories. Crucially, when the official maintainers later pushed a legitimate update to the main branch, the release.yml workflow pulled the malicious artifacts from the cache believing them to be valid results from previous clean builds and “baked” them into the final production bundle. The maintainers were, in effect, tricked into packaging the malware themselves, giving the finished product the appearance of being totally legitimate.

Exploiting Sigstore and SLSA Provenance

In a first for the npm ecosystem, these malicious packages carry valid SLSA Build Level 3 provenance. This is a landmark failure of the current “Provenance” model. The worm exploited “Trusted Publishing” (the modern replacement for long-lived npm tokens) by hijacking OIDC tokens (ACTIONS_ID_TOKEN_REQUEST_TOKEN) while the legitimate release workflow was in flight.

By interacting with the Sigstore stack – specifically the Fulcio certificate authority and the Rekor transparency log, the malware generated authentic in-toto statements. These attestations “proved” to any automated security scanner that the package was built by the official TanStack repository on a legitimate GitHub runner. This represents a fundamental shift in supply chain security: it proves that “signed” and “attested” packages are not inherently safe if the build pipeline itself has been compromised via an upstream cache or PR. The signature no longer guarantees the integrity of the code, only its origin. It tells you where the package came from, but not what was inside it during the build process.

Payload Obfuscation

The primary malicious file, router_init.js, is a massive 2.3 MB single-line JavaScript file. To evade detection by both static analysis tools and automated sandboxes, it utilizes three distinct layers of cryptographic defense, requiring a sophisticated deobfuscation sequence:

  • Layer 1 (String Table Rotation): The code contains a shuffled array of 11,516 custom-base64-encoded strings. Upon execution, an Immediately Invoked Function Expression (IIFE) rotates these elements millions of times until a specific internal checksum (0x79e08) is reached. This checksum acts as a “key” that, when matched, unlocks the primary deobfuscator function. This prevents basic grep-based scanners from finding common keywords like “https”, “crypto”, or “fetch”.
  • Layer 2 (Secondary Cipher): A Fisher-Yates substitution cipher driven by a SHA-256 stream RNG. The master key for this cipher is derived via PBKDF2-SHA256 using a specific salt (svksjrhjkcejg) with 200,000 iterations. This layer hides over 300 sensitive strings, including the command-and-control (C2) domains, credential file paths, and the names of the AI tools targeted for the next stage of infection.
  • Layer 3 (AES-256-GCM Encrypted Payloads): The actual functional logic of the worm including the memory scraper and persistence scripts is stored as eleven secondary payloads encrypted with AES-256-GCM. These blobs are only decrypted and decompressed using the Bun runtime at the final stage of execution. The use of Bun is a deliberate evasion technique; many security monitors only hook into the standard node process, leaving Bun-based execution essentially invisible to many runtime protection tools.

What the Malware Steals

Memory Scraping via /proc/{pid}/mem

The most technically advanced capability of Mini Shai-Hulud is its memory scraper. Written in Python and decrypted from the Layer 3 storage, it targets the Runner.Worker process on Linux-based GitHub Actions runners. By reading the process memory directly, it can extract secrets that are “masked” in logs.

While a GitHub log might show *** for a secret variable, the malware sees the raw plaintext value sitting in the runner’s heap memory. This ensures that every secret configured in a repository – even those never explicitly called, referenced, or used in the workflow YAML is exfiltrated to the C2 server. This is a total “omniscience” exploit that renders standard secret-masking and vault-masking techniques entirely useless. It effectively turns the secure runner environment into an open book for the attacker.

Granular File Harvesting: 100+ Targeted Paths

The payload performs a comprehensive “smash and grab” of the local file system, scanning over 100 hardcoded paths decoded from the secondary cipher. This list is vast and covers nearly every modern developer tool:

  • Cloud Infrastructure & Kubernetes: ~/.aws/credentials, ~/.azure/accessTokens.json, ~/.kube/config, and Google Cloud’s ~/.config/gcloud/*. It also aggressively targets Terraform state files and credentials, which often contain broad access to production infrastructure.
  • Developer Ecosystem Secrets: Every .npmrc (containing publishing tokens), .pypirc, .docker/config.json, and .terraform.d/credentials.tfrc.json. The goal here is to find tokens that allow the worm to publish even more packages under the victim’s name.
  • The AI Frontier: Interestingly, the worm specifically targets AI agent configurations like ~/.claude.json and ~/.kiro/settings/mcp.json. TeamPCP recognizes that modern developers are increasingly using AI assistants that store high-privilege tokens (like Anthropic or OpenAI API keys) in new, non-standard file paths.
  • Crypto, Messaging, & History: It scans for Exodus, Electrum, and Atomic wallet stores, along with session cookies for Signal, Slack, Telegram, and Discord. Shell history files (.bash_history, .zsh_history, .python_history) are also harvested to find passwords and tokens that developers may have accidentally pasted into terminal commands.

Exfiltration: Redundant and Stealthy Channels

Channel 1: The Session Protocol CDN

To bypass traditional corporate firewalls, egress filters, and DNS sinkholes, the malware uploads encrypted blobs to filev2.getsession.org. Because the Session messaging protocol is a legitimate privacy-focused tool built on the Oxen network, its CDN is often white-listed or categorized as “low risk” by enterprise security products. The payload even pins the TLS certificate for seed1.getsession.org (valid until 2033) to prevent the connection from being intercepted or analyzed by middle-boxes or “man-in-the-middle” security proxies used by SOC teams.

Channel 2: GitHub GraphQL Dead-Drops

As a fallback channel, the worm uses the GitHub GraphQL API to “dead-drop” encrypted data into attacker-controlled repositories. These commits are authored to appear as if they come from the legitimate Claude bot (claude@users.noreply.github.com) to avoid raising suspicion. The commit message is consistently and innocuously set to chore: update dependencies.

The branch names are a clear signature of the TeamPCP group, using a specific wordlist drawn from the Dune universe (e.g., sietch, sardaukar, atreides, shai-hulud). These branches are prefixed with dependabot/github_actions/format/ to blend in with the thousands of automated PRs generated by security scanning tools every day. By masquerading as Dependabot, the attacker ensures that a quick glance at the “Branches” tab of a repository won’t immediately reveal the malicious activity.

The Ransom Ultimatum: A Destructive Logic Bomb

The malware attempts to secure its foothold through a psychological and technical “logic bomb.” During its initial run, it creates a new npm token on the victim’s account with a very specific and terrifying description:

"IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner"

This is not an idle threat. The persistence layer (the “Shai-Hulud Hook”) includes a watchdog routine that periodically checks if its stolen tokens are still valid by querying the npm registry. If it detects that a token has been revoked, deleted, or “burned,” the malware triggers a destructive routine. This routine attempts a recursive deletion of the user’s home directory (rm -rf ~) and overwrites critical configuration files. Do not revoke tokens until the infected machine is fully isolated, disconnected from the internet, and its drive has been imaged for forensic analysis.

Detailed Attack Methodology
Detailed Attack Methodology

Recovery and Mitigation Steps for Developers

  1. Lockfile Audit: Search your lockfiles for the compromised versions listed in the table above. Use grep or find to look specifically for any github:tanstack/router#79ac49ee... entries in your optionalDependencies section. Any occurrence is a confirmed infection.
  2. Secret Rotation: If you have interacted with a compromised version, you must treat your entire environment as “poisoned.” Rotate ALL secrets: npm tokens, GitHub PATs, AWS/GCP keys, and SSH keys. Assume any secret that was in your environment variables at the time of the attack is now in the hands of TeamPCP.
  3. Remove Persistence Hooks:
    • Delete .claude/router_runtime.js, .claude/setup.mjs, and .vscode/setup.mjs.
    • On macOS, remove ~/Library/LaunchAgents/com.user.gh-token-monitor.plist.
    • On Linux, stop and disable gh-token-monitor.service via systemctl --user.
    • Check .vscode/tasks.json for any “folderOpen” tasks you didn’t create.
  4. Isolate and Image: If you find the ransom token description, do not revoke it while the machine is connected to the internet. Power down the hardware immediately. Seek professional incident response assistance to image your drive and safely extract the malware before attempting to clean the system.

Apart from the generic ones, perform the following steps:

  • Check for malicious router_init.js files
    • Search your dependency tree for every router_init.js file.
    • Generate a SHA-256 hash for each one.
    • Treat it as compromised if it matches:
ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
  • Rotate secrets immediately
    • Rotate credentials on every system that installed an affected @tanstack/* package.
    • Prioritize in this order:
      • npm tokens
      • GitHub PATs
      • GitHub OIDC trust relationships
      • AWS static keys
      • AWS instance-role credentials
      • Vault tokens
      • Kubernetes service account tokens
  • Temporarily revoke GitHub Actions OIDC access
    • Disable OIDC federation for affected repositories.
    • Re-enable it only after confirming the release workflow, repository settings, and runner environment are clean.
  • Inspect developer and project configuration folders
    • Check .claude/ and .vscode/ directories in:
      • Developer home directories
      • Project roots
      • CI workspaces
    • Remove suspicious files such as:
      • router_runtime.js
      • setup.mjs
      • Unknown hooks
      • Unexpected settings.json entries
      • Suspicious tasks.json commands
  • Review suspicious Git commits
    • Search for commits using:
git log --all --author=claude@users.noreply.github.com
  • Investigate any commit not created through the legitimate Claude Code GitHub App.
  • Revert or force-push only after confirming the commit is unauthorized.
  • Audit npm publishing activity
    • Review recent package releases from your organization.
    • Look for unexpected versions.
    • Pay extra attention to publishes triggered from GitHub Actions runners without a known release approval.
  • Block suspicious outbound traffic
    • Block egress to:
filev2.getsession.org
  • Use DNS-level blocking for getsession.org if Session infrastructure is not required.
  • DNS blocking is more practical than IP blocking because the backend infrastructure is distributed.
  • Enforce package integrity checks
    • Verify @tanstack/* entries in:
      • package-lock.json
      • pnpm-lock.yaml
      • Other lockfiles
    • CI should fail if the package integrity hash does not match the lockfile.
  • Restrict GitHub Actions OIDC permissions
    • Set OIDC to disabled by default:
permissions: id-token: none
  • Grant OIDC only to the exact publishing job that needs it:
permissions: id-token: write
  • Do not rely only on provenance badges
    • Sigstore or provenance badges are not enough by themselves.
    • If an attacker controls the GitHub Actions publishing workflow, they may still generate valid-looking attestations for malicious packages.

Indicators of Compromise (IOCs)

Files

router_init.js / router_runtime.js

  • SHA256 ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
  • SHA1 12ed9a3c1f73617aefdb740480695c04405d7b4b
  • MD5 833fd59ebe66a4449982c6d18db656b4

tanstack_runner.js / router_init.js

  • SHA256 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96
  • SHA1 e7d582b98ca80690883175470e96f703ef6dc497
  • MD5 b82e54923f7e440664d2d75bd31588ca

Network

h[tt]p://filev2[.]getsession[.]org/file/

FAQ: Frequently Asked Questions

Q: Why was this attack so hard to detect with standard tools? A: Because it subverted trust at every level. By using cache poisoning and OIDC token hijacking, the attacker forced the official build system to sign and publish the malware. To an end-user, the package looked identical to a legitimate update, complete with valid SLSA provenance and signatures. It was an attack on the infrastructure of trust.

Q: Does using a lockfile prevent this? A: A lockfile only protects you if you have already pinned a “good” version. If you ran npm install for the first time or ran an npm update during the 24-hour attack window, your lockfile now records the “bad” version as the source of truth for your project. You must manually check the version numbers.

Q: What is the significance of the “Dune” theme? A: TeamPCP uses “Shai-Hulud” (the giant sandworm) as a metaphor for their malware. Like the worm, the malware “burrows” through infrastructure, is difficult to stop once it gains momentum, and “consumes” everything in its path—specifically the “spice” (the credentials). It’s a branding exercise common among elite threat actor groups.

Q: Should I stop using “Trusted Publishing” for npm? A: No. Trusted Publishing (OIDC) is still significantly more secure than long-lived static tokens. This attack proves that we must now move beyond securing the authentication method and focus on securing the build environment (like GitHub Actions runners) and the build cache integrity.

Q: How did the worm bypass MFA? A: It didn’t need to. By hijacking the OIDC token of a running GitHub Actions workflow, the worm inherited the “Trusted” identity of the repository itself. Since the repository is already authorized to publish to npm, no further MFA challenge was triggered.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 12 hours
  • Zero paywalls: Keep the main content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

News

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from The CyberSec Guru

Subscribe now to keep reading and get access to the full archive.

Continue reading