ZeroPath at Black Hat USA 2026

TanStack npm Supply Chain Compromise (CVE-2026-45321): Overview of a Chained CI/CD Attack That Published 84 Malicious Packages

A brief summary of CVE-2026-45321, where attackers chained GitHub Actions misconfigurations, cache poisoning, and OIDC token extraction to publish credential-stealing malware across 42 TanStack npm packages. Includes detection methods and indicators of compromise.

CVE Analysis

12 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-05-11

TanStack npm Supply Chain Compromise (CVE-2026-45321): Overview of a Chained CI/CD Attack That Published 84 Malicious Packages
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

On May 11, 2026, an attacker published 84 malicious package versions across 42 @tanstack/* npm packages in under six minutes, distributing credential-stealing malware through one of the JavaScript ecosystem's most widely used library families. The compromise, tracked as CVE-2026-45321 with a CVSS score of 9.6, is notable not for a single novel technique but for the precise chaining of three well-documented vulnerability classes into a single attack that bypassed OIDC-based trusted publishing, SLSA provenance, and npm audit signatures simultaneously.

TanStack provides routing, data fetching, table rendering, and form management libraries primarily for React and Solid applications. The project's packages receive millions of weekly downloads, and its router library is a foundational dependency in a large number of production web applications. That reach made TanStack an attractive target for supply chain attackers seeking maximum downstream impact.

Technical Information

The attack methodology relied on chaining three distinct vulnerability classes to bypass standard security controls and publish malware under a trusted identity. We will walk through each phase in sequence.

Phase 1: Pwn Request via pull_request_target

The attacker exploited a pull_request_target trigger in the bundle-size.yml workflow within the TanStack/router repository. The pull_request_target event is a well-documented footgun in GitHub Actions: it runs the workflow definition from the base branch but in the context of the base repository, with access to repository secrets and write permissions. If the workflow checks out and executes code from the pull request head, untrusted code runs with elevated privileges.

The attacker created a fork of TanStack/router on May 10, 2026 at 17:16 UTC. The fork was renamed to zblgg/configuration to evade fork-list searches. On May 11 at 11:11 UTC, the attacker force-pushed a malicious commit onto the pull request head. When the bundle-size.yml workflow triggered, it checked out and ran the attacker's code in the privileged base repository context.

Phase 2: Cache Poisoning Across the Fork/Base Trust Boundary

During execution of the untrusted code, the malware wrote a 1.1 GB payload into the pnpm store directory. The GitHub Actions cache mechanism then saved this poisoned store under a deterministic key:

Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11

This key was crafted to match the cache key that the legitimate release workflow would later attempt to restore. The poisoned cache entry was saved at 11:29 UTC, staging the malicious payload for the next phase.

Phase 3: OIDC Token Extraction from Runner Memory

When a legitimate maintainer merged a separate pull request later that day, the release workflow triggered and restored the poisoned cache. The attacker-controlled binaries, now present in the pnpm store, executed during the build process. The payload located the GitHub Actions Runner.Worker process and read its memory via /proc/{pid}/mem to extract the OIDC token.

This token was then used to authenticate direct POST requests to the npm registry, bypassing the workflow's own publish step entirely. Because the token was minted by the legitimate runner in the legitimate repository, the resulting packages carried valid SLSA Build Level 3 provenance attestations. Running npm audit signatures would not flag them.

Payload Architecture

The malware used a dual payload delivery system:

  1. An optionalDependencies entry was injected into each compromised package's package.json, pointing to a fictitious @tanstack/setup package hosted via a GitHub commit reference. This package's prepare script silently downloaded a Bun binary.

  2. A 2.3 MB obfuscated file called router_init.js was planted at the root of every compromised tarball. This primary payload gathered environment variables to detect CI pipeline execution contexts, then exfiltrated secrets to attacker-controlled infrastructure at filev2.getsession.org and seed1.getsession.org.

Attack Timeline

Time (UTC)Event
2026-05-10 17:16Attacker creates a fork of TanStack/router
2026-05-11 11:11Force push lands malicious commit on the pull request head
2026-05-11 11:29Poisoned cache entry saved to GitHub Actions
2026-05-11 19:20First wave of malicious packages published to npm
2026-05-11 19:26Second wave of malicious packages published to npm
2026-05-11 19:50External researcher reports the compromise

Each of the 42 compromised packages received exactly two malicious versions, published roughly six minutes apart.

Detection Methods

This supply chain compromise leaves a rich trail of indicators across package manifests, file systems, network traffic, CI/CD logs, and developer workstations. Detection breaks down into several complementary approaches.

Manifest and Lockfile Inspection

The single most reliable static indicator is an injected optionalDependencies entry in any @tanstack/* package's package.json pointing to a fork commit:

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

This entry should never appear in legitimate TanStack packages. Any @tanstack/* dependency that references a github: URL with a pinned commit hash is a strong compromise indicator. To check your own projects:

# npm grep "@tanstack/" package-lock.json | grep -v node_modules # pnpm grep "@tanstack/" pnpm-lock.yaml # yarn grep "@tanstack/" yarn.lock # Also check for non-TanStack propagation targets grep -E "(draftlab|draftauth|taskflow-corp|tolka)" package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null

Cross-reference any resolved versions against the affected version lists published in GHSA-g7cv-rxg3-hmpx.

File System Artifacts

A ~2.3 MB file called router_init.js was planted at the root of every compromised tarball. The package's files field only declares ["dist", "src"], so the presence of router_init.js at the package root is itself anomalous:

find node_modules -name "router_init.js" -type f 2>/dev/null grep -r "@tanstack/setup" node_modules/*/package.json 2>/dev/null

The payload hashes (SHA-256) are confirmed across all compromised packages:

FileSHA-256
router_init.jsab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
tanstack_runner.js2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96
@tanstack/setup package.json7c12d8614c624c70d6dd6fc2ee289332474abaa38f70ebe2cdef064923ca3a9b

A useful heuristic: compromised tarballs are approximately 905 KB compared to ~190 KB for clean versions, a nearly 5x increase. Any SCA tool or registry proxy that tracks tarball sizes over time would flag this jump.

Network Indicators of Compromise

The malware uses two exfiltration channels plus cloud metadata and second-stage download endpoints. Monitor or block the following:

C2 and exfiltration domains: filev2.getsession.org, seed1.getsession.org, seed2.getsession.org, seed3.getsession.org, api.masscan.cloud, git-tanstack.com

Second-stage payload URLs: https://litter.catbox.moe/h8nc9u.js, https://litter.catbox.moe/7rrc6l.mjs

Cloud metadata targets: 169.254.169.254 (AWS EC2 IMDS), 169.254.170.2 (ECS/Fargate task metadata), 127.0.0.1:8200 (local HashiCorp Vault)

Outbound HTTPS connections to filev2.getsession.org or api.masscan.cloud during npm install or build steps are a definitive sign the payload executed. Note that the Session Protocol CDN (getsession.org) is a legitimate privacy-focused service being abused, so its domains may not appear on standard threat intelligence blocklists. Defenders should add them explicitly for this campaign.

The malware also pins against a specific TLS certificate: CN=seed1.getsession.org, O=Oxen Privacy Tech Foundation (Melbourne, AU, valid until 2033). TLS inspection tools can use this certificate fingerprint as an additional detection signal.

CI/CD Runtime Detection

In GitHub Actions environments, the payload runs a Python script that scrapes the Runner.Worker process memory via /proc/{pid}/mem to extract every secret, including masked ones. Detection signals include:

  • Unexpected python3 processes reading /proc/*/mem during CI runs
  • Process command lines showing Runner.Worker being targeted by another process
  • Execution of bun run tanstack_runner.js in CI process logs (the payload requires the Bun runtime, which is not standard in most CI environments)

The attacker's workflow runs that performed the malicious publishes are:

  • https://github.com/TanStack/router/actions/runs/25613093674 (attempt 4)
  • https://github.com/TanStack/router/actions/runs/25691781302

A secondary detection signal is the double-tap publishing pattern: each of the 42 compromised packages received exactly two malicious versions, published roughly six minutes apart. Publish-rate alerting (two patch bumps for the same package in under 10 minutes) would catch this.

Persistence Artifacts on Developer Machines

If the malware executed on a developer machine (via npm install), it drops several persistence mechanisms:

  • .claude/settings.json with a SessionStart hook that re-executes the malware on every Claude Code session
  • .vscode/tasks.json with a folderOpen task that re-executes the malware on every VS Code workspace open
  • .claude/router_runtime.js, .claude/setup.mjs, .vscode/setup.mjs as dropped payload and setup scripts
  • macOS: ~/Library/LaunchAgents/com.user.gh-token-monitor.plist for persistent GitHub token monitoring
  • Linux: ~/.config/systemd/user/gh-token-monitor.service performing the same function
  • .github/workflows/codeql_analysis.yml as an injected workflow disguised as CodeQL that exfiltrates all repository secrets on push or deployment events using ${{ toJSON(secrets) }}

Campaign-Specific Markers

The attacker leaves distinctive fingerprints attributed to the "Mini Shai-Hulud" campaign (Dune-themed):

  • Git commits authored by [email protected] with message chore: update dependencies (dead-drop commits for encrypted exfiltration data)
  • Branch names matching dependabot/github_actions/format/{dune-word} (e.g., atreides, fremen, sandworm, harkonnen, melange), designed to mimic legitimate Dependabot branches
  • npm tokens with the description IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner. Important: do not revoke such tokens before forensically imaging the affected machine, as the payload reportedly includes a destructive wipe routine triggered by revocation
  • Worm marker repositories: siridar-ghola-567, tleilaxu-ornithopter-43, described as "A Mini Shai-Hulud has Appeared"

Token Pattern Matching

The deobfuscated payload contains regex patterns it uses to scan memory and files for credentials. These same patterns can be used defensively:

  • npm tokens: npm_[A-Za-z0-9]{36,}
  • GitHub tokens: gh[op]_[A-Za-z0-9]{36}
  • Vault tokens: hvs\.[A-Za-z0-9_-]{24,}
  • Kubernetes tokens: eyJhbGciOiJSUzI1NiIsImtpZCI6[\w\-.]+
  • AWS access keys: AKIA[0-9A-Z]{16}

SLSA Provenance Caveat

A critical detection lesson: the compromised packages carry valid SLSA Build Level 3 provenance attestations. Running npm audit signatures will not flag them because the attacker minted a legitimate OIDC token from the runner's own memory and used it with the Sigstore stack (Fulcio and Rekor) to generate valid attestations. Provenance confirms which pipeline produced the artifact, not whether the pipeline was behaving as intended. Defenders should not rely solely on SLSA provenance as a safety guarantee.

Attacker Infrastructure

Indicator TypeValue
GitHub accountszblgg (ID: 127806521), voicproducoes (ID: 269549300)
Attacker forkzblgg/configuration (renamed fork of TanStack/router)
Secondary forkvoicproducoes/router
Orphan payload commit79ac49eedf774dd4b0cfa308722bc463cfe5885c
Malicious commit in fork65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14
Forged commit identityclaude <[email protected]>
Fictitious package@tanstack/setup
Payload filenamesrouter_init.js, tanstack_runner.js
Exfiltration domainsfilev2.getsession.org, seed1.getsession.org
Poisoned cache keyLinux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11

Affected Systems and Versions

The compromise affected 42 distinct @tanstack/* packages on the npm registry. Each affected package received exactly two malicious versions, for a total of 84 malicious versions published between 19:20 and 19:26 UTC on May 11, 2026. The complete list of affected packages and specific version numbers is published in the security advisory GHSA-g7cv-rxg3-hmpx.

Any version of an @tanstack/* package installed or updated after 19:20 UTC on May 11, 2026 and before the affected versions were pulled should be treated as potentially compromised. Versions published before 19:00 UTC on May 11, 2026 are confirmed clean.

The following package families have been confirmed clean and were not affected: @tanstack/query, @tanstack/table, @tanstack/form, @tanstack/virtual, @tanstack/store, and the @tanstack/start meta package.

Organizations running CI/CD pipelines that installed affected packages during the compromise window should treat their environments as breached, as the malware was specifically designed to exfiltrate CI/CD secrets, cloud provider credentials, GitHub tokens, and SSH keys.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Jira
  • Linear
  • Slack
  • Security Compass
Security magnifying glass visualization