deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Mini Shai-Hulud npm worm spread poisoned TanStack packages with valid provenance via Dependabot

A supply-chain worm dubbed Mini Shai-Hulud published 84 malicious TanStack package versions with valid npm provenance, then used a merged Dependabot PR to hijack a maintainer's token and publish 110 more.

Mini Shai-Hulud npm worm spread poisoned TanStack packages with valid provenance via Dependabot

A worm that shipped with a valid signature

On May 11, 2026, a worm now known as Mini Shai-Hulud published 84 malicious versions across 42 @tanstack/* packages to npm, all built inside TanStack's own release pipeline and all carrying valid SLSA provenance. Two and a half hours later, a routine Dependabot pull request pulled two of those versions into a small aviation-data project, and the maintainer's single merge turned a publish token into 110 more malicious versions in 95 minutes. According to a detailed timeline on dev.to, assembled from TanStack's postmortem by Tanner Linsley and the downstream maintainer's incident report, no password was phished and the only human action in the entire chain was that one click.

How the worm got into TanStack's pipeline

The attack began at 10:49 UTC with a pull request from a renamed fork of TanStack/router. TanStack's bundle-size workflow triggers on pull_request_target, which runs in the context of the base repository rather than the fork: it checked out and built the fork's code, executing the attacker's changes. At 11:29 that code saved a 1.1 GB pnpm store cache under the exact key the release workflow would later look up. The pull request was then force-pushed to an empty change, closed and deleted, leaving the poisoned cache as the only trace.

At 19:16 a legitimate merge triggered the release workflow, which restored that cache. Per the postmortem, attacker binaries read the memory of GitHub's Runner.Worker process, extracted the short-lived OIDC token granted by id-token: write, and posted packages straight to the registry at 19:20 and 19:26, two versions per package. The workflow's own publish step had been skipped because tests failed; the malware published anyway. TanStack did not spot the compromise itself — a StepSecurity researcher raised the alarm about 26 minutes after the first publish, and TanStack deprecated the affected versions and posted a public advisory by 21:19.

The Dependabot hop

Twenty-nine minutes after the advisory, Dependabot opened a grouped dev-dependency bump on the neilcochran/squawk repository: 13 updates, two of them compromised TanStack versions. The maintainer reviewed and merged it 24 minutes later. The project's publish workflow then ran npm ci with NPM_TOKEN in scope, and the malicious prepare script exfiltrated the credential — a single overly broad classic token reaching all 22 @squawk/* packages plus three unrelated personal ones. Between 22:17 and 23:52 the worm published five malicious versions of every package it covered, roughly one HTTP request per version with no human step or cooldown. The maintainer learned from npm's notification emails just after midnight, revoked the token and disabled Actions, and GitHub Trust & Safety removed the 110 versions and reset the latest tags by 03:41.

Every poisoned TanStack version had already been deprecated when the downstream merge happened. As the dev.to timeline notes, deprecation is only a warning and npm still installs the version, while npm declines to unpublish packages that have dependents. The bad versions stayed installable for up to four and a half hours. Vendors counted more than 160 infected packages ecosystem-wide, including one belonging to Mistral.

Inside the payload

StepSecurity deobfuscated the 2.3 MB payload. It spreads on install: compromised versions add a hidden optionalDependency pointing at an orphan git commit, which npm fetches as a tarball and whose prepare script it runs during installation — effectively invisible in a grouped-bump diff that reads as a routine version bump. The worm hunts for a classic npm token permitted to publish without two-factor authentication; in CI it exchanges the GitHub OIDC token for a per-package publish token. It then queries the registry for everything the maintainer controls and republishes an infected tarball for each. Its dead-drop commits are dressed as automation: a fabricated author named "claude" (unaffiliated with Anthropic), the commit message "chore: update dependencies", and Dependabot-style branch names mixed with Dune references such as harkonnen and atreides.

Why valid provenance did not help

The compromised builds carried genuine SLSA Build Level 3 provenance because they really were produced by TanStack's official pipeline — the pipeline just was not running TanStack's code. StepSecurity describes Mini Shai-Hulud as the first documented npm worm to ship validly attested malicious packages, and TanStack's follow-up concedes that provenance, SLSA, OIDC and 2FA all worked as designed without stopping the attack. Provenance answers which pipeline built an artifact; it cannot say whether that pipeline was clean. The same limit applies to OIDC: the token was short-lived, but the worm read it out of memory while it was still alive.

Why it matters

This incident is a working demonstration that supply-chain attestations verify pipelines, not intent, and that a pull_request_target workflow plus a predictable cache key can turn a stranger's fork into a signed, trusted publish. It also shows how mechanical downstream trust has become: a bot proposed the update, a human clicked merge, and the worm repaid the favor by wearing Dependabot's uniform. The mitigations are unglamorous but concrete — never run fork code in the base repository's context, key caches defensively, keep install steps and publish credentials in separate CI jobs, and scope npm tokens per package rather than per maintainer. The response, at least, was fast: both maintainers published timestamped postmortems within a day, and the downstream pipeline was rebuilt within three.

  • #npm
  • #supply-chain
  • #security
  • #github-actions
  • #dependabot

Related posts