Contents
ToggleExecutive Summary
Sha1-Hulud is not a vulnerability in the conventional sense. There is no CVE, no memory safety bug, no mismatched HTTP parser. The Shai-Hulud-Open-Source reference implementation is, in its entirety, a multi-stage credential infostealer and supply chain attack platform. The whole codebase is the exploit.
Between late 2025 and mid-2026, the operators behind it — tracked publicly as TeamPCP, also identified as PCPcat, DeadCatx3, ShellForce, and CipherForce — industrialised this model across npm, PyPI, GitHub Actions, Docker Hub, OpenVSX, and IDE integrations. Every wave refined the tradecraft: new loaders, new persistence mechanisms, new exfiltration channels, and a growing target list that now includes AI SDK libraries, password manager CLIs, and the security scanners DevSecOps teams rely on to detect compromise.
Five things to carry out of this article: this is install-time code execution, not a CVE. The primary objective is credential theft and package republishing. The malware’s polymorphism is operational — loaders, filenames, hooks, and exfil paths rotate while the kill chain stays stable across every wave. Persistence now spans GitHub Actions, self-hosted runners, IDE and AI assistant configurations, Python import hooks, and package caches. And OIDC plus signed provenance do not protect you if the workflow identity is compromised — Mini Shai-Hulud generated cryptographically valid Sigstore provenance for backdoored releases. Standard SCA tooling was blind to every wave.

Key Findings from Phoenix Purple https://phoenix.security/phoenix-purple-ai-sast-sca-ai-generated-code/
TL;DR for Engineering Teams
| What it is | A self-propagating supply chain worm that steals credentials, backdoors npm/PyPI packages, establishes IDE and CI persistence, and abuses GitHub OIDC to republish malicious releases with valid Sigstore provenance. No CVE exists for any wave. |
| Where it bites | npm preinstall/postinstall hooks; PyPI .pth import hooks; GitHub Actions pull_request_target workflows with id-token: write; .claude/ and .vscode/ IDE config directories; Python site-packages; systemd user services; Windows Startup folder. |
| Primary objective | Credential harvest: GitHub tokens, npm tokens, AWS/GCP/Azure keys, Kubernetes tokens, Vault secrets, SSH keys, crypto wallets, AI assistant configs. Then automated package republishing using the stolen credentials. |
| Why CVE scanners miss it | Entire campaign is CVE-invisible. Malicious versions pass signature checks. Mini Shai-Hulud wave produces Sigstore-verified provenance for backdoored packages. Tarball-vs-source divergence is the only reliable SCA signal. |
| Immediate action | Set ignore-scripts=true in CI. Audit pull_request_target workflows for id-token: write + untrusted checkout. Pin GitHub Actions to full commit SHA. Rotate all tokens exposed in CI during affected windows. Audit .pth, .claude/, and .vscode/ directories. |

Malware Overview
| Field | Value |
| Malware Family | Shai-Hulud V1/V2, Sha1-Hulud V3, CanisterWorm, Mini Shai-Hulud |
| Threat Actor | TeamPCP (PCPcat, DeadCatx3, ShellForce, CipherForce, Persy_PCP) |
| Primary Ecosystems | npm, PyPI, GitHub Actions, Docker Hub, OpenVSX |
| Malware Type | Self-propagating supply chain worm — credential stealer — persistent implant |
| CVEs | None — across every known wave |
| Active Exploitation | Confirmed — multiple campaigns 2025–2026 |
| Credential Targets | GitHub tokens, npm tokens, AWS/GCP/Azure keys, K8s tokens, Vault secrets, SSH keys, crypto wallets, AI assistant configs |
| Scale (V2 wave) | ~700 malicious package versions; 25,000+ attacker-created/abused GitHub repos; 37 affected organisations |
| Scale (Mini Shai-Hulud) | 170 packages across 19 namespaces; 84 malicious TanStack versions in ~24 hours; >1.3M weekly-download packages affected |
| Destructive Capability | Confirmed: rm -rf ~/ wiper gated on victim org membership |

Kill Chain analysis with Phoenix Purple https://phoenix.security/phoenix-purple-ai-sast-sca-ai-generated-code/
Get early access to Phoenix Purple to find TRUE critical attack chain with exploits
Verified Kill Chain (src/index.ts)
Shai-Hulud-Open-Source is a fully operational attack platform. Every step below is verified against the published source code. The entire codebase is the exploit.
| Step | Action | Code Location | Verified |
| 1 | Python loader bootstraps Bun runtime | src/assets/PYTHON_LOADER.py:48-98 | ✅ |
| 2 | preflight() — check Russian locale, daemonize, acquire lock | src/index.ts:67-89 | ✅ |
| 3 | checkTargetRepo() — if in opensearch-js CI, run NPM supply-chain backdoor | src/index.ts:42-65 | ✅ |
| 4 | setupQuickResults() — filesystem + shell + runner harvest | src/index.ts:29-40 | ✅ |
| 5 | Connect to primary C2 at git-tanstack.com | src/index.ts:95-99 | ✅ |
| 6 | Fallback: create GitHub repo, exfil via commits | src/sender/github/createRepo.ts:58-105 | ✅ |
| 7 | Run AWS SSM, Secrets Manager, K8s, Vault providers | src/index.ts:136-142 | ✅ |
| 8 | For each captured ghtoken: inject malicious workflow into victim repos | src/providers/actions/workflow.ts:193-233 | ✅ |
| 9 | Encrypt all results with operator RSA pubkey, POST to C2 | src/sender/base.ts:40-70 | ✅ |
| 10 | Cleanup: delete injected branches and workflow runs | src/providers/actions/workflow.ts:171-187 | ✅ |
Encryption pipeline: data → JSON → gzip → AES-256-GCM(random key) → RSA-OAEP(operator pubkey) → base64. All sensitive strings in the binary are obfuscated via a custom scramble() function with AES-256-GCM encrypted constants in src/generated/index.ts. None are recoverable from the binary without the operator’s key.

Background: From Shai-Hulud to TeamPCP
The first Shai-Hulud wave proved the concept: a package-manager worm can spread without a traditional vulnerability. It used compromised npm maintainers, poisoned tarballs, lifecycle hooks, GitHub token abuse, and automated republishing. The second wave, Sha1-Hulud V2, expanded the blast radius to roughly 700 malicious package versions, 25,000+ attacker-created or abused GitHub repositories, and 37 affected organisations across analytics, Web3, API tooling, e-commerce, automation, and developer infrastructure. CyberScoop reported the November 2025 wave exposing more than 26,000 GitHub repositories in under 24 hours.
The V3.0 variant appears more contained in initial distribution but more mature in tradecraft: new loader names, removed dead-man switch, improved Windows/Bun handling, and a useful detection artefact in the c0nt3nts.json vs c9nt3nts.json filename mismatch. The newest Mini Shai-Hulud / TeamPCP-linked wave crosses into high-value projects and signed supply-chain workflows — TanStack, Mistral AI, Guardrails AI, UiPath, OpenSearch, and Bitwarden CLI — abusing OIDC tokens to publish with verifiable provenance.
Variant Evolution
| Variant | Execution Hook | Payload Names | Key Mutations | Defender Implication |
| Shai-Hulud V1 | postinstall | bundle.js | Establishes worm viability; GitHub repo exfil with Shai-Hulud naming | Proves install-time RCE as propagation vector |
| Sha1-Hulud V2 | preinstall | setup_bun.js, bun_environment.js | Earlier execution window; CI-aware; cloud.json / truffleSecrets.json exfil; 26K repo exposure in <24h | Higher CI yield; noisy exfil repos still present |
| Sha1-Hulud V3 | lifecycle -> Bun | bun_installer.js, environment_source.js | Renamed loaders; removed dead-man switch; Windows Bun fix; Goldox-T3chs repo brand; c0nt3nts vs c9nt3nts mismatch IOC | IOC rotation defeats hash-only detection |
| CanisterWorm | postinstall (npm) | CanisterWorm payload | Deployed alongside Trivy/KICS GitHub Actions wave; lateral npm propagation from CI access | Security scanners used as delivery vector |
| Mini Shai-Hulud / TeamPCP | preinstall, prepare, optional deps | router_init.js, @tanstack/setup | OIDC token abuse; Sigstore provenance; IDE hook injection (.claude, .vscode); P2P C2; 170 packages in <24h; PyPI crossing | CVE-invisible; npm audit signatures passes; no SCA signal |

Hardening Guidelines with https://phoenix.security/phoenix-purple-ai-sast-sca-ai-generated-code/
Technical Anatomy
Bootstrap: Three Polymorphic Loaders
The worm ships three independent bootstrap paths. This is not redundancy — it is guaranteed execution coverage across the widest possible range of CI configurations and developer machines.
Loader 1 — JavaScript/Bun (primary): The npm package.json preinstall hook invokes setup.mjs, which downloads the Bun runtime for the exact platform from github.com/oven-sh/bun, then executes the 11-12 MB obfuscated JavaScript payload. Bun was chosen deliberately: most detection rules assume Node-only execution, and Bun’s performance allows a full credential sweep within a normal install-time window.
Loader 2 — Python (fallback): src/assets/PYTHON_LOADER.py implements a complete Python bootstrap activated via Python .pth site-packages hooks. Any Python interpreter startup triggers the payload — not just importing the compromised package, but any Python process at all. This is the mechanism used in the LiteLLM and Bitwarden compromises.
Loader 3 — Bash (tertiary): An encrypted BASH_LOADER payload stored in src/generated/index.ts provides a third execution path using only standard shell utilities. Three loaders across three runtimes means the worm survives CI environments with unusual toolchain configurations.
Credential Harvesting: The Worm’s Real Payload
The malware is not primarily destructive. It is a credential factory. The FileSystemService sweeps 100+ file paths across Linux, macOS, and Windows. Deep collection providers sweep cloud APIs, secret stores, and CI runner contexts.
| Credential Class | Target Examples | Why It Matters |
| Source control | GitHub PATs, GITHUB_TOKEN, GitHub App tokens, OIDC tokens | Repo takeover, workflow injection, package publishing rights |
| Package registries | npm tokens, PyPI credentials, OIDC publisher federation | Worm propagation — each stolen token backdoors all packages that token can publish |
| Cloud | AWS access keys, Azure CLI cache, GCP service accounts, IMDS tokens | Cloud pivot and secret-manager enumeration |
| CI/CD | GitHub Actions secrets, runner environment, build variables | Tier-0 blast radius — everything the pipeline touches |
| Kubernetes | Service account tokens, kubeconfig, cluster secrets, k3s configs | Runtime and cluster-level control |
| Vault / secret stores | HashiCorp Vault KV, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault | Central exfiltration of all stored secrets in one sweep |
| Developer tooling | SSH keys, .npmrc, .pypirc, .netrc, .env, shell history, AI assistant configs | Local developer compromise; persistent re-infection vector |
| Crypto / wallets | Bitcoin, Ethereum, Monero, Zcash keystores; Exodus, Electrum | Direct monetisation path |
The AwsSsmService and AwsSecretsManagerService sweep all AWS SSM parameters and Secrets Manager entries across all regions using a full SigV4 credential resolution chain — environment variables, INI profiles, IRSA OIDC, ECS container metadata, and EC2 IMDS — whichever is available in the current context.
Critical Exploit Techniques
| F-02 | sudo python3 Runner Secret Dumpsrc/providers/ghrunner/runner.ts:39-44 pipes an AES-256-GCM encrypted Python script to sudo python3 -c. The script reads Runner.Worker and Runner.Listener process memory, extracting every secret injected by GitHub Actions at runtime — including secrets marked as masked. The encryption keeps the script invisible to static analysis; it decrypts only at runtime, in memory. Consequence: masking secrets in workflow YAML does not protect them if the runner process is accessible. Rotating only npm tokens after this fires is insufficient — everything accessible to the runner must be treated as exposed. |
| F-03/04 | GitHub Actions CI Hijack: CodeQL Impersonation + Self-Cleaning Evidence. When a ghp_ token with workflow scope is found, the worm creates a branch named dependabot/github_actions/format/setup-formatter, commits a malicious workflow to .github/workflows/codeql_analysis.yml with committer identity github-advanced-security[bot]@users.noreply.github.com, waits for CI to execute and extract secrets via artifact, then deletes the branch and run logs. Three simultaneous layers of impersonation: Dependabot branch naming, GitHub CodeQL workflow path, GitHub Security bot committer identity. A developer reviewing recent commits would need to read commit content — not just metadata — to catch it. |
| F-09 | Public Token Extortion via Commit Message src/utils/config.ts:2-4 defines SEARCH_STRING as the literal string: IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner. When the worm commits exfiltrated data using the victim’s own token, this string is posted as a public commit message containing the victim’s token. This is not an oversight — it is an explicit extortion mechanism designed to deter credential rotation during incident response. Responders who revoke the token are warned the wiper will activate. |
| F-10 | Org Membership-Gated Wiper src/sender/github/gitHubSenderFactory.ts:100-105 gates the rm -rf ~/ destructive payload on whether the victim’s GitHub account has zero organisational memberships. Solo developers, freelancers, and open-source maintainers are specifically selected for data destruction. The logic is deliberate: no org membership correlates with no incident response process and no monitoring. Accounts with org membership trigger exfiltration without the wiper. |
| F-11 | OIDC Supply Chain with Cryptographically Valid Sigstore Provenance NPMOidcClient targets the opensearch-js release pipeline, stealing the GitHub OIDC token from ACTIONS_ID_TOKEN_REQUEST_URL, exchanging it for npm publish rights, generating a fully valid Sigstore bundle (ephemeral ECDSA P-256 keypair, Fulcio signing certificate, real Rekor transparency log entry), injecting @opensearch/setup as an optional dependency (pinned to attacker-controlled commit d446803f), and republishing. The provenance is not forged — it is genuinely signed using the victim CI’s identity. npm audit signatures returns a pass. Signed provenance does not prove benign code; it proves a compromised workflow produced the artifact. |
| F-12/13 | Three Bootstrap Loaders; Nine Encrypted Payloads JS/Bun, Python .pth, and Bash loaders ensure execution across radically different environments. Nine AES-256-GCM encrypted payloads are stored in src/generated/index.ts: config, task, DEADMAN_SWITCH, verify_key, claude_settings, python_util, PYTHON_LOADER, BASH_LOADER, and workflow. None are recoverable from the binary without the operator’s decryption key. String-level analysis of the production bundle surfaces no plaintext indicators. |
| F-14 | Raw Shell Injection via ${{ raw: command }}src/utils/shell.ts:20 uses Bun’s ${{ raw: command }} syntax, which bypasses Bun’s default shell escaping. This is the internal execution wrapper throughout the payload. Any attacker-controlled string reaching a shell execution point runs without sanitization. |
| F-15/16/17 | 25+ CI Platforms; 100 Repos per Token; Full Org Secret Coverage src/utils/config.ts:53-148 detects 25+ CI platforms including GitHub, GitLab, Travis, CircleCI, Jenkins, Azure Pipelines, AWS CodeBuild, Buildkite, Netlify, Vercel, and more. When a ghp_ token is found, up to 100 writable repos are enumerated and org-level Actions secrets are swept across all accessible repos. The blast radius of a single compromised token is enormous. |
| F-19/20 | Signal Handler Suppression and TLS Verification Disabled SIGINT and SIGTERM handlers are set to no-op at startup — the process cannot be cleanly terminated. npm publish operations use rejectUnauthorized: false, disabling TLS certificate verification and evading network inspection. User-Agent is spoofed to npm/11.13.1 node/v24.10.0. Russian locale check (Intl, LC_ALL, LC_MESSAGES, LANGUAGE, LANG, Windows regional env) exits immediately — a classic nation-state operational security marker. |
Operational Polymorphism
Sha1-Hulud is not polymorphic malware in the classic packed-binary sense. Call it operationally polymorphic supply-chain malware. It is not mutating encryption routines or rewriting opcodes. It is rotating the surface presentation of a stable kill chain.
Across waves, the following change: filenames, lifecycle hook names, execution engines (Node, Bun, Python, Bash), package paths, exfiltration repo descriptions, exfil file names, staging methods, C2 fallback patterns, persistence locations, and campaign branding. What stays constant across every wave: install-time execution → credential theft → GitHub/npm/cloud abuse → exfiltration → persistence → package republishing.
This mutation pattern defeats brittle detection that only looks for one hash, one filename, or one malicious package version. A defender looking only for bundle.js misses setup_bun.js. A defender looking only for setup_bun.js misses router_init.js. A defender looking only for unsigned packages misses OIDC-signed malicious releases. Each wave is designed to invalidate the indicators published about the previous wave.
Exfiltration Architecture: Four-Level Fallback
The malware is designed not to fail closed. If one exfiltration path breaks, it falls back to the next without operator intervention.
| Priority | Channel | Mechanism | Fallback Trigger |
| 1 | git-tanstack.com:443/router | HTTPS POST — AES-256-GCM + RSA-OAEP encrypted payload | Primary — attempted first |
| 2 | Signed-commit C2 discovery | Search thebeautifulmarchoftime repo, RSA-verify commit message, extract domain | Primary unreachable |
| 3 | Attacker GitHub repo (attacker token) | Double-base64 encoded token from commit; creates Dune-themed repo | C2 discovery fails |
| 4 | Victim’s own GitHub account | Harvested ghp_/gho_ token; creates repo; commits data; extortion message in commit body | All prior channels unavailable |
Earlier waves also used webhook-style exfiltration to webhook.site endpoints and GitHub Actions artifacts. The Telnyx wave introduced Session P2P network exfiltration via filev2.getsession.org alongside the steganographic WAV delivery channel.
Persistence Mechanisms
| Mechanism | Implementation | Trigger Condition |
| .claude/ + .vscode/ repo injection | Malware binary to .claude/<SCRIPT_NAME>; encrypted loaders to .claude/setup.mjs, .vscode/setup.mjs, .vscode/tasks.json; committed to every branch of every writable repo | VS Code workspace open; Claude Code invocation |
| GitHub Actions workflow implant | Workflow injected as dependabot/github_actions/format/setup-formatter branch; committer impersonates github-advanced-security[bot]; self-deletes after secret extraction | ghp_ token with workflow scope found |
| Self-hosted runner registration (V2) | Persistent runner named SHA1HULUD registered; triggered by .github/workflows/discussion.yaml via GitHub Discussions events | GitHub App token found; RCE bridge to internal infra |
| Python .pth site execution | litellm_init.pth in site-packages imports loader on every Python startup | Compromised PyPI package installed |
| systemd user service | ~/.config/sysmon/sysmon.py registered as systemd service | LiteLLM/TeamPCP PyPI wave; Linux hosts |
| Windows Startup folder | PE extracted from WAV file via steganography written as msbuild.exe to user Startup folder | Telnyx wave; Windows hosts; no admin rights required |
| Package republishing | Backdoored versions in lockfiles, artifact caches, internal registries, Docker image layers persist after upstream cleanup | Any npm token with bypass_2fa + package:write |
Package caches and build images are persistence locations. Cleaning the npm registry upstream does not remove infected versions already pinned in lockfiles or cached in Artifactory/Nexus layers. Treat any internal registry that pulled packages during affected windows as potentially contaminated.
IOC Catalogue
File and Payload IOCs
| Indicator | Associated Wave | Notes |
| bundle.js | V1 | Original npm payload |
| setup_bun.js | V2 | V2 Bun setup loader |
| bun_environment.js | V2 | V2 environment payload |
| bun_installer.js | V3 | V3 renamed loader |
| environment_source.js | V3 | V3 main payload |
| router_init.js | Mini Shai-Hulud / TeamPCP | TanStack/Mistral wave payload |
| router_runtime.js | PyPI / Lightning-linked | Reported by SafeDep; PyPI crossing |
| cloud.json | V2 | Exfil credential snapshot |
| contents.json | V2 | Exfil snapshot |
| environment.json | V2 | Environment dump |
| truffleSecrets.json | V2 | Secret scan output |
| 3nvir0nm3nt.json | V3 | Renamed exfil file |
| cl0vd.json | V3 | Renamed exfil file |
| pigS3cr3ts.json | V3 | Renamed exfil file |
| c0nt3nts.json (fetch) vs c9nt3nts.json (save) | V3 | Detection artefact — fetch and save use different filenames |
| .github/workflows/shai-hulud-workflow.yml | V1/V2 | Workflow persistence |
| .github/workflows/discussion.yaml | V2 | Self-hosted runner persistence via Discussions trigger |
| .vscode/tasks.json | Mini Shai-Hulud | IDE persistence — runs on VS Code workspace open |
| .claude/settings.json | Mini Shai-Hulud | AI assistant config hijack |
| .claude/setup.mjs | Mini Shai-Hulud | AI assistant bootstrap loader |
| litellm_init.pth | LiteLLM / TeamPCP PyPI | Python import hook persistence |
| ~/.config/sysmon/sysmon.py | LiteLLM / TeamPCP | Linux systemd persistence agent |
| msbuild.exe in Startup folder | Telnyx wave | Windows boot persistence — no admin required |
| SHA-256: 46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09 | V1 | Frequently cited V1 payload hash |
Repository and Branch IOCs
| Indicator | Meaning |
| Repo name/description contains: Shai-Hulud | V1/V2 exfil branding — public repos created in victim accounts |
| Repo description: Sha1-Hulud: The Second Coming | V2 campaign repo description |
| Repo description: Goldox-T3chs: Only Happy Girl | V3 campaign repo description — IOC rotation |
| Repo description: A Mini Shai-Hulud has Appeared | Mini Shai-Hulud exfil repo marker |
| Repo names: sardaukar-*, sandworm-* in victim accounts | Procedurally generated Dune-themed exfil repo names |
| Branch: shai-hulud | Persistence / staging indicator in victim repos |
| Branch: dependabot/github_actions/format/setup-formatter | GitHub Actions workflow injection — Dependabot impersonation |
| Self-hosted runner named SHA1HULUD | V2 persistent runner registration |
| Commit author: github-advanced-security[bot]@ on non-CodeQL commits | Workflow hijack indicator |
| Commit message containing IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner | Extortion marker + exposed victim token |
Domain and Infrastructure IOCs
| Indicator | Context | Campaign |
| git-tanstack.com | Primary C2 exfiltration endpoint | Mini Shai-Hulud / TeamPCP |
| audit.checkmarx[.]cx | Secondary exfil endpoint (typosquat of legitimate Checkmarx domain) | Bitwarden CLI wave |
| checkmarx[.]zone | Persistence polling domain — LiteLLM variant polls every 50 minutes | LiteLLM / TeamPCP PyPI |
| filev2.getsession.org | Session P2P network C2 | Telnyx wave |
| 89.36.224.5 | C2 IP | velora-dex/sdk incident |
| metrics-trustwallet[.]com | Related infrastructure | V1/V2 era |
| m.fasterxml[.]org | Typosquatted Maven-related domain | V3 context |
| webhook.site endpoints | Earlier exfil path for secrets | V1/V2 waves |
| thebeautifulmarchoftime (GitHub repo) | Signed-commit C2 domain discovery source | Mini Shai-Hulud |
Use domain IOCs with care in the article: several are campaign-specific and should not be treated as universal across every Shai-Hulud variant. Confirm campaign context before blocking at the network layer.
MITRE ATT&CK Mapping
| Technique | ID | Implementation |
| Supply Chain Compromise | T1195.001 | npm/PyPI package backdooring via stolen tokens and OIDC federation |
| Command and Scripting Interpreter | T1059.004 | Bash loader; shell.ts raw injection; Python .pth import hook |
| Boot or Logon Autostart: XDG/systemd | T1547.013 | systemd user service persistence (LiteLLM/TeamPCP wave) |
| Boot or Logon Autostart: Startup Folder | T1547.001 | Windows Startup folder (Telnyx wave — no admin required) |
| Event Triggered Execution | T1546 | GitHub Actions workflows triggered by push/discussion; IDE task hooks |
| Hijack Execution Flow: .pth | T1574 | Python site-packages .pth execution on every interpreter startup |
| Valid Accounts: Cloud Accounts | T1078.004 | OIDC token exchange for npm publish; cloud credential abuse |
| Steal Application Access Token | T1528 | GitHub OIDC token from ACTIONS_ID_TOKEN_REQUEST_URL; runner process memory |
| Credentials from Files | T1552.001 | 100+ filesystem hotspots across Linux/macOS/Windows |
| Credentials from Password Stores | T1555 | Bitwarden CLI backdoor; crypto wallet sweep; Vault/K8s secret APIs |
| Obfuscated Files / Information | T1027 | AES-256-GCM encrypted payload constants; JS obfuscation; scramble() |
| Steganography | T1027.003 | WAV-embedded PE delivery (Telnyx wave) |
| Abuse Elevation Control Mechanism | T1548 | sudo python3 runner secret dump (F-02) |
| Software Deployment Tools | T1072 | npm publish / PyPI upload abused as lateral movement and propagation |
| Exfiltration Over Web Service | T1567.002 | GitHub repo commits; GitHub Actions artifacts; P2P Session network |
| Data Encrypted for Impact | T1486 | rm -rf ~/ wiper gated on org membership (F-10) |
| Impersonation | T1656 | Dependabot + CodeQL + GitHub Security bot commit identity spoofing |
Detection Engineering
Package Scan Signals
Standard CVE-based SCA has zero surface against this campaign. These are the detection primitives that work:
- Lifecycle script delta: flag packages where preinstall, postinstall, or prepare was added or changed between versions. This single rule would have caught every Shai-Hulud wave at the package level.
- External binary download during install: alert on npm install processes that spawn curl, wget, or fetch connections to github.com/oven-sh/bun or any non-registry domain.
- Tarball-vs-source divergence: compare the published npm tarball content against the corresponding GitHub tag commit tree. Every TeamPCP-compromised package shows files in the registry that are absent from the source repo.
- Optional dependency pointing to unfamiliar setup packages: @opensearch/setup, @tanstack/setup, and similar patterns in optionalDependencies should trigger review.
- Filename patterns in package contents: flag any package containing bundle.js, setup_bun.js, bun_installer.js, bun_environment.js, router_init.js, or environment_source.js.
GitHub Hunting Queries
Search organisation repositories for:
repo.description: Shai-Hulud OR Sha1-Hulud OR Goldox-T3chs OR Mini Shai-Hulud
branch: shai-hulud OR dependabot/github_actions/format/setup-formatter
workflow path: .github/workflows/shai-hulud-workflow.yml
workflow path: .github/workflows/discussion.yaml
workflow path: .github/workflows/codeql_analysis.yml (verify against expected CodeQL config)
committer: github-advanced-security[bot] (verify against real CodeQL scan events)
self-hosted runner name: SHA1HULUD
repo visibility changed private -> public (unexpected)
CI/CD Alerts
- npm publish –force from CI runners outside expected release workflows
- Bun execution in jobs that do not normally use Bun — especially during npm install
- OIDC token requests (ACTIONS_ID_TOKEN_REQUEST_URL access) outside release workflows
- GitHub Actions cache writes from pull_request_target workflows checking out untrusted forks
- New workflow files committed by bot identities not matching expected automation
- Workflow run logs deleted shortly after completion
- Sudden publishing of many patch versions across a namespace in a short window
Cloud and Secret Store Alerts
- AWS access keys first used from CI identities shortly after a suspicious install job
- AWS Secrets Manager or SSM ListSecrets/GetSecretValue calls from build agents that do not normally read secrets
- GCP Secret Manager access from build agents outside production deploy workflows
- Kubernetes API secret enumeration (list secrets across all namespaces) from non-ops identities
- HashiCorp Vault KV list/read bursts from CI runner IPs
Endpoint / Developer Machine Signals
Search developer workstations and CI nodes for:
~/.claude/ (unexpected files beyond normal Claude config)
~/.claude/settings.json (compare against expected content)
~/.claude/setup.mjs (should not exist in normal Claude Code installation)
.vscode/tasks.json (in repo working directory — inspect content for base64 or external fetch)
.vscode/setup.mjs (in repo working directory — should not exist)
cloud.json / contents.json / environment.json / truffleSecrets.json
3nvir0nm3nt.json / cl0vd.json / pigS3cr3ts.json
litellm_init.pth in any Python site-packages directory
msbuild.exe in %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup
Remediation Guidance
Immediate Containment
| Action | Owner | Reason |
| Freeze dependency upgrades for npm/PyPI | Platform / DevOps | Stop pulling newly infected versions during the active campaign window |
| Block direct npm/PyPI from CI; route through internal proxy | Platform | Force registry inspection; enable quarantine period |
| Disable lifecycle scripts where feasible (ignore-scripts=true in CI .npmrc) | Engineering / CI owners | Breaks install-time execution at the first hop for every Shai-Hulud variant |
| Quarantine affected runners immediately | DevOps / SOC | Assume everything accessible to the runner is exposed |
| Revoke and rotate all GitHub PATs, npm tokens, cloud keys, PyPI tokens | IAM / Platform | Tokens are the worm’s primary propagation fuel |
| Audit GitHub repo creation and repo visibility changes (private → public) | GitHub org admin | Detect exfil repositories created in victim accounts |
| Remove suspicious workflows and self-hosted runners | DevOps | Kill CI and runner persistence before next triggered job |
| Purge npm/PyPI package caches and rebuild all container images | Platform | Remove cached poisoned artifacts from internal registries and Docker layers |
Medium-Term Controls
| Control | Rationale |
| Require lockfiles and exact versions across all projects | Prevent silent patch-version compromise — Shai-Hulud always bumps the patch version |
| Enforce registry quarantine or cooldown window (24-48h) | Both Shai-Hulud and Mini Shai-Hulud had short, intense exposure windows — delay breaks the propagation chain |
| Diff package tarball vs source repo in CI pipeline | Detect hidden payloads not present in Git — primary detection signal for every TeamPCP package |
| Require human approval for new lifecycle scripts | Lifecycle hooks are execution primitives; treat them as code review requiring sign-off |
| Protect pull_request_target workflows from untrusted code checkout | Pin to trusted authors or remove id-token: write from all untrusted-fork-accessible workflows |
| Restrict OIDC audiences and Trusted Publisher mappings to specific workflow file paths | Reduce OIDC blast radius — Mini Shai-Hulud abused a namespace-wide OIDC binding |
| Monitor GitHub Actions cache writes from PR contexts | Cache poisoning enables re-execution without code changes in the PR |
| Use short-lived, scoped publishing tokens; rotate on schedule | Limits worm propagation radius per stolen token |
| Alert on package publishing outside expected release workflows | Detects maintainer token abuse and CI identity compromise immediately |
| Inventory packages owned by each team before compromise | Know your propagation surface; map token → packages → downstream consumers |
Phoenix Security Recommendations
The Sha1-Hulud / TeamPCP campaign breaks the scan-and-patch model at every layer. The issue is not vulnerable dependency X — it is malicious dependency intake, poisoned package versions, trusted publisher abuse, CI identity compromise, cloud secret exposure, developer workstation persistence, hidden workflow implants, repo visibility changes, internal registry contamination, and cached malicious artifacts. Recovery is not patching a package. It requires pinning, proxying, credential rotation, workflow removal, runner cleanup, and reachability-driven triage.
Phoenix Security addresses this through controls that operate below the CVE layer:
- Tarball-vs-source divergence detection: Phoenix correlates published package tarballs against corresponding source repository commits, surfacing packages where the registry artifact diverges from what the maintainer published — the primary and often only detection signal across every TeamPCP-compromised package.
- Lifecycle script change detection: Phoenix flags new or changed preinstall/postinstall/prepare entries across the dependency graph, triggering review before the package reaches a CI runner.
- OIDC and Actions configuration auditing: Phoenix maps pull_request_target workflows with sensitive permission grants across your entire GitHub organisation, surfacing configurations that match the TeamPCP initial access pattern before compromise occurs.
- Reachability-driven blast radius analysis: Rather than flagging every package with a lifecycle hook, Phoenix identifies which hooks are reachable in your specific dependency graph and execution context, then maps the downstream credential and package publishing exposure for each.
- Remediation campaign tracking: When a compromise is confirmed or suspected, Phoenix creates a remediation campaign mapping every affected repository, assigns ownership to the responsible team, and tracks fix verification against runtime exposure — rather than leaving incident response as ad-hoc manual work across distributed teams.
- Developer tooling treated as Tier-0: Phoenix monitors the supply chain posture of Trivy, KICS, Bitwarden CLI, LiteLLM, and similar tools with the same controls applied to production dependencies. The Trivy and KICS GitHub Actions compromises demonstrate that security tooling is not trusted infrastructure — it is a high-value attack surface.
The strong positioning for ASPM: Sha1-Hulud is the case study for remediation-aware exposure management. The winning control is not a bigger vulnerability list. It is code-to-cloud attribution, package-version provenance, CI workflow visibility, owner mapping, and blast-radius reduction.
Hardening Guide: Code-Mapped Defenses
Every control below is mapped to a specific source file and line in the Shai-Hulud-Open-Source reference implementation. Attack-vector claims are validated from code. Some platform-configuration recommendations (exact GitHub settings UI paths, audit event field names) are operationally well-grounded but are not provable from the repo alone — those are marked accordingly.
The three controls that stop this malware with minimal engineering effort:
| Control | Blocks | Code Reference | Effort |
| ignore-scripts=true in .npmrc | npm lifecycle bootstrap — no postinstall = no execution | src/assets/config.mjs:176-199 | Minutes |
| Remove sudo from CI runners | sudo python3 runner secret dump — the single most powerful harvester | src/providers/ghrunner/runner.ts:39-45 | Hours |
| Branch protection + require signed commits on * | ReadmeUpdater entire persistence pipeline — commits unsigned, blocked by policy | src/mutator/branch/index.ts:47-63 | Hours |
Note on the ignore-scripts claim: this is the highest-value single control against the npm bootstrap path shown in config.mjs. It is not the only possible execution path once a payload is already resident — GitHub Actions abuse, branch mutation, and npm republishing operate independently once the worm has run once. Treat it as the essential first gate, not a universal kill switch.
Layer 1: npm Supply-Chain (Entry Point)
The malware enters via npm postinstall (src/assets/config.mjs:176-199), downloads the Bun runtime from github.com/oven-sh/bun, and executes the obfuscated payload. Blocking this execution path stops the worm before any credential harvesting begins.
| Control | What It Blocks | How to Implement | Validated |
| ignore-scripts=true in .npmrc | postinstall hook entirely | Commit .npmrc with ignore-scripts=true to every repo; enforce in CI with npm ci –ignore-scripts | Validated — config.mjs:176 |
| npm ci (not npm install) in CI | Tampered package-lock.json | npm ci fails if lockfile differs from package.json — prevents silent version drift | Validated — mutator bumps patch version src/mutator/npm/index.ts:71 |
| Block objects.githubusercontent.com from CI egress | Bun runtime download | Egress firewall: block objects.githubusercontent.com from runner IPs — download is required before payload executes | Validated — config.mjs:29-44 |
| Private registry proxy (Artifactory / Nexus) | Direct registry.npmjs.org access and backdoored tarball distribution | Route all npm traffic through internal proxy; scan tarballs before caching; quarantine period minimum 24-48h | Inferred from mutator publish path src/mutator/npm/publish.ts:113 |
| Block registry.npmjs.org PUT/POST from CI | npm package backdooring | Egress firewall: allow GET to npm registry; block PUT and POST — republish requires PUT | Validated — src/mutator/npm/publish.ts:113-159 |
| Disable bypass_2fa on npm automation tokens | npm token backdooring | Malware specifically filters for bypass_2fa=true tokens before attempting republish; disable on all automation tokens | Validated — src/mutator/npm/tokenCheck.ts:9-125 |
Layer 2: GitHub Token Scope Hardening
The malware checks for ghp_, gho_, ghs_, and npm_ tokens (src/providers/ghrunner/runner.ts:14-18) and requires repo scope before initialising the GitHub exfiltration sender (src/sender/github/gitHubSenderFactory.ts:81-83).
| Control | What It Blocks | Grounded In | Validated |
| Fine-grained PATs only; no contents:write unless required | GitHub repo creation and commit-based exfiltration | Sender requires repo-capable token via hasRepoScope check — narrower scopes break the sender initialisation | Validated — gitHubSenderFactory.ts:81-83 |
| Token expiry <= 24h on all automation PATs | Long-lived tokens harvested from filesystem | Short TTL means a harvested token is already expired before the operator can use it | Validated — filesystem.ts sweeps .git-credentials and .netrc |
| No workflow scope on PATs not used for workflow management | GitHub Actions workflow injection | Workflow injection proceeds only against tokens with workflow write access | Validated as inference — workflow.ts:50-78 |
| GitHub token permission audit: revoke admin:org and secrets:read not explicitly required | Org secret enumeration across all repos | Malware enumerates org secrets via Actions API using harvested tokens | Validated — src/providers/actions/secrets.ts:36-47 |
| Secret scanning push protection enabled on all repos | Token committed to public GitHub repos | Malware commits token-bearing data in commit messages when includeToken is set | Validated — githubSender.ts:156-158; exact push protection behavior is platform-level |
Layer 3: GitHub Actions Hardening
The most sophisticated attack surface. The malware injects a fake CodeQL workflow (src/providers/actions/workflow.ts:13), impersonates github-advanced-security[bot] (workflow.ts:71-75), exfiltrates secrets via artifact download, and self-deletes the branch and run logs (workflow.ts:177-186).
Hardened workflow permissions template — apply as org-level default:
permissions:
contents: read # NOT write
id-token: none # Override per-job only when signing is required
actions: none
secrets: none # No ambient secret enumeration
| Control | What It Blocks | Code Reference | Validated |
| Branch protection: require PR for all branches (* wildcard) | Direct push to dependabot/github_actions/format/setup-formatter without review | src/providers/actions/workflow.ts:56-62 | Validated — branch creation and direct push are the injection mechanism |
| Pin all Actions to full commit SHA not floating tags | Malicious action tag hijacking (Trivy/KICS wave) | Trivy/KICS wave: aquasecurity/trivy-action@v0.20.0 vs full SHA | Validated from TeamPCP campaign (not this repo) |
| Require workflow approval for first-time and outside contributors | Any injected workflow from compromised fork or new account | src/providers/actions/workflow.ts:50-77 | Validated as reasonable platform guidance |
| Block sudo on CI runners (sudoers policy or hardened runner image) | sudo python3 runner secret dump — the entire GitHubRunner harvest path | src/providers/ghrunner/runner.ts:39-45 | Validated — entire runner dump depends on sudo python3 |
| Restrict id-token: write to the specific job that needs it | OIDC token stolen for Sigstore provenance and npm publish | src/mutator/npmoidc/provenance.ts:173-199 | Validated — OIDC token extraction requires id-token: write permission |
| Use ephemeral self-hosted runners (–ephemeral flag) | Persistence between workflow runs on self-hosted runners | SHA1HULUD runner registration in V2 wave | Validated from campaign analysis (not this repo code) |
| Stream GitHub audit logs to SIEM | Self-deleting workflow runs — API delete removes UI visibility but audit log entry is durable | src/providers/actions/workflow.ts:177-186 | Partially validated — exact audit event names are platform-level |
Layer 4: Git Repository Hardening
ReadmeUpdater (src/mutator/branch/index.ts:34-63) commits to every branch using GitHub GraphQL createCommitOnBranch. It explicitly avoids dependabot/** and copilot/** branches but attempts all others. Commit headline is hardcoded as ‘chore: update dependencies’ with co-author claude@users.noreply.github.com.
| Control | What It Blocks | Implementation | Validated |
| Branch protection on ALL branches (* wildcard, require 1 reviewer) | Commit to any unprotected branch without PR | GitHub repo Settings > Branches > Apply protection to * pattern | Validated — malware pushes directly without PR flow |
| Require signed commits (GPG or SSH) | Unsigned disguised dependency-update commits | Settings > Branches > Require signed commits. ReadmeUpdater does not implement commit signing in source | Validated — no signing code found in src/mutator/branch/index.ts; the ‘has no signing key’ phrasing is softened — no key found in code but cannot assert globally |
| CODEOWNERS: .claude/ and .vscode/ require @security-team review | .claude/ and .vscode/ file injection reaching main without review | Add to CODEOWNERS: /.claude/ @security-team /.vscode/tasks.json @security-team | Validated — injected files are explicit in src/mutator/branch/index.ts:34-40 |
| Alert on co-author claude@users.noreply.github.com on non-Claude-Code commits | Disguise co-author used to blend into repos that use Claude Code | SIEM: GitHub audit git.push events where commit trailer contains claude@users.noreply.github.com | Validated — co-author hardcoded: src/mutator/branch/index.ts:58-63 |
| GitHub Rulesets (Enterprise): block push of .claude/** without approval | .claude/ injection into protected branches | Repo Rulesets > File path restrictions: .claude/** | Reasonable platform guidance — not directly evidenced in repo code |
Layer 5: Secret Management Hardening
The malware sweeps 100+ filesystem hotspots and calls AWS SSM, Secrets Manager, Kubernetes, and HashiCorp Vault APIs directly. All of these providers are validated in source code.
| Control | What It Blocks | Grounded In |
| Never write ~/.aws/credentials; use IAM roles, IRSA, or instance metadata instead | Static AWS credential file harvest | src/providers/filesystem/filesystem.ts — sweeps ~/.aws/credentials explicitly |
| AWS IAM: deny ssm:GetParametersByPath and secretsmanager:ListSecrets except from specific VPC endpoints | Full SSM and Secrets Manager enumeration across all regions | src/providers/aws/ssm.ts:17-93; src/providers/aws/secretsManager.ts:13-180 — sweeps all default regions |
| Kubernetes RBAC: ServiceAccount must not have get/list on secrets cluster-wide; use namespace-scoped roles | All-namespace Kubernetes secret dump | src/providers/kubernetes/kubernetes.ts:166-213 — lists namespaces then reads secrets in each |
| HashiCorp Vault: enable audit logging; token TTL <= 1h; enable max_lease_ttl | Full Vault KV sweep across all mounts (v1 and v2) | src/providers/vault/vault-secrets.ts:195-220 — enumerates all KV mounts and reads all keys |
| Remove gh CLI from CI runner images; use GITHUB_TOKEN env var at minimum scope | ShellService execSync(‘gh auth token’) harvest | src/providers/devtool/devtool.ts:19-29 — explicitly runs gh auth token |
| Encrypt developer home directories (FileVault on macOS, LUKS on Linux) | SSH keys, git credentials, shell history, crypto wallet files at rest | src/providers/filesystem/filesystem.ts — sweeps ~/.ssh, ~/.bash_history, wallet files |
Layer 6: Network Egress Controls
The malware uses a four-level exfiltration fallback chain. Blocking all outbound from CI runners except allowlisted destinations collapses every channel simultaneously.
| Control | What It Blocks | Code Reference |
| Block git-tanstack.com (DNS sinkhole + firewall rule) | Primary C2 exfiltration endpoint | src/index.ts:95-100 |
| Block objects.githubusercontent.com from CI runners | Bun runtime download — required before any payload executes | src/assets/config.mjs:29-44 |
| Block registry.npmjs.org PUT/POST from CI | npm package backdooring via republish | src/mutator/npm/publish.ts:113-159 |
| Block fulcio.sigstore.dev and rekor.sigstore.dev except from dedicated signing infra | Sigstore provenance forgery using victim CI OIDC identity | src/mutator/npmoidc/provenance.ts:227-231; provenance.ts:288-295 |
| Alert on DNS queries to domains registered < 30 days ago from CI runners | Signed-commit C2 discovery — operator can rotate C2 domain via a new commit | src/sender/domain/domainSenderFactory.ts:26-47 |
| TLS inspection on CI egress | AES-256-GCM encrypted exfiltration to C2 (note: malware disables TLS verification on npm publish but not C2) | src/mutator/npm/publish.ts:157; src/index.ts:95-100 |
| Block api.github.com from production systems (not CI) | GitHub commit-based exfiltration fallback | src/sender/github/githubSender.ts:108-121 — this is a context-specific recommendation, not universal |
Layer 7: SIEM Detection Signals
Nine behavioral fingerprints grounded directly in source. The IfYouRevokeThisToken commit message and the dependabot/github_actions/format/* branch pattern are the highest-fidelity signals — they are hardcoded strings in the malware with no operational reason to appear in legitimate automation.
| Signal | Source (Code) | Severity | Detection Query |
| Branch created matching dependabot/github_actions/format/* by non-Dependabot actor | workflow.ts:10-11 | CRITICAL | GitHub audit: ref=branch AND ref LIKE dependabot/github_actions/format/% AND actor != dependabot[bot] |
| Workflow file .github/workflows/codeql_analysis.yml created/modified by non-GitHub actor | workflow.ts:13 | CRITICAL | GitHub audit: action=workflows.created AND path LIKE *codeql_analysis* AND actor != github-advanced-security[bot] |
| Commit message containing IfYouRevokeThisToken | src/utils/config.ts:2-4 | CRITICAL | GitHub audit + git log: message CONTAINS IfYouRevokeThisToken — this string has no legitimate use |
| sudo python3 spawned from npm postinstall or Bun context | runner.ts:39 | CRITICAL | auditd: execve sudo python3 WHERE parent process is node, npm, or bun |
| rm -rf ~/ spawned from bash -s | githubSender.ts:60,89 | CRITICAL | auditd: execve rm -rf WHERE parent is bash with -s flag AND grandparent is bun |
| Commit co-authored by claude@users.noreply.github.com on non-Claude-Code commits | branch/index.ts:58-63 | HIGH | GitHub audit: action=git.push AND commit trailer CONTAINS claude@users.noreply.github.com |
| npm patch version bump with new preinstall: node setup.mjs in same publish | npm/index.ts:68-70 | HIGH | npm publish log: version bump AND preinstall script added in same event |
| Mass branch commits >5 branches, same commit message, within 60 seconds | branch/index.ts:74,181 | HIGH | GitHub audit: action=git.push COUNT > 5 WHERE message=’chore: update dependencies’ WITHIN 60s |
| Outbound DNS/HTTPS to git-tanstack.com from CI runners | src/index.ts:95-100 | CRITICAL | Firewall/DNS: any query or connection to git-tanstack.com from runner IP ranges |
| .claude/ or .vscode/tasks.json added in same commit as binary blob | branch/index.ts:34-40 | HIGH | Git hook or SIEM: commit diff contains .claude/ path AND binary file in same tree |
Hardening Priority Matrix
| Priority | Control | Attack Surface Blocked | Implementation Effort |
| P0 — Critical | ignore-scripts=true in .npmrc | npm lifecycle bootstrap (config.mjs:176) — no execution at all | Minutes |
| P0 — Critical | Block sudo on CI runners | sudo python3 runner dump (runner.ts:39) — entire GitHubRunner harvest path gone | Hours |
| P0 — Critical | Branch protection + signed commits on * (all branches) | ReadmeUpdater persistence pipeline (branch/index.ts:47) — branches become write-protected | Hours |
| P0 — Critical | Egress block: git-tanstack.com + objects.githubusercontent.com | Primary C2 + Bun binary download (config.mjs:29-44) — payload cannot download or phone home | Minutes |
| P1 — High | Fine-grained PATs, no workflow scope, <= 24h expiry | GitHub sender (gitHubSenderFactory.ts:81) + workflow injection | Days |
| P1 — High | Disable bypass_2fa on npm automation tokens | npm backdooring (tokenCheck.ts:9-125) — malware filters for this flag specifically | Hours |
| P1 — High | GITHUB_TOKEN: permissions: read-all as org default | Workflow injection and org secret enumeration | Hours |
| P1 — High | CODEOWNERS for .claude/ and .vscode/ | IDE file injection persistence (branch/index.ts:34-40) | Minutes |
| P2 — Medium | AWS IAM: deny ssm:GetParameter* without VPC endpoint | AWS SSM and Secrets Manager full dump (aws/ssm.ts) | Days |
| P2 — Medium | K8s RBAC: no secrets:list or secrets:get cluster-wide for workloads | All-namespace Kubernetes secret dump (kubernetes.ts:166) | Days |
| P2 — Medium | SIEM rules for all 10 behavioral signals above | Detection after successful compromise | Days |
| P2 — Medium | Vault audit logging + token TTL <= 1h | Vault KV sweep (vault-secrets.ts:195) | Hours |
| P3 — Hardening | TLS inspection on CI egress | Encrypted C2 exfiltration (C2 at git-tanstack.com:443) | Weeks |
| P3 — Hardening | Private npm registry proxy with tarball scanning | Backdoored package distribution; tarball-vs-source divergence detection | Weeks |
The results are clear:
- Bazaarvoice saved $6.3M in developer time and for teams removed critical in the first weeks of adoption
- ClearBank cut critical container vulnerabilities by 96–99% and reclaimed 4 hours per engineer per week.
- A global AdTech company saved an equivalent of 1.5M in development hours and reduced SCA-to-container noise by 82.4%
- Optimizely has been able to act on vulnerabilities sitting on the backlog.
Or learn how Phoenix Security slashed millions in wasted dev time for fintech, retail, and adtech leaders.
Fix with remediation. Don’t chase ghost vulnerabilities
Related Phoenix Security Coverage
- Mini Shai-Hulud: TanStack OIDC Compromise — https://phoenix.security/mini-shai-hulud-teampcp-tanstack/
- Mini Shai-Hulud: SAP CAP/MBT npm Packages — https://phoenix.security/mini-shai-hulud-sap-cap-mbt-npm-supply-chain-bun-credential-stealer/
- Bitwarden CLI: 93-Minute npm Window — https://phoenix.security/bitwarden-cli-backdoored-shai-hulud-returns-through-a-93-minute-npm-window/
- Axios Supply Chain RAT — https://phoenix.security/axios-supply-chain-compromise-npm-rat-2026/
- TeamPCP Telnyx PyPI: WAV Steganography — https://phoenix.security/teampcp-telnyx-pypi-supply-chain-wav-steganography-windows-persistence/
- TeamPCP LiteLLM PyPI — https://phoenix.security/teampcp-litellm-supply-chain-compromise-pypi-credential-stealer-kubernetes/
- TeamPCP: Trivy + Checkmarx + GitHub Actions — https://phoenix.security/teampcp-supply-chain-attack-trivy-checkmarx-github-actions-npm-canisterworm/
- Trivy Supply Chain Compromise — https://phoenix.security/trivy-supply-chain-compromise-teampcp-weaponised-scanner-ongoing-attack/
- Sandworm Mode: Original npm Worm — https://phoenix.security/sandworm-mode-npm-supply-chain-worm/
- Sha1-Hulud v3 — https://phoenix.security/sha1-hulud-v3-npm-supply-chain-attack/
- npm Sha1-Hulud Explained — https://phoenix.security/npm-sha1-hulud-supply-chain-compromise-explained/
External References
- Shai-Hulud Open Source Reference Implementation: https://github.com/g00dfe11ow/Shai-Hulud-Open-Source
- StepSecurity — Mini Shai-Hulud Self-Spreading Supply Chain Attack: https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem
- Palo Alto Unit 42 — npm Supply Chain Attack (Sha1-Hulud): https://unit42.paloaltonetworks.com/npm-supply-chain-attack/
- Kaspersky — Critical Supply Chain Attack: Trivy, LiteLLM, Checkmarx, TeamPCP: https://www.kaspersky.com/blog/critical-supply-chain-attack-trivy-litellm-checkmarx-teampcp/55510/
- Wiz — TeamPCP KICS GitHub Action Compromise: https://www.wiz.io/blog/teampcp-attack-kics-github-action
- Wiz — Mini Shai-Hulud SAP npm: https://www.wiz.io/blog/mini-shai-hulud-supply-chain-sap-npm
- Trend Micro — TeamPCP Telnyx Attack: https://www.trendmicro.com/en/research/26/c/teampcp-telnyx-attack-marks-a-shift-in-tactics.html
- HexaStrike — Telnyx Python SDK Compromise: https://hexastrike.com/resources/blog/threat-intelligence/ringing-in-chaos-how-teampcp-weaponized-the-telnyx-python-sdk/
- SANS ISC — LiteLLM PyPI Compromise: https://isc.sans.edu/diary/32838
- Endor Labs — Mini Shai-Hulud SAP Developer Packages: https://www.endorlabs.com/learn/mini-shai-hulud-npm-worm-hits-sap-developer-packages
- OX Security — Shai-Hulud 170 Packages: https://www.ox.security/blog/shai-hulud-here-we-go-again-170-packages-hit-across-npm-pypi/
- Palo Alto — Bitwarden CLI Supply Chain Attack: https://www.paloaltonetworks.com/blog/cloud-security/bitwardencli-supply-chain-attack/
- Elastic Security — Axios One RAT to Rule Them All: https://www.elastic.co/security-labs/axios-one-rat-to-rule-them-all
- Rami McCarthy — TeamPCP Campaign Overview: https://ramimac.me/teampcp/
- Snyk — Trivy GitHub Actions Supply Chain Compromise: https://snyk.io/articles/trivy-github-actions-supply-chain-compromise/
- The Hacker News — SAP npm Packages Mini Shai-Hulud: https://thehackernews.com/2026/04/sap-npm-packages-compromised-by-mini.html
- CyberScoop — November 2025 Wave: 26,000 GitHub Repositories Exposed
- SafeDep — Shai-Hulud PyPI Crossing via Lightning Package: SafeDep Research Blog