TeamPCP’s Mini Shai-Hulud Worm Spreads Across 170 npm Packages and PyPI

Summary
A self-propagating worm attributed to TeamPCP reportedly compromised 170 npm packages and two PyPI packages, stealing CI credentials through OIDC and spreading via republished releases. The article details affected versions, persistence, exfiltration, and response steps
Key points
- The article reports 170 compromised npm packages across 19 namespaces and two PyPI packages, including OpenSearch and Mistral clients.
- The campaign reportedly began with an orphaned commit in a TanStack repository that exposed a CI workflow’s npm publishing OIDC token, bypassing 2FA-protected publishing.
- Compromised packages use preinstall hooks to download Bun, run an obfuscated payload, harvest CI and cloud credentials, and republish packages using OIDC federation.
- The payload reportedly exfiltrates credentials through Session’s P2P messaging network and injects hooks and workflow files into downstream repositories.
- The PyPI package guardrails-ai 0.10.1 reportedly downloads and executes a remote Python archive on import; mistralai 2.4.6 was also listed as compromised.
- The article recommends moving off affected versions, rotating credentials exposed to infected CI runs, checking repositories for injected files, and disabling npm install scripts in CI.
Article Details
- Attack Vectors
- An attacker exploited an orphaned commit in a TanStack repository to access a CI workflow run holding an npm publishing OIDC token, bypassing 2FA-protected publishing.
- Compromised npm packages execute through a preinstall hook, download Bun, and use it to launch a loader and an obfuscated credential-harvesting payload.
- On CI runners with an active OIDC token, the worm mints npm publishing tokens through OIDC federation and republishes infected packages with Sigstore provenance attestations.
- The implant harvests CI and cloud credentials, including GitHub Actions secrets, AWS and ECS metadata credentials, Kubernetes service-account tokens, and Vault tokens.
- The implant exfiltrates credentials through Session messaging and its file endpoint; the PyPI package guardrails-ai 0.10.1 downloads and executes a remote Python archive on import on Linux.
- The worm uses GitHub GraphQL to inject files and workflow definitions into downstream repositories, with commits attributed to claude@users.noreply.github.com.
- Defensive Notes
- Pin affected packages away from the compromised versions; for @opensearch-project/opensearch, the article recommends versions at or below 3.5.2 or a confirmed-clean newer release.
- Rotate credentials that were accessible to CI runs that installed compromised packages, including GitHub Actions secrets, AWS keys, Kubernetes service-account tokens, and Vault tokens.
- Search repositories for unexpected .claude and .vscode files, newly added workflow files, and commits attributed to claude@users.noreply.github.com.
- Block the listed attacker infrastructure at egress and investigate affected hosts for /tmp/transformers.pyz and the described implant files.
- Disable npm install scripts on CI runners with ignore-scripts where lifecycle hooks are not required.
Indicators of compromise
| Type | Indicator | Context |
|---|---|---|
| DOMAIN | api[.]masscan[.]cloud | Identified by the article as an attacker-controlled API. |
| DOMAIN | git-tanstack[.]com | Typosquat domain used to serve the PyPI dropper and a page claiming TeamPCP responsibility. |
| DOMAIN | seed1[.]getsession[.]org | Session bootstrap node listed as campaign infrastructure. |
claude@users[.]noreply[.]github[.]com | Spoofed author address used on injected commits to disguise them as Claude Code agent output. | |
| MD5 | 833fd59ebe66a4449982c6d18db656b4 | Hash of router_init.js, the obfuscated npm implant. |
| MD5 | b82e54923f7e440664d2d75bd31588ca | Hash of tanstack_runner.js, the worm-propagation loader. |
| SHA1 | 12ed9a3c1f73617aefdb740480695c04405d7b4b | Hash of router_init.js, the obfuscated npm implant. |
| SHA1 | e7d582b98ca80690883175470e96f703ef6dc497 | Hash of tanstack_runner.js, the worm-propagation loader. |
| SHA256 | 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96 | Hash of tanstack_runner.js, the worm-propagation loader. |
| SHA256 | ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c | Hash of router_init.js, the obfuscated npm implant. |
| URL | hxxp[:]//filev2[.]getsession[.]org/file/ | Primary npm-side credential exfiltration endpoint identified in the article. |
| URL | hxxps[:]//git-tanstack[.]com/transformers[.]pyz | PyPI dropper payload URL downloaded and executed by guardrails-ai 0.10.1 on import. |
MITRE ATT&CK
T1027 · Obfuscated Files or Informationrouter_init.js is described as a 2.3 MB obfuscated payload using string-array rotation and control-flow flattening.T1048.003 · Exfiltration Over Unencrypted Non-C2 ProtocolThe article describes credential exfiltration through the plain-HTTP Session file endpoint.T1059.006 · PythonThe PyPI dropper downloads a .pyz file and invokes it with python3.T1059.007 · JavaScriptCompromised npm packages use a preinstall hook to run JavaScript setup and loader files.T1105 · Ingress Tool TransferThe npm implant downloads the Bun runtime, and the PyPI package downloads transformers.pyz.T1195.002 · Compromise Software Supply ChainThe attackers compromised npm and PyPI packages, distributing the worm through affected software releases.T1497.001 · System ChecksThe payload behaves benignly when specified fake-platform environment variables are present, which the article identifies as likely sandbox-detection canaries.T1528 · Steal Application Access TokenThe worm steals an OIDC token from an infected CI run and uses OIDC federation to mint an npm publish token.T1552.005 · Cloud Instance Metadata APIThe payload accesses AWS instance metadata through IMDSv2 to harvest credentials.
Threat Actors
Malware
Vendors
Amazon Web Servicesattack has spread to 170 npm packages spanning 19 namespaces, the official OpenSearch JavaScript client at AWS (1.3 million weekly downloads), the Mistral AI clients on both npm and PyPI, and a handful of PyPI packagesMistral AI19 namespaces, the official OpenSearch JavaScript client at AWS (1.3 million weekly downloads), the Mistral AI clients on both npm and PyPI, and a handful of PyPI packages including guardrails-ai. We've documented
Products
Buninstall into a republisher. Each compromised release wires a preinstall hook that downloads the legitimate Bun runtime from github.com/oven-sh/bun and uses Bun as a LOLBin to launch a loader (tanstack_runner.js), whichGitHub Actions(router_init.js). That payload harvests every credential it can reach on a typical CI runner — GitHub Actions secrets, AWS instance metadata via IMDSv2, ECS task metadata, Secrets Manager and SSM Parameter StoreMistral AI clients19 namespaces, the official OpenSearch JavaScript client at AWS (1.3 million weekly downloads), the Mistral AI clients on both npm and PyPI, and a handful of PyPI packages including guardrails-ai. We've documentednpmThe OSM team has been tracking a self-propagating npm supply-chain worm that exploded across the registry over the last 24 hours. It started with the TanStack packages, but didn't stop there — by the time you read this,OpenSearchthe time you read this, the attack has spread to 170 npm packages spanning 19 namespaces, the official OpenSearch JavaScript client at AWS (1.3 million weekly downloads), the Mistral AI clients on both npm and PyPI,PyPIOpenSearch JavaScript client at AWS (1.3 million weekly downloads), the Mistral AI clients on both npm and PyPI, and a handful of PyPI packages including guardrails-ai. We've documented every affected version onSessionExfiltration: Routed through the Session P2P messaging network — encrypted IM traffic, not HTTP C2SigstorePropagation: Steals OIDC tokens from infected CI, mints fresh npm publish tokens via OIDC federation, republishes packages with valid Sigstore provenance attestations as a deceptive trust signalTanStacknpm supply-chain worm that exploded across the registry over the last 24 hours. It started with the TanStack packages, but didn't stop there — by the time you read this, the attack has spread to 170 npm packages