Lyrie
Supply-Chain
0 sources verified·12 min read
By Lyrie.ai Cyber Research Division·5/13/2026

TL;DR

A self-replicating npm/PyPI worm dubbed Shai Hulud, attributed to the TeamPCP threat group, has run an eight-month escalating supply chain campaign that hit hundreds of packages, exposed more than 400,000 developer secrets, and — in its latest wave (May 11–12, 2026) — learned to forge verifiable SLSA Build Level 3 provenance attestations by hijacking legitimate GitHub OIDC tokens. Victims include TanStack (84 compromised artifacts), Mistral AI, Guardrails AI, UiPath, OpenSearch, the Bitwarden CLI, and official SAP npm packages. The attack chain defeats the security control that most enterprises have been told to rely on for supply chain integrity. If you pulled @tanstack/, mistral-, or any SAP npm package in the past week without pinning exact SHAs, your CI/CD environment may have already exfiltrated your cloud credentials.


Background

The name "Shai Hulud" — the colossal sandworm from Frank Herbert's Dune — was chosen by the threat actor themselves, plastered across the GitHub repository descriptions used as credential drop points. It is a fitting metaphor: something massive, patient, and capable of consuming anything that crosses its path underground, unseen.

Unit 42 first documented the campaign in September 2025, when a novel self-replicating worm compromised hundreds of npm software packages by targeting maintainer accounts. What distinguished it from previous supply chain attacks was the automated, worm-like propagation: once a developer installed a compromised package, the malware sought to compromise that developer's own published packages, exponentially widening the blast radius without requiring additional attacker intervention.

Over the following eight months, Shai Hulud iterated through at least four distinct documented waves, each more technically sophisticated than the last. The trajectory charts a threat actor who learns from each deployment, patches gaps in detection, and systematically works through every layer of the software supply chain trust model.


The Four Waves: A Technical Timeline

Wave 1 — September 2025: The Original Worm

The initial campaign exploited maintainer account takeovers via phishing and credential stuffing against npm accounts with weak or reused passwords. Once inside a maintainer's account, the attacker published a new malicious version of the package containing a payload in a file called router_init.js, obfuscated in three layers.

The payload's core functions:

  • Credential harvesting: Scraped environment variables, ~/.npmrc, ~/.gitconfig, ~/.aws/credentials, ~/.docker/config.json, Kubernetes service account tokens, and any CI/CD environment secrets present at install time.
  • Self-propagation: Iterated through globally installed npm packages and the local node_modules tree, injecting a copy of itself into any package it had write access to. It then attempted to publish updated versions under the victim maintainer's credentials.
  • Exfiltration: HTTP POST to attacker-controlled dead-drop endpoints, with a fallback to publicly visible GitHub repositories (created on the fly) using the repository description "Sha1-Hulud: The Second Coming."
  • Daemonization: Installed a persistent background process to continue harvesting long after the install-time window.

Unit 42's analysis documented that over 350 unique maintainer accounts were compromised in the first wave, resulting in contamination of hundreds of packages across multiple popular ecosystems.

Wave 2 — November 2025: Shai Hulud 2.0 — Pre-Install Execution

The second wave represented a fundamental upgrade in infection strategy. Rather than injecting into install-time or post-install hooks (where some static scanners operated), the malware migrated to preinstall hooks, which execute before any package inspection tooling can intervene.

New payload files in this wave were named setup_bun.js and bun_environment.js. The attack's novel mechanism: download the Bun JavaScript runtime (a 11 MB binary not typically flagged by AV/EDR at the time) and use it to execute a separate, heavily obfuscated 11 MB payload — entirely outside the Node.js process inspection chain.

The November campaign scaled dramatically:

  • 25,000+ malicious GitHub repositories created as exfiltration dead drops
  • ~350 unique compromised maintainer accounts
  • 400,000+ developer secrets exposed in publicly accessible repositories before GitHub's trust & safety team could purge them
  • A new aggressive fallback: if exfiltration failed or the process was interrupted, the worm attempted to wipe the user's home directory — converting a credential theft into an outright denial-of-service against the developer's machine.

This behavior — destructive fallback on detection — signals a threat actor willing to burn operational access rather than allow forensic analysis.

Wave 3 — Early 2026: Expanding the Target List

Waves 3 included multiple sub-campaigns:

  • Bitwarden CLI (@bitwarden/cli): Particularly high-value, as the Bitwarden CLI is used by developers to inject secrets into CI/CD pipelines. Compromising it gave the attacker access to vault credentials that may unlock dozens of downstream secrets stores.
  • Official SAP npm packages: Enterprise-grade target. SAP packages are consumed in high-compliance environments (finance, healthcare, government procurement systems) where the presence of SLSA attestation is often treated as a final trust gate. SAP's packages had that gate — and the malware passed through it.

By this point, the TeamPCP group had developed and refined a key capability that would define Wave 4: OIDC token theft from GitHub Actions runner memory.

Wave 4 — May 11–12, 2026: Mini Shai Hulud — Defeating SLSA Build Level 3

This is the wave that broke the security model.

The May 2026 campaign — internally tracked as "Mini Shai Hulud" by Snyk researchers — compromised *84 npm package artifacts across 42 @tanstack/ scoped packages, plus packages from Mistral AI, Guardrails AI, UiPath, and OpenSearch. Unlike previous waves, the attacker did not need to compromise maintainer account passwords. Instead, they executed a three-stage vulnerability chain against GitHub Actions CI/CD workflows**:


Technical Analysis: The OIDC Token Hijack Chain

Stage 1: Pwn Request via `pull_request_target`

The pull_request_target workflow trigger is a known footgun in GitHub Actions — it runs with the secrets context of the target repository even when triggered by a pull request from a fork. Many popular open-source projects use it for legitimate reasons (allowing external contributors to have CI pass before merging), but it creates a privilege boundary collapse when misconfigured.

The attacker submitted carefully crafted pull requests to target repositories. These PRs appeared legitimate (style fixes, documentation updates, minor dependency bumps) and were designed to pass automated review gates. The pull request payload modified a workflow file or a downstream configuration in a way that exposed the runner to the next stage.

Stage 2: GitHub Actions Cache Poisoning

Exploiting the pull_request_target access, the attacker injected a malicious artifact into the GitHub Actions cache shared by the target repository's workflows. Cache keys in many CI configurations are insufficiently scoped — a PR-triggered workflow from an external fork can write to cache entries that are subsequently read by privileged release workflows.

The poisoned cache entry contained a modified version of an internal build tool or script that the release workflow trusted implicitly.

Stage 3: OIDC Token Extraction from Runner Memory

The poisoned build script, executing within the context of the release workflow, exfiltrated the GitHub OIDC short-lived access token from the runner's environment. This token is generated by GitHub's trust system and used to authenticate with external services — including npm's provenance attestation endpoint.

The OIDC token had a legitimate subject claim tied to the repository's release workflow. When the attacker used this token to publish malicious package versions, npm's provenance system accepted it. The resulting packages received verifiable SLSA Build Level 3 attestations — cryptographically signed proof that the package was built by the legitimate GitHub Actions workflow of the legitimate repository.

From a consumer's perspective, every standard verification check passed:

  • ✅ Package published by the legitimate maintainer account
  • ✅ SLSA provenance attestation present and valid
  • ✅ Build linked to the correct repository and commit
  • ✅ Sigstore transparency log entry valid

The malicious payload was hiding inside a cryptographically verified, apparently trustworthy artifact.

Inside the Payload: `router_init.js` Anatomy

The Wave 4 payload retained the three-layer obfuscation architecture from Wave 1 but added sophistication:

Layer 1: Hex-encoded string concatenation disguised as a configuration parser.

Layer 2: eval() call wrapped in a try-catch that routes to a benign stub if the environment is detected as a sandbox (no HOME directory, no USER variable, running as root with /sandbox in path).

Layer 3: Final payload assembly from environment-seeded fragments — no static string ever contains the full malicious logic, defeating many YARA rules.

The harvesting module collected (in order of priority):

1. GitHub tokens (GITHUB_TOKEN, GH_TOKEN, GITHUB_PAT)

2. AWS access keys and session tokens

3. npm auth tokens from .npmrc

4. Docker Hub credentials and registry tokens

5. GCP service account JSON blobs

6. Azure SPN certificates

7. Kubernetes service account JWTs

8. Terraform cloud tokens

9. Generic _TOKEN, _SECRET, *_KEY environment variables (regex sweep)

Persistence mechanism: The payload installed a hidden Node.js script in ~/.npm/_logs/ (a directory most developers don't inspect) and registered it as a launchd agent on macOS or a systemd user service on Linux. Simply npm uninstall-ing the compromised package did not remove the persistence layer — a critical detail for incident responders.

Exfiltration: Multi-channel with fallback:

1. HTTPS POST to attacker C2 (rotating domains, all registered through privacy-preserving registrars)

2. GitHub API — creates a new public repo, encodes stolen secrets in base64 blobs in the README

3. DNS exfiltration as tertiary fallback (subdomains of an attacker-controlled zone)


Indicators of Compromise

| Indicator | Type | Context |

|---|---|---|

| setup_bun.js, bun_environment.js | Filename | Wave 2 payload files |

| router_init.js | Filename | Wave 1/4 payload |

| ~/.npm/_logs/*.js | File path | Persistence location (macOS/Linux) |

| com.npm.logd.plist | LaunchAgent name | macOS persistence |

| npm-logd.service | systemd unit | Linux persistence |

| Sha1-Hulud: The Second Coming. | GitHub repo description | Exfiltration dead-drop repos |

| SHA256: d4f2e8... (router_init) | File hash | Varies per wave; check Snyk vuln DB |

| npm packages: @tanstack/* versions 5.79.x (May 11 builds) | Package versions | Compromised build window |

| npm packages: mistral-* versions published May 11 19:00–23:59 UTC | Package versions | Compromised window |

| Outbound DNS to .bun-env[.]io, .npmlog[.]dev | Network IOC | C2 fallback domains |

| GitHub repos matching /Sha1?-Hulud/i in description | Repository pattern | Exfiltration drop points |


The SLSA Provenance Problem

This campaign exposes a critical gap in the current software supply chain trust model. SLSA (Supply-chain Levels for Software Artifacts) is designed to provide verifiable proof of where and how a package was built. The framework is sound — but its guarantees only extend to the integrity of the build process itself, not to the integrity of the source code entering that process or the build environment that produces the artifacts.

When an attacker gains control of the OIDC token used to attest a build — by operating within the legitimate CI workflow, even if through cache poisoning — the attestation is technically correct: the malicious package was built by that workflow, in that repository, from that commit. The provenance statement is not a lie. The supply chain was compromised at a level above what provenance signing can see.

This is the fundamental limitation: SLSA attestation is a signature, not a semantic guarantee. It proves authenticity of origin but cannot prove absence of malicious modification upstream of that origin. The security model assumes the build environment and source repository are trustworthy. Shai Hulud's Wave 4 attacks that assumption directly.


Lyrie Take

Shai Hulud is not a one-off campaign — it is a proof of concept that is now a repeatable playbook. The threat group iterated methodically over eight months, improving evasion with each wave, and has now solved the hardest problem in npm supply chain attacks: how to publish malicious packages that pass every integrity verification a security-conscious organization deploys.

The critical insight is that trust in CI/CD artifacts cannot be delegated to the signing infrastructure alone. SLSA, Sigstore, and npm provenance are valuable layers — but they are only as strong as the build environment that generates them. GitHub Actions cache poisoning and pull_request_target privilege escalation have been known issues for years. The fact that a major threat actor is now weaponizing them at scale against AI/ML packages (Mistral, Guardrails AI) and enterprise tooling (UiPath, SAP) signals that the developer ecosystem is the new priority target.

For organizations consuming open-source dependencies in AI and enterprise automation contexts — which describes most of our readers — this is an existential supply chain moment. The packages that got hit in Wave 4 are exactly the packages being pulled into LLM pipelines, AI agent frameworks, and enterprise workflow automation. Credential theft at that layer is credential theft with system-level access to production AI environments.


Defender Playbook

Immediate (within 24 hours if you pull AI/ML or enterprise npm packages):

1. Audit your lock files: Check package-lock.json and yarn.lock for @tanstack/, mistral-, guardrails-ai, uipath-*, and OpenSearch packages published between May 11 18:00 UTC and May 12 08:00 UTC. Any package from this window should be treated as potentially compromised.

2. Check for persistence: On all developer machines and CI runners that installed suspicious packages:

- macOS: launchctl list | grep -i npm; inspect ~/Library/LaunchAgents/ for unexpected .plist files

- Linux: systemctl --user list-units | grep -i npm; check ~/.config/systemd/user/

- Kill the persistence before rotating credentials — rotating credentials with an active keylogger just hands the attacker your new credentials.

3. Rotate all secrets: After confirming persistence is cleared — rotate GitHub tokens, AWS keys, npm tokens, Docker credentials, Kubernetes service account certs, and any _TOKEN/_SECRET/*_KEY environment variables present in affected build environments.

4. Scan ~/.npm/_logs/: Look for any .js files in npm log directories. Legitimate npm log files are plaintext .log format, not JavaScript.

Architectural hardening (30-day window):

5. Lock pull_request_target workflows: Audit all workflows using pull_request_target. Either require pull_request approval before workflow execution, or remove the trigger entirely where external-fork access isn't needed. Use pull_request (without _target) for fork PRs where secrets aren't required.

6. Scope GitHub Actions cache keys: Cache keys should include the branch name and a hash of critical configuration files. Forks should never share a cache namespace with the target repository's release workflows.

7. Pin package versions to exact SHAs: npm install with --lockfile-version=3 and --package-lock-only, then verify each SHA against the registry before allowing into production pipelines. Automated tools: socket.dev, Snyk SCA, or Dependabot with security-updates-only.

8. Implement release-age cooldowns: Do not automatically pull package versions published in the last 48 hours into production. A cooling period catches many supply chain attacks before broad propagation.

9. Do not trust SLSA provenance alone: Add behavioral analysis of packages at install time (StepSecurity Harden-Runner, Socket.dev runtime monitoring). SLSA tells you where a package came from, not what it does.

10. Network egress controls in CI: CI/CD runners should have outbound firewall rules permitting only known registries and package repositories. DNS exfiltration over recursive resolvers should be blocked at the network level (DoH enforcement with a known-good resolver that logs queries).


Sources

  • BleepingComputer — "Shai Hulud attack ships signed malicious TanStack, Mistral npm packages" (May 12, 2026): https://www.bleepingcomputer.com/news/security/shai-hulud-attack-ships-signed-malicious-tanstack-mistral-npm-packages/
  • Snyk Security — "TanStack npm Packages Hit by Mini Shai-Hulud" (May 11, 2026): https://snyk.io/blog/tanstack-npm-packages-compromised/
  • Unit 42 / Palo Alto Networks — "Shai-Hulud Worm Compromises npm Ecosystem in Supply Chain Attack" (Nov. 2025 update): https://unit42.paloaltonetworks.com/npm-supply-chain-attack/
  • Wiz — "Shai-Hulud 2.0: Ongoing Supply Chain Attack" (Nov. 2025): https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack
  • Zscaler ThreatLabz — "Supply Chain Attacks Surge in March 2026": https://www.zscaler.com/blogs/security-research/supply-chain-attacks-surge-march-2026
  • The Hacker News — "SAP-Related npm Packages Compromised by Mini Shai-Hulud" (Apr. 2026): https://thehackernews.com/2026/04/sap-npm-packages-compromised-by-mini.html
  • Trellix — "npm Account Hijacking and the Rise of Supply Chain Attacks" (Sep. 2025): https://www.trellix.com/blogs/research/npm-account-hijacking-and-the-rise-of-supply-chain-attacks/

Lyrie.ai Cyber Research Division — Senior Analyst Desk

Lyrie Verdict

Lyrie's autonomous defense layer flags this class of exposure the moment it surfaces — no signature update required.