Cremit
/incidentsfield log
CatchesCampaignsExfilPatternsLLMIncidentsMethodology
↺rss↗cremit.io

incidents.cremit.io

A reference feed of real-world Non-Human Identity (NHI) credential leak incidents. Maintained by Cremit.

Browse

  • All incidents
  • npm supply chain
  • CI/CD compromise
  • Methodology

Subscribe

  • RSS feed
  • @cremit_io
  • GitHub
// status
monitor active
// build
2026-05-20
// origin
cremit · seoul, kr
// license
CC BY 4.0

© 2026 Cremit. content reuse encouraged with attribution.

home/incidents/tanstack-mini-shai-hulud-npm-worm-2026
CRITICAL9.5·confirmed·disclosed May 12, 2026·10 min read

Mini Shai-Hulud npm Worm: TanStack, UiPath, Mistral AI and 169 Packages Compromised (May 2026)

npm worm hit 373 versions across 169 packages (@tanstack, @squawk, @uipath, mistralai) via trusted-publishing OIDC abuse and a prepare-script git dep that exfiltrates cloud and registry secrets at install.

Summary

On May 12, 2026, Aikido Security disclosed a self-propagating npm worm — termed "Mini Shai-Hulud" after an earlier April 2026 SAP-themed wave — that compromised 373 versions across 169 packages. Major clusters include @tanstack/* (83 versions), @squawk/* (87), @uipath/* (66), @tallyui/* (30), mistralai / mistralai-gcp / mistralai-azure, and 39 unscoped packages such as safe-action, ts-dna, cross-stitch, cmux-agent-mcp, and nextmove-mcp.

Unlike conventional install-script malware, the worm publishes packages that look entirely clean on inspection — no preinstall/install/postinstall hooks, no malicious code in the published tarball. Instead, it injects a git-URL optionalDependencies entry that npm installs by cloning the referenced commit and running its prepare script. The cloned repo executes tanstack_runner.js, which harvests GitHub tokens (PATs, App tokens, Actions OIDC), npm publish tokens, AWS instance and ECS credentials, Kubernetes service account material, HashiCorp Vault tokens, and broad environment-variable secrets, then exfiltrates everything to filev2.getsession.org. The payload uses the stolen npm credentials to find more packages the victim can publish, inject the same malicious dependency, bump versions, and propagate — the worm shape that gives this campaign its name.

Timeline

  • 2026-04-XX — Precursor "Mini Shai-Hulud" cluster targeting SAP-themed npm packages observed by Aikido. Same payload family, smaller blast surface.
  • 2026-05-07 02:38 UTC — Earliest compromised version captured in npm RSS: @squawk/mcp@0.9.0 (publisher neilcochran).
  • 2026-05-07 01:14 → 17:43 UTC — @uipath/apollo-react and @uipath/apollo-wind versions published from publisher cristiancalina and vnaren23.
  • 2026-05-08 03:59 → 04:42 UTC — @tanstack/redact versions 0.0.8 → 0.0.10 (publisher tannerlinsley).
  • 2026-05-08 14:28 UTC — First mass batch of @tanstack/form-* 1.29.2 across 13 packages (form-core, react-form, vue-form, svelte-form, solid-form, lit-form, angular-form, react-form-nextjs, react-form-remix, react-form-start, react-form-devtools, form-devtools, solid-form-devtools), published in the same minute by GitHub Actions. Two more batches at 16:04 (1.29.3) and 17:36 / 19:39 / 20:53 the same day.
  • 2026-05-09 15:26 UTC — @tanstack/form-* 1.30.0 batch. @tanstack/lit-store@0.0.1 and 0.13.2 published.
  • 2026-05-10 22:35 UTC — @tanstack/form-* 1.32.0 batch.
  • 2026-05-11 — Latest observed publishes in the compromised cluster.
  • 2026-05-12 — Aikido publishes the writeup. Packages remain available on the npm registry at the time of writing. npm Trust & Safety, GitHub Security Lab, and affected maintainers are aware.

Attack Vector

Conventional npm install-script malware hooks preinstall, install, or postinstall. Static scanners and most heuristic detectors trigger on the presence of those fields in package.json. The Mini Shai-Hulud worm engineers its way around that detector entirely:

  1. The published tarball contains no install scripts. The victim package's package.json has empty or unchanged scripts. Anything scanning published-tarball metadata sees a "boring" release.

  2. The compromised tarball does ship an obfuscated payload file — router_init.js (SHA-256 ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c) — but by itself it does nothing: npm doesn't execute arbitrary files during install.

  3. The trigger is added as a git-URL dependency under optionalDependencies:

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

    When npm resolves the dependency, it clones the referenced commit and runs npm install inside the cloned repo.

  4. The cloned git repo defines a prepare script. prepare runs unconditionally on git-URL dependency installs. The script is bun run tanstack_runner.js && exit 1.

  5. tanstack_runner.js is the real payload (SHA-256 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96). Hosting it in the git repo, not the tarball, keeps it invisible to anything that only scans the npm artifact.

  6. The trailing && exit 1 intentionally fails the install so operators retry or investigate — by which point the payload has already exfiltrated secrets and started worm propagation.

The entry vector — how the attacker got publish rights — is trusted publishing abuse. Modern npm allows GitHub Actions workflows with id-token: write to exchange an OIDC token for a short-lived npm publish token, bypassing 2FA. Compromising or replaying a single OIDC token from any workflow with publish access mints a fresh npm token usable across whatever the workflow can publish. The cluster of @tanstack/form-* releases on May 8–10, all from publisher GitHub Actions in tight time windows, are consistent with that path: the legitimate maintainer accounts (tannerlinsley, kevinvandy, lachlancollins) are not implicated.

Tokens & Credentials Exposed

The tanstack_runner.js payload targets, based on observed network endpoints and environment-variable enumeration:

  • GitHub credentials — PATs from environment, GitHub App tokens, and GitHub Actions OIDC tokens obtained via $ACTIONS_ID_TOKEN_REQUEST_URL / $ACTIONS_ID_TOKEN_REQUEST_TOKEN.
  • npm tokens — $NPM_TOKEN, .npmrc contents, and freshly-minted trusted-publishing tokens via https://registry.npmjs.org/-/npm/v1/tokens.
  • AWS credentials — IAM access keys and session tokens from environment, plus EC2 instance metadata at http://169.254.169.254/latest/meta-data/iam/security-credentials/ and ECS container credentials at http://169.254.170.2.
  • Kubernetes — service account token and CA bundle at /var/run/secrets/kubernetes.io/serviceaccount/.
  • HashiCorp Vault — VAULT_TOKEN / VAULT_ADDR from environment, plus discovery of in-cluster Vault at vault.svc.cluster.local:8200.
  • Filesystem and environment secrets — broad sweep for names matching common secret-naming patterns across the host's environment.

Exfiltration is HTTP POST to http://filev2.getsession.org/file/. Session is an encrypted-messenger network whose file servers do not log uploads — once posted, the data is unrecoverable by defenders and unattributable to any operator.

Indicators of Compromise

File hashes:

  • router_init.js — ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
  • tanstack_runner.js — 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96
  • router_runtime.js (companion file; hash not yet published)

Network endpoints:

  • hxxp://filev2[.]getsession[.]org/file/ — exfiltration
  • hxxp://169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/ — AWS IMDS
  • hxxp://169[.]254[.]170[.]2 — AWS ECS task metadata
  • hxxps://registry[.]npmjs[.]org/-/npm/v1/tokens — npm token mint (legitimate endpoint abused)
  • vault[.]svc[.]cluster[.]local:8200 — in-cluster Vault

Campaign strings: "A Mini Shai-Hulud has Appeared", Dune-themed repository / branch names.

Package cluster (snapshot — 169 packages, 373 versions, per Aikido):

  • @tanstack/* — 83 versions across form, router, store, devtools, start, virtual-file-routes, solid-router, vue-router, etc.
  • @squawk/* — 87 versions (types, mcp, weather, airspace, airports, navaids, NOTAMs, airways, …)
  • @uipath/* — 66 versions (apollo-react, apollo-wind, coded-apps-dev, …)
  • @tallyui/* — 30 versions
  • mistralai, mistralai-gcp, mistralai-azure — 9 versions total
  • Unscoped — 39 versions including safe-action, ts-dna, cross-stitch, cmux-agent-mcp, agentwork-cli, git-branch-selector, wot-api, git-git-git, nextmove-mcp, ml-toolkit-ts.

Confirmed Impact

  • 373 package versions across 169 packages confirmed malicious by Aikido as of May 12, 2026.
  • Active worm propagation: any developer or CI runner that installs a compromised version, and holds publish credentials for any other npm package reachable via stolen tokens, becomes a re-publishing host. The 373/169 numbers are a snapshot of one snapshot, not a ceiling.
  • Reach into production-scale dependencies: @tanstack/react-form (≈1.95M weekly downloads in this DB), @tanstack/form-core (≈2.11M), @tanstack/react-form-devtools (≈130k), and @tanstack/form-devtools (≈128k). Any CI pipeline or developer machine that ran npm install against these between May 7 and May 11, 2026, must be treated as having executed tanstack_runner.js until proven otherwise.

What is not yet publicly enumerated:

  • Exact count of downstream organizations whose secrets reached filev2.getsession.org.
  • Second-wave packages that were re-published using credentials harvested in this campaign. By the worm's design, those packages would themselves look like legitimate trusted-publishing releases.

Mitigation & Lessons

  1. Pin and review every dependency upgrade. Avoid npm install <name>@latest in production code paths. Use package-lock.json with diff review.
  2. Block --ignore-scripts-bypassing git-URL dependencies in CI. A line like optionalDependencies: { "@vendor/setup": "github:..." } is rare in legitimate ecosystems and is the entire payload trigger here. CI policy:
    npm install --ignore-scripts
    
    plus a registry-only resolver setting (@tanstack:registry=https://registry.npmjs.org/, no github:/git+ resolvers).
  3. Rotate every npm publish token in scope for any developer or CI runner that touched any of the 169 affected packages between May 7 and May 11, 2026.
  4. Scope trusted-publishing workflows tightly. Any GitHub Actions workflow with id-token: write and npm publish permission is, in the post–Mini Shai-Hulud world, equivalent to a long-lived publish credential — except invisible to inventory. Use permissions: blocks scoped to the minimum publishable packages, and prefer per-job OIDC over per-workflow.
  5. Block egress to *.getsession.org at corporate proxies and CI egress controls. Legitimate code does not POST to Session file servers.
  6. Require AWS IMDSv2 with HttpPutResponseHopLimit=1. This forces credential-stealing payloads to be on the host itself, not in a container behind a hop.
  7. Audit Vault auth methods. Replace any long-lived VAULT_TOKEN-in-env pattern with workload-bound JWT/OIDC auth that ties to the runner identity.
  8. Inspect dependency diffs between consecutive releases of the same package. The injection moment is the version that adds a git-URL entry to dependencies / optionalDependencies — every prior version of the same package was clean.

Cremit Analysis

NHI Severity Index: 9.5 (Critical)

DimensionValueRationale
Blast radiusecosystem-wide169 packages across multiple maintainer orgs, with worm self-propagation. Any victim with reusable publish credentials becomes a propagator. Surface expands daily until the trusted-publishing path is throttled and clean publishes resume.
ReachabilityproductionThe payload triggers at npm install — the same code path every CI runner and developer machine uses. There is no staging-only exposure; the install always runs the prepare script.
Privilege levelvariableThe harvested credential set spans npm publish tokens (top-tier within the registry trust model), GitHub Actions OIDC (capable of provisioning further AWS/GCP via federation), AWS instance metadata (full host IAM), Kubernetes service account material, and Vault. Whatever the host has, the attacker now has.

NHI Kill Chain Mapping

  • Ghost Key — npm tokens harvested from CI runners are the canonical ghost-key target: long-lived, broadly-scoped, invisible to inventory. The worm uses them to publish from a host the legitimate owner controls but cannot see.
  • Drifted Key — GitHub Actions OIDC tokens were designed as the answer to drifted-key risk. The worm demonstrates how OIDC trust still drifts: an OIDC token leaked from a single workflow run can be replayed within its TTL window to mint fresh long-lived publish tokens, breaking the very assumption short-lived auth was supposed to enforce.
  • Unattributed Key — filev2.getsession.org is the textbook unattributed-key escape: encrypted drop, no operator, no audit trail. Every credential sent there is permanently outside defender control.

Why static npm-package scanning missed this

The worm is engineered specifically to defeat the standard detector stack:

  1. The compromised tarball has no preinstall/install/postinstall field set. Heuristics that match on those fields see an empty list and clear the package.
  2. The malicious executable code is not in the npm tarball at all — it lives in a git URL pinned by commit hash, only fetched at install time. Static analysis of the published artifact never sees tanstack_runner.js.
  3. The trigger script is prepare, which legitimate packages widely use for build steps. Flagging prepare alone produces unacceptable false-positive rates against the broader ecosystem.
  4. The publisher field is GitHub Actions — a trusted-publishing identity. Publisher-reputation, account-age, and owner-change heuristics are inert against it because the credential was stolen from the legitimate workflow, not from the maintainer's account.

Signals that do survive

These are the per-event signals we believe are practical and worth tracking on every npm publish going forward:

  1. optionalDependencies or dependencies entries with github:/git+/http(s):// URL specs, especially pinned to a specific commit hash. Legitimate packages overwhelmingly resolve from the npm registry via semver. A git-URL spec is rare enough at the population level to merit per-event inspection.
  2. First-time appearance of a git-URL dependency in a package that previously used only registry deps. Diffing the dependency graph version-over-version surfaces the injection moment cleanly: prior versions of the same package are clean controls.
  3. Burst publishing across a monorepo by a trusted-publishing workflow that introduces a new dependency. TanStack legitimately ships ~13 packages at once on a release — but at a known cadence and with previous-publish diffs limited to expected source files. A release that also adds optionalDependencies is anomalous on its face.
  4. Network-egress signatures from the published tarball pointing to non-vendor destinations. Even when the install-time payload lives in a git URL, the router_init.js companion left in the tarball contains the loader logic. Static-string matching for getsession.org, 169.254.169.254, vault.svc.cluster.local, and registry.npmjs.org/-/npm/v1/tokens flags the tarball even when the actual prepare script is hidden.

These signals are being added to the Cremit live-monitor pipeline as a direct response to this incident. The April→May 2026 Mini Shai-Hulud waves are unlikely to be the last; trusted-publishing abuse is now an established class of npm supply-chain attack, and the worm-shape — stealing publish credentials to propagate via more publishes — is what makes it self-reinforcing.


References

  1. [1]
    Mini Shai-Hulud is back: TanStack compromised — Aikido Security
    primary·2026-05-12·aikido.dev
  2. [2]
    @tanstack/react-form on npm
    primary·npmjs.com
  3. [3]
    @squawk/mcp on npm
    primary·npmjs.com
  4. [4]
    @uipath/apollo-react on npm
    primary·npmjs.com
  5. [5]
    mistralai on npm
    primary·npmjs.com

Related incidents

2026-04-22·CRITICAL
Bitwarden CLI Supply Chain Compromise (2026)
2021-10-22·CRITICAL
ua-parser-js npm Account Compromise (2021)
2021-11-04·CRITICAL
rc and coa Coordinated npm Account Takeover (2021)
2026-05-04·HIGH
microsop npm Cluster: Dependency-Confusion Campaign Targeting Apple Internal CI/CD (2026)
last reviewed / 2026-05-12license / CC BY 4.0
// incident metadata
severity
CRITICAL9.5
status
confirmed
disclosed
2026-05-12
occurred
2026-05-07 → 2026-05-11
vector
npm supply chain
platforms
npmGitHubAWS
tokens
GitHub PATGitHub App TokenGitHub OAuth Tokennpm Publish TokenAWS Access KeyAWS Session TokenOAuth Token (generic)API Key (generic)Environment Variable
nhi severity index
score9.5 / 10blast radiusecosystem-widereachabilityproductionprivilegevariable
nhi kill chain
Ghost Key↗Drifted Key↗Unattributed Key↗
metrics
exposure5 days