Not merely credential theft — Shai‑Hulud chained GitHub Actions to publish trusted npm and PyPI packages
The May 2026 wave of the Shai‑Hulud campaign didn’t just steal credentials: attackers chained multiple GitHub Actions weaknesses to publish more than 170 malicious npm and PyPI packages that carried valid SLSA provenance, turning build attestations into cover for a wide-ranging, self‑propagating compromise.
How the CI chain was abused end to end
The intrusion began with a malicious pull request that abused GitHub’s pull_request_target workflow trigger in TanStack’s CI, executing attacker-supplied code inside the project’s trusted context. That foothold let the actor poison pnpm’s cache with a 1.1 GB malicious entry and, crucially, read OIDC tokens out of the GitHub Actions runner’s memory to sign and publish packages—84 malicious versions under TanStack alone. These concrete steps (pull_request_target misuse → pnpm cache poisoning → OIDC token extraction) define the chain attackers repeated across namespaces.
Because the attack used a legitimate repository context and real OIDC tokens, the published artifacts carried valid SLSA attestations. That turned cryptographic provenance—normally a trust enhancer—into a mechanism for masking malicious releases as bona fide builds, complicating automated and manual vetting that relies on attestation checks.
What the injected packages did and who was at risk
Malicious npm payloads were implemented as obfuscated JavaScript worms that targeted a broad set of credentials and developer tooling: GitHub tokens, npm credentials, AWS/GCP/Azure keys, Kubernetes service tokens, Vault secrets, SSH keys, CI environment variables, and AI tool configurations such as Anthropic Claude Code and VS Code settings. The worm also included persistence hooks (files like router_init.js and tanstack_runner.js were reported as indicators) and a dead‑man’s switch designed to wipe machines if stolen tokens were revoked.
PyPI infections (for example, mistralai@2.4.6 and guardrails-ai@0.10.1) used a clearer backdoor: a modified __init__.py that fetched and executed a remote Python payload. Microsoft’s analysis noted geofencing logic that avoided Russian‑language environments and a probabilistic destructive branch targeting systems in Israel or Iran—showing selective targeting, not indiscriminate vandalism. Exfiltration used the decentralized, end‑to‑end encrypted Session Messenger network, which raises detection and takedown latency compared with centralized command‑and‑control channels.
Where existing defenses broke down — and a compact checklist
Two structural failures enabled this campaign to scale: privileged runtime access in CI workflows (pull_request_target runs with elevated permissions) and mutable caches/install paths (pnpm cache poisoning). Adding cryptographic attestations that were themselves generated by hijacked pipelines removed a key signal defenders normally use. Those failures make revocation and recovery harder because tokens, builds, and provenance all appear authentic.
| Attack stage | Mechanism | Practical checkpoint / mitigation |
|---|---|---|
| Repository workflow abuse | pull_request_target executes attacker code with repo privileges | Avoid pull_request_target for untrusted input; require workflow approvals and constrained permissions |
| Package cache poisoning | pnpm cache injection persisted malicious artifacts | Harden cache integrity; validate package checksums and use immutable artifact storage |
| OIDC token capture & signing | Reading runner memory to harvest tokens and publish signed packages | Limit token scopes, shorten lifetimes, and isolate signing environments; monitor token use |
Decisions maintainers and orgs must make now
Immediate remediation differs from typical incident playbooks because revoking tokens can trigger the campaign’s dead‑man wipe. Teams should first attempt to neutralize persistence (remove known hooks, disable infected developer tooling directories, and scrub indicators like router_init.js) before rotating credentials. A practical sequence is: isolate affected runners, suspend publish rights, inspect CI logs for memory access patterns, then revoke and rotate tokens once persistence is removed.
Longer term, projects should weigh three policy changes: tighter runtime permission controls for CI (explicitly deny high‑risk triggers like pull_request_target for public contributions), stricter npm/PyPI limits on install‑time or post‑install scripts, and mandatory build isolation for SLSA attestation generation. The next checkpoint to watch is whether npm, GitHub, and PyPI adopt runtime permission reductions or ban certain install/runtime behaviors; decisions by those platforms will materially change attackers’ ability to replicate this chain.
Short Q&A
When should I rotate credentials? Only after removing persistence and disabling any dead‑man switch logic; immediate rotation without cleansing risks remote wipe triggers.
Do SLSA attestations still help? They help when generated from isolated, auditable environments; attestations produced by hijacked pipelines can be weaponized, so validate attestation provenance and the signing environment.
Which indicators to scan for first? Look for modified package files (router_init.js, tanstack_runner.js), unexpected pnpm cache entries, unusual publish activity (84 versions under a single namespace is a red flag), and runner memory access artifacts in CI logs.

