Editorial

Three Supply Chain Attacks. Three Different Vectors. One Defensive Primitive.

RobertUpdated May 24, 20265 min read
Dark terminal-style feature image. White text reads: Three vectors. One primitive. Below it in teal monospace: CI/CD hijack. Dormant account. Tag rewrite. Attestd branding bottom left.

Three Supply Chain Attacks. Three Different Vectors. One Defensive Primitive.#

Three supply chain compromises in twelve days. Three completely different ingress paths. The same credentials stolen. The same detection approach.

DatePackage(s)Attack vector
May 11@tanstack/react-router, @mistralai/mistralai, guardrails-ai and othersCI/CD cache poisoning via stolen OIDC token. Valid SLSA Build Level 3 provenance on malicious packages.
May 14node-ipcDormant maintainer account recovered via expired domain password reset.
May 22laravel-lang/lang, laravel-lang/http-statuses, laravel-lang/attributes, laravel-lang/actionsExisting git tags rewritten to point at malicious commits in an attacker-controlled fork. No new versions published. No source code modified.

The mechanism is irrelevant at detection time. A registry of known-compromised (ecosystem, package, version) tuples catches all three. Here is what that looks like.


Attack 1: CI/CD pipeline hijack (May 11)#

The TanStack attack chained three vulnerabilities: a risky pull_request_target workflow, GitHub Actions cache poisoning, and OIDC token theft from runner process memory. The attacker published malicious versions through the legitimate TanStack release pipeline. The packages carried valid Sigstore attestations and valid SLSA Build Level 3 provenance. The npm provenance badge shows verified.

npm audit passes. Provenance verification passes. The pipeline was compromised, not the signatures.

bash
curl "https://api.attestd.io/v1/check?product=%40tanstack%2Freact-router&version=1.169.5" \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "product": "@tanstack/react-router",
  "version": "1.169.5",
  "supported": true,
  "risk_state": "none",
  "cve_ids": [],
  "supply_chain": {
    "compromised": true,
    "sources": ["osv", "registry"],
    "malware_type": "worm",
    "description": "Mini Shai-Hulud (TeamPCP). Cache-poisoned OIDC publish; router_init.js worm steals CI credentials and self-propagates. Valid SLSA Build Level 3 provenance.",
    "advisory_url": "https://socket.dev/blog/tanstack-npm-packages-compromised-mini-shai-hulud-supply-chain-attack",
    "compromised_at": "2026-05-11T19:20:00Z"
  }
}

The same campaign reached PyPI via stolen credentials from compromised maintainer accounts:

bash
curl "https://api.attestd.io/v1/check?product=guardrails-ai&version=0.10.1" \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "product": "guardrails-ai",
  "version": "0.10.1",
  "supported": true,
  "risk_state": "none",
  "supply_chain": {
    "compromised": true,
    "sources": ["osv", "registry"],
    "malware_type": "backdoor",
    "description": "Mini Shai-Hulud second wave (TeamPCP). Executes on import — downloads transformers.pyz from git-tanstack.com and runs without integrity check.",
    "compromised_at": "2026-05-12T03:05:00Z"
  }
}

risk_state: "none" on both. No CVE exists for either attack.


Attack 2: Dormant maintainer account (May 14)#

The maintainer account atiertant had been dormant since approximately 2022. The email address on the account was registered to the domain atlantis-software.net, which expired in January 2025 and was re-registered by the attacker in May 2026. A password reset via the expired domain gave the attacker full control of the npm account. Three trojanized versions were published: 9.1.6, 9.2.3, and 12.0.1.

The package has 690,000 weekly downloads. The prior maintainer's 2022 intentional sabotage incident is still documented in the repository.

bash
curl "https://api.attestd.io/v1/check?product=node-ipc&version=9.1.6" \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "product": "node-ipc",
  "version": "9.1.6",
  "supported": true,
  "risk_state": "none",
  "supply_chain": {
    "compromised": true,
    "sources": ["osv"],
    "malware_type": "malware",
    "description": "Malicious code in node-ipc (npm)",
    "advisory_url": "https://safedep.io/malicious-node-ipc-npm-compromise/",
    "compromised_at": "2026-05-14T16:53:17Z"
  }
}

Note that node-ipc shows sources: ["osv"] while the TanStack packages show sources: ["osv", "registry"]. The detection output is identical. The ingestion path differs. Both surface supply_chain.compromised: true.


Attack 3: Git tag rewriting (May 22)#

This is the most technically novel of the three. The attacker obtained org-wide push access to the Laravel Lang GitHub organization via a compromised credential. Starting at 22:32 UTC, they rewrote every existing git tag across four repositories to point at malicious commits stored in an attacker-controlled fork. The operation took approximately 90 minutes and poisoned 700+ historical versions across four packages.

The official repository source code was never modified. The version numbers were never changed. A developer inspecting the main branch would have seen nothing wrong.

GitHub allows tags to reference commits in forks of the same repository. The attacker exploited this to make historical release tags resolve to malicious commits without touching the canonical repository.

The version-pinning paradox: a team pinned to laravel-lang/[email protected] for reproducibility ran composer install after the rewrite and pulled malicious code. The version string was legitimate. The tag existed before the attack. The commit behind it was not the one that had always been there.

Coverage note: Laravel Lang packages are distributed via Packagist and Composer, outside Attestd's current PyPI and npm watchlist. The API returns supported: false for these packages. The detection model is identical a registry of known-compromised version tuples keyed on (ecosystem, package, version) only the registry scope differs.


What all three have in common#

risk_state: "none" on every compromised version across all three attacks. No CVE exists for any of them. A tool that reads CVE severity returns clean. The supply chain signal is the only automated detector in all three cases.

The clean version one step prior:

bash
curl "https://api.attestd.io/v1/check?product=%40tanstack%2Freact-router&version=1.169.4" \
  -H "Authorization: Bearer YOUR_API_KEY"
# → supply_chain.compromised: false
 
curl "https://api.attestd.io/v1/check?product=node-ipc&version=11.1.0" \
  -H "Authorization: Bearer YOUR_API_KEY"
# → supply_chain.compromised: false

Same package. Different version. Different answer. That is exact-version tuple matching. It does not matter how the compromise happened.


The pattern#

The three attack vectors share nothing technically. OIDC token theft from runner memory is a different operation from recovering a dormant npm account via an expired domain, which is a different operation from rewriting git tags across an org. The tooling, the access paths, and the techniques are unrelated.

The goal is identical across all three. Developer credentials, cloud access tokens, CI/CD secrets, SSH keys, Kubernetes service account tokens.

The defensive primitive is identical across all three. A registry of known-compromised version tuples, checked at deploy time, is the only automated signal that catches a CI/CD pipeline hijack producing valid SLSA attestations, a dormant maintainer account publishing new malicious versions, and a git tag rewrite that never produces a new version at all.

The mechanism does not matter at detection time. The (ecosystem, package, version) tuple does.

Get an API key at api.attestd.io/portal/login. Free tier, 1,000 calls a month, no credit card required.