Tech 23 min read

Mini Shai-Hulud hits TanStack & Mistral npm: CVE-2026-45321 (CVSS 9.6), TeamPCP campaign chain

IkesanContents

TL;DR

Status Live tracking. Recent additions: Mandiant counts 1,000+ downstream SaaS environments, GTIG alias UNC6780, CipherForce parallel ransomware operation, intercom-php Packagist cross-ecosystem confirmation

Impact Started May 11, 2026 UTC with 42 TanStack packages / 84 malicious versions, now nearly 200 packages including UiPath (60+). CVE-2026-45321 (CVSS 9.6 / Critical). First observed case of malicious npm packages carrying a valid SLSA Build Level 3 provenance. Downstream credential reuse touches 1,000+ SaaS environments per Mandiant’s count

Attribution Threat group TeamPCP (Google GTIG tracks as UNC6780). Same campaign line as the Aqua Trivy compromise (March 2026), the Bitwarden CLI npm compromise (April 2026), and the Checkmarx Jenkins AST Plugin compromise (May 9, 2026)

Action For any dev machine or CI runner that ran npm install / pnpm install / yarn install with affected versions: network-isolate → kill the gh-token-monitor daemon → then revoke and rotate npm, GitHub, AWS, GCP, Kubernetes, Vault, and SSH credentials. Reversing this order triggers an rm -rf $HOME from the monitor when it detects token revocation. Also inspect ~/.claude/ and .vscode/tasks.json on developer machines

Check Lockfiles, package caches, and CI logs for router_init.js, tanstack_runner.js, @tanstack/setup, unexpected Bun execution, and unexpected npm publish activity. Payload SHA256 ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c, C2 domains api.masscan.cloud / git-tanstack.com, exfil domain *.getsession.org — also worth grepping DNS logs

Secondary risk Vect ransomware (actually a wiper that destroys files larger than 128KB, with no decryption capability) is targeting TeamPCP victims. Plan DR on the assumption that paying the ransom does not get data back


On May 11, 2026 between 19:20 and 19:26 UTC, 84 malicious versions were published across 42 TanStack npm packages.
TanStack’s official postmortem describes the attack as a chain linking pull_request_target, GitHub Actions cache poisoning, and OIDC token extraction from the runner’s memory.

Aikido’s investigation shows this wasn’t limited to TanStack.
They detected 373 malicious package-versions across 169 package names as part of the current Mini Shai-Hulud wave. StepSecurity then reported further expansion past 170 packages, including 50+ UiPath packages and DraftLab. Affected scopes include @tanstack, @mistralai, @uipath, @squawk, and @tallyui.

The threat group is attributed as TeamPCP. According to StepSecurity, the same operators are behind the Aqua Security Trivy scanner compromise (March 2026), the Bitwarden CLI npm compromise (April 2026), and the Checkmarx Jenkins AST Plugin compromise (May 9, 2026) — a continuous campaign line rather than a one-off. The Dune-themed naming for operational branches (fremen, sandworm, harkonnen) carries over from prior waves. The current incident has been assigned CVE-2026-45321 (CVSS 9.6 / Critical).

The blast radius extends beyond React projects using @tanstack/react-router or @tanstack/history — any environment pulling the Mistral SDK in CI is also caught up.
Aikido lists the compromised Mistral versions as @mistralai/mistralai 2.2.2, 2.2.3, 2.2.4, and @mistralai/mistralai-azure / @mistralai/mistralai-gcp 1.7.1 through 1.7.3.
Mistral has also published an official security advisory, stating that there are no signs of compromise on Mistral’s own infrastructure and noting that the PyPI release mistralai==2.4.6 runs a malicious script on import.

TanStack was hit via OIDC abuse on the runner, not npm token theft

The most unsettling part of TanStack’s postmortem is that no npm tokens were stolen.
The release workflow itself didn’t push malicious packages through its defined publish steps either.

Here’s how the attack chain worked.

graph TD
    A["攻撃者がfork PRを作成"] --> B["pull_request_target workflowで<br/>fork側コードが実行"]
    B --> C["pnpm store cacheを汚染"]
    C --> D["mainへの通常pushで<br/>release workflowが起動"]
    D --> E["汚染cacheをrestore"]
    E --> F["runner内でOIDC tokenを抽出"]
    F --> G["registry.npmjs.orgへ直接publish"]
    G --> H["42パッケージ84バージョンが汚染"]

Trusted publishing is designed to remove long-lived npm tokens from GitHub Actions.
It uses GitHub Actions OIDC to issue short-lived publish tokens for npm.
A sound design when the workflow is clean — but when an attacker’s code runs on the runner, that short-lived token gets used on the spot.

Provenance only proves “where the build ran,” not “whether the build was safe.”
The axios incident where a maintainer was compromised via RAT was about seizing a legitimate maintainer’s machine, making 2FA and local publishing the weak points.
This time, the legitimate release workflow’s permissions were directly consumed by malicious code on the runner.
In both cases, the “trusted path” collapses the moment it falls into the attacker’s hands.

How the OIDC token was actually lifted is fairly hands-on. Cross-referencing Aikido, Socket, and StepSecurity’s analyses, the payload reads /proc/<pid>/mem directly and scans the GitHub Actions runner process heap for JSON objects shaped like {"value":"...","isSecret":true}. That pulls out both masked secrets and the raw OIDC JWT in one pass. GitHub Actions log masking (the *** substitution) only affects log output — the raw values still sit in process memory, and that’s the gap being exploited.

Valid SLSA provenance doesn’t make the package safe

The compromised TanStack versions carry a properly signed SLSA Build Level 3 provenance attestation. StepSecurity describes this as “the first documented case of a validly-attested malicious npm package.”

Provenance cryptographically guarantees that “this artifact was produced from this public repo’s specified workflow, using this commit as input.” That guarantee held in this case too — the malicious versions were published by TanStack’s legitimate GitHub Actions workflow, starting from a legitimate commit, through the registered pipeline path. The artifacts were still malicious.

Provenance only tells you which pipeline built the artifact, not whether the pipeline behaved as intended. With fork-PR cache poisoning landing earlier in the chain, the workflow definition stayed healthy while only its internal state was corrupted.

The practical takeaway for consumers is to stop treating provenance verification as a sufficient trust boundary. Concretely: don’t read “SLSA signature OK” on a dependency as “safe”; add post-publish monitoring right after new releases (new lifecycle scripts, tarball file diffs, Git references in optional dependencies) for time-shifted detection; and on the publishing side, audit the combination of pull_request_target with branch/workflow pinning when using trusted publishing. Only at that depth does this attack surface actually close.

Malicious packages pull their payload from GitHub via optional dependencies

The compromised TanStack packages had router_init.js injected at the root.
Additionally, package.json had an optional dependency pointing to a specific commit on GitHub.

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

This Git dependency has a prepare script that runs bun run tanstack_runner.js && exit 1.
Since Git dependency lifecycle scripts execute at install time, the payload runs during what looks like normal dependency resolution.

The trailing exit 1 isn’t sloppy — it’s deliberate.
Because this is an optional dependency, failure doesn’t necessarily stop the overall install.
The payload runs first, then the failure is absorbed as just an optional dependency that didn’t work out.

This is the same attack surface as the fake Strapi plugins that used postinstall.js for immediate execution — npm lifecycle scripts remain a reliable entry point.
The difference is that this time it wasn’t a fake package; it was injected through a path close to the legitimate release pipeline.

The exfiltration targets assume a CI runner environment

Aikido’s analysis shows the payload targets CI environments specifically, not just developer machines.
It goes after more than npm and GitHub tokens — AWS metadata, GCP metadata, Kubernetes service account tokens, Vault tokens, environment variables, and secrets on the local filesystem are all in scope.

This kind of worm turns compromised environments into the next publish origin.
It uses stolen npm and GitHub credentials to find packages the victim can publish, modifies the package archive, bumps the version, and republishes.
SANDWORM_MODE also centered on worm propagation via npm tokens, GitHub tokens, and SSH.
Mini Shai-Hulud appears to layer GitHub Actions OIDC and trusted publishing abuse on top of that.

Traces tend to show up in lockfiles and CI logs.
If you see @tanstack/setup, github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c, router_init.js, router_runtime.js, tanstack_runner.js, or bun run tanstack_runner.js, treat that environment as compromised.
In CI logs, look for unexpected Bun execution during install, optional dependency failures, unexpected OIDC token requests, and unexpected npm publish activity.

Concrete IoCs are also out in the open. The SHA256 of router_init.js is ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c. C2 endpoints are api.masscan.cloud and git-tanstack.com, and exfiltration uses subdomains under *.getsession.org. The attacker’s GitHub account is voicproducoes (created 2026-03-19), and the malicious auto-commits are signed with the spoofed address claude@users.noreply.github.com — that address is not an Anthropic-official identity, so if it shows up in repo commit history, treat it as a compromise signal. Blocking DNS queries for getsession.org is a reasonable short-term detection and containment move.

.claude/ and .vscode/ persistence on developer machines

The uncomfortable part of Socket’s and StepSecurity’s findings is that the payload doesn’t stop at the CI runner. When npm install runs on a developer machine, it installs an independent persistence layer locally.

Observed write targets include:

  • ~/.claude/router_runtime.js and rewrites to ~/.claude/settings.json hook definitions
  • An auto-launch task added to .vscode/tasks.json directly under the project

A .vscode/tasks.json injection means the attacker’s task fires the moment that folder is opened in VS Code. A hook rewrite in .claude/settings.json means the payload runs every time Claude Code starts. Neither survives only at the npm layer — they remain after npm uninstall or even a node_modules reinstall.

For triage on a potentially affected developer machine, look at:

  • ~/.claude/ for an unfamiliar router_runtime.js or a tampered settings.json
  • The project’s .vscode/tasks.json for unfamiliar tasks
  • VS Code “automatically open this folder” history, if it’s enabled for the affected project

If anything turns up, treat that machine as compromised and proceed to the full credential rotation TanStack’s postmortem prescribes.

Follow-up: PyTorch Lightning, SAP-targeting npm, fake tanstack

Aikido’s follow-up coverage shows that Mini Shai-Hulud doesn’t end with TanStack and Mistral. Spread has been observed in PyTorch Lightning on PyPI, npm packages targeting SAP, and fake tanstack-named packages.

The PyTorch Lightning report confirms Mini Shai-Hulud-style malicious code in PyPI’s lightning package, versions 2.6.2 and 2.6.3 — note that lightning is Lightning.ai’s current unified package name, not the legacy pytorch-lightning.
If you’re only looking at npm you’ll miss this, but the same “grab secrets at install time” pattern needs to be checked on the Python side too. ML CI in particular tends to carry cloud credentials, model-distribution tokens, and experiment-tracking API keys, so don’t scope the impact assessment to JavaScript projects only.

Another Aikido write-up covers SAP-targeting npm packages hit with a Bun-based preinstall payload.
They steal GitHub, npm, cloud, and CI secrets and propagate via OhNoWhatsGoingOnWithGitHub. Same as the router_init.js + optional dependency pattern in this post, lifecycle scripts during dependency resolution should be treated as the entry point.

There’s also a fake tanstack package incident where four versions were published in 27 minutes, exfiltrating .env via postinstall.
That’s a separate operation from the genuine TanStack compromise, but the failure mode is collateral: developers reading the news search the name and pull in the typosquat. Audit not only @tanstack/* in your lockfiles but also bare tanstack-style imitation names.

PyPI side adds Mistral SDK and guardrails-ai

As of May 12, 2026, PyPI’s mistralai==2.4.6 and guardrails-ai==0.10.1 are both in quarantine. StepSecurity’s Mini Shai-Hulud list now includes both packages.

If you only watch npm’s @mistralai/*, you’ll miss ML pipelines that pull the Python Mistral SDK via pip install. Combined with PyTorch Lightning 2.6.2/2.6.3 noted earlier, the PyPI side now covers three compromised packages.

guardrails-ai is a library for guarding LLM outputs and is likely sitting in AI-focused CI. If you recently added mistralai or guardrails-ai, rotate secrets to the same standard as the npm side — cloud credentials, HuggingFace tokens, and any API keys reachable during install.

On the npm side, @opensearch-project/opensearch@3.6.2 has also been added to StepSecurity’s compromised list. Beyond TanStack, UiPath, and Mistral, the search-infrastructure Node client family is in scope too — one extra line for the checklist.

Expansion as of May 13, 2026

The blast radius kept widening through May 12 and 13. Combining Wiz’s follow-up with The Hacker News coverage (citing OX Security), the npm side alone is closing in on 200 compromised packages, with 518M cumulative downloads and 400+ attacker-created GitHub repositories used to store exfiltrated credentials.

StepSecurity’s report also places intercom-client@7.0.4 (published April 30, 2026) as an earlier TeamPCP victim. The Mini Shai-Hulud “first move” was an Intercom client npm package rather than TanStack. The sequence reads as Trivy (March 2026), Bitwarden CLI (April 2026), intercom-client (April 30, 2026), Checkmarx Jenkins AST Plugin (May 9, 2026), then the TanStack wave (May 11, 2026).

The Intercom wave also compromised the Packagist package intercom-php at the same time, as summarized in SANS ISC Weekly W18. That is the first observed worm propagation crossing npm → PyPI → Packagist in production. “Don’t scope this to JavaScript projects” extends beyond ML-focused PyPI to PHP projects’ composer install path. Laravel or Symfony pipelines that pull intercom/intercom-php need the same rotation discipline as the npm side.

New scope and version details confirmed as of May 13:

  • @uipath: 50+ → 60+ packages
  • @draftauth scope added, separate from the previously-noted @draftlab
  • @opensearch-project/opensearch: Wiz tracks the affected range as 3.5.3 through 3.8.0, wider than StepSecurity’s initial single-version note of 3.6.2

For exfiltration, beyond *.getsession.org domains, the campaign is now observed using the Session messenger network itself. Session is a decentralized, encrypted messenger with no central server, so DNS blocks or single-domain takedowns don’t fully stop the channel. Short-term, blocking DNS queries for getsession.org still helps, but expect domain rotation while the same Session backbone keeps serving as the next window.

TeamPCP and the prior Trivy / Bitwarden CLI / Checkmarx Jenkins compromises

StepSecurity’s report frames this wave as one chapter in a continuous campaign by the group signing itself as “TeamPCP.” The same operators reportedly compromised Aqua Security’s Trivy scanner in March 2026 and Bitwarden CLI’s npm distribution in April 2026. Palo Alto Networks Unit 42’s campaign analysis lines up Trivy → KICS → LiteLLM → VS Code extensions → Bitwarden → SAP-targeting npm → TanStack/Mistral as a single operation, framed under “Weaponizing the Protectors” — the supply chains of security tooling are being systematically targeted.

What ties the playbooks together is that the targets themselves are legitimate maintainers’ legitimate release paths — the operators slip through holes in CI/CD configuration (fork PRs, caches, OIDC, trusted publishing) and ride the legitimate identity all the way to distribution. The attack surface is the boundary design of the CI/CD pipeline, not the OS or the application itself.

The practical implication: unless your repo’s pull_request_target, cache scope, id-token: write scope, third-party action SHA pinning, and trusted publishing branch/workflow pinning are at least as tight as TanStack’s, you’re exposed to the same technique. The group rotates targets while reusing the same template, so “we don’t use TanStack, doesn’t apply to us” doesn’t hold.

Checkmarx Jenkins AST Plugin compromise (May 9, 2026)

Two days before the TanStack wave, on May 9, 2026, Checkmarx’s Jenkins AST plugin version 2026.5.09 was compromised by TeamPCP, as reported by The Hacker News, The Register, and SecurityWeek. The attacker also renamed the AST plugin listing page on repo.jenkins-ci.org to Checkmarx-Fully-Hacked-by-TeamPCP-and-Their-Customers-Should-Cancel-Now and altered the description text. Any Jenkins instance that pulled the affected version during the exposure window should be treated as compromised.

The last known clean version was 2.0.13-829.vc72453fa_1c16 from December 2025. The tone of Checkmarx’s official notice is that any secret visible to the Jenkins runner — GitHub tokens, AWS/GCP/Azure credentials, Kubernetes configs, SSH keys, API keys in environment variables — should be considered exposed.

In timeline terms, this slots in between the TanStack wave and the earlier intercom-client compromise, and is easy to miss if you only watch the TanStack story.

Mandiant counts 1,000+ downstream SaaS environments and names victims

Downstream breaches that reuse credentials harvested from TeamPCP’s primary compromises (Trivy, LiteLLM, Bitwarden, SAP, TanStack and other CI/CD paths) reach 1,000+ SaaS environments per Mandiant’s count (SANS ISC Update 006). Beyond Guesty, USHA International, and S&P Global from the Vect partnership listed in the next section, named downstream victims observed so far include:

OrganizationPath / scaleSource
CiscoSource code stolen via the Trivy scanner compromiseSANS ISC Update 007
European CommissionCERT-EU confirms a cloud breachUpdate 006
SportradarSports data provider; multiple customer downstreams affectedUpdate 006
DatabricksMajor cloud platform under investigation as the first downstream of its sizeSANS ISC Update 004
MercorAI talent marketplace confirms a supply-chain-originated leakAviatrix Threat Research

Google GTIG now tracks TeamPCP as UNC6780. When correlating reports across vendors, watch all four labels — TeamPCP / UNC6780 / Vect affiliate / BreachForums thread — to avoid losing channel-specific coverage of the same operation.

The practical implication shifts the question from “did I install a compromised package?” to “do I rely on a SaaS reachable from credentials TeamPCP stole?” An organization that uses neither TanStack nor Mistral can still sit inside Mandiant’s 1,000+ SaaS sample if it ran Trivy, Checkmarx KICS, Bitwarden CLI, LiteLLM, SAP-targeting npm, or the Checkmarx Jenkins AST Plugin through CI in the last six months. The inventory expands from “dependencies I pulled” to “SaaS reachable via stolen credentials.”

Vect ransomware partnership: secondary damage phase

On April 15, 2026, the Vect ransomware group announced on a public forum that it was targeting organizations TeamPCP had previously breached, and named the property-management SaaS Guesty as the first victim, exfiltrated via the Trivy/LiteLLM campaign (Halcyon Ransomware Alerts, Industrial Cyber, Socket). They claim approximately 4 million emails and 700GB of data exfiltrated.

Named victims listed since:

  • Guesty (property-management SaaS, ~700GB from the Trivy/LiteLLM campaign)
  • USHA International (Indian manufacturing, employee data and SAP databases)
  • S&P Global (claimed publicly, unconfirmed by third parties)

Three organizations so far. The shape is: TeamPCP exfiltrates CI/CD, cloud, and SaaS secrets in the first stage, and Vect then targets those organizations in the second.

Worth noting separately: TeamPCP also runs its own ransomware operation, CipherForce, in parallel with the Vect partnership (SANS ISC Update 004). If Vect is “the RaaS funnel pulling in the BreachForums affiliate pool (roughly 300,000 registered users),” CipherForce is “TeamPCP’s in-house independent operation.” Both run downstream from the same primary breaches at the same time. From a victim’s perspective, there isn’t a single counterparty to negotiate with, so “paying Vect ends it” isn’t sufficient — separate-channel data publication or destruction has to be in the threat model as well.

The operationally dangerous part: while Vect calls itself “ransomware,” the implementation is a wiper and there is no decryption functionality. Independent confirmations from Check Point Research, The Register, Halcyon, and CSA Lab Space show Vect destroys any file larger than 128KB. VM disks, databases, documents, backups — virtually every file with real content exceeds that threshold, so paying the ransom does not bring the data back.

Any organization that ran npm install against the TanStack wave needs both of these in its DR plan and IR runbook:

  • “Do not start with token revoke” (the gh-token-monitor wipes the host)
  • “Do not treat ransom payment as a recovery option” (Vect destroys rather than encrypts)

That means strengthening the “restore from backup” assumption, increasing offline-stored and immutable snapshots, and similar measures.

Token-revocation wipe and geofenced destruction

This is the most operationally dangerous piece of the campaign. The payload carries a dead man’s switch that runs rm -rf $HOME when it detects token revocation. Confirmed in Semgrep’s analysis and a DevOps Daily breakdown.

The payload installs a persistent daemon named gh-token-monitor:

  • Linux: ~/.config/systemd/user/gh-token-monitor.service plus ~/.local/bin/gh-token-monitor.sh
  • macOS: ~/Library/LaunchAgents/com.user.gh-token-monitor

It polls GitHub’s API every 60 seconds with the stolen token. When the response is HTTP 40x (token revoked), it executes the rm -rf "$HOME" handler stored at ${CONFIG_DIR}/handler. Semgrep’s analysis cites the unambiguous string IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner inside the payload. If untriggered for 24 hours, the daemon exits cleanly, limiting its forensic footprint.

Separately, Upwind’s analysis describes a geofenced destruction path where a roulette.py module probabilistically runs rm -rf / when the host appears to be in Israel or Iran. The exact probability isn’t disclosed, but Upwind notes the randomization is deliberate — making destructive behavior harder to correlate across victims.

The practical implication is sharp: the textbook “revoke tokens first” incident response sequence is a self-detonation against this attack. The order has to be inverted:

  1. Network-isolate the host (cuts C2 traffic and the token-validation pings)
  2. Stop and remove gh-token-monitor services from ~/.config/systemd/user/ and ~/Library/LaunchAgents/ (systemctl --user stop / launchctl unload)
  3. Remove residual payload files like ${CONFIG_DIR}/handler
  4. Only then revoke and rotate npm, GitHub, AWS, GCP, Kubernetes, Vault, and SSH credentials
  5. Rebuild the host from clean install — including pulling out the .claude/ and .vscode/ persistence — rather than cleaning in place

CI runners are easier when they’re ephemeral. Self-hosted long-lived runners need the same ordering, or the $HOME of the runner host gets caught in the same wipe.

Upgrading the version isn’t enough

Even after unpublishing or deprecating the compromised versions, machines and runners that already installed them are a separate problem.
TanStack’s postmortem recommends rotating credentials for AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH on any environment that installed the affected versions on May 11, 2026.

Checking only the lockfile misses CI ephemeral environments and caches.
GitHub Actions caches, npm/pnpm/yarn package caches, self-hosted runner workspaces, artifacts, and recent publish history all need review.
Self-hosted runners in particular aren’t ephemeral — recreating the runner from scratch becomes a realistic option.

TanStack has since deleted all cache entries, restructured the pull_request_target workflow, added a repository owner guard, and pinned third-party actions by SHA.
For your own repos, the starting point is checking whether fork PR code gets checked out and built under pull_request_target, whether caches are stored on the base repo side, and whether id-token: write is scoped too broadly.


Primary and near-primary sources include TanStack’s official Postmortem, Aikido’s Mini Shai-Hulud Is Back, Socket’s analysis, StepSecurity’s report, Palo Alto Networks Unit 42’s TeamPCP campaign analysis, Snyk’s post, and Endor Labs’ post.
On the Vect ransomware partnership: Halcyon, Industrial Cyber, Socket. On Vect’s actual behavior: Check Point Research.
Downstream victims, the CipherForce parallel operation, the UNC6780 alias, and the three-ecosystem Packagist crossover are tracked across SANS ISC’s ongoing TeamPCP coverage (Update 004 / Update 006 / Update 007 / Weekly W18) and the Aviatrix threat research on the Mercor case.
Package lists and IoCs are still moving, so for final confirmation, the Affected Packages And Versions / IoC sections in each vendor’s article are the fastest reference.