A self-propagating supply chain worm turns each developer or CI credential it steals into the means of publishing its next infection, so one compromised maintainer can poison hundreds of packages in hours. Since Shai-Hulud hit npm in September 2025 the pattern has repeated across registries and tools: GlassWorm in VS Code extensions, CanisterSprawl and ChainDrop on npm, and Miasma and Hades across npm, PyPI and GitHub. This guide treats registry worms as a threat class rather than a single campaign. It explains the propagation loop they share, compares the known families, and sets out the controls that break each link: install-time execution, credential exposure, publishing rights and release workflows. It is for platform, AppSec, DevOps and identity teams who consume or publish open source packages. For the detail of one family see the Shai-Hulud npm Worm Guide, and for incident handling the Malicious Package Worm Response Playbook.
Key takeaways
- Worms spread on identities, not exploits. Every family so far propagates by authenticating to a registry as the victim with a stolen publishing token or a hijacked CI identity, then republishing the victim's other packages.
- Install time and open time are execution time. Lifecycle scripts, native build files, Python
.pthfiles and AI coding tool trigger files all run code before anyone has used the package. - Trusted publishing narrows the loop but does not close it. ChainDrop and Miasma published through OIDC trusted publishing after taking over maintainer GitHub accounts: provenance proved the build path, not the honesty of the identity behind it.
- Containment is a credential problem. CISA's first advice for Shai-Hulud was to rotate all developer credentials and require phishing-resistant MFA, not just to remove a package version.
- Delay and isolate. A cooldown before adopting new versions, install scripts disabled in CI and release jobs separated from jobs that install third-party code defeat most waves seen to date.
What makes a supply chain worm different
A conventional malicious package waits to be installed. A worm adds a propagation step: once it runs on a maintainer's laptop or a CI runner, it looks for credentials that can publish, uses them to inject itself into every package those credentials can write to, and publishes new versions. Each new version infects more installers, some of whom are maintainers too. The Computer Worms Guide covers network worms; registry worms differ because their "network" is the dependency graph and their "exploit" is a valid credential.
The loop has five links, and every control in this guide breaks one of them:
- Entry: a maintainer account, token or CI workflow is compromised (phishing, a leaked token, an earlier infection).
- Execution: the trojanised version runs code automatically at install, build or repository open.
- Harvest: the payload collects publishing tokens, Git hosting tokens, cloud keys and CI secrets from files, environment variables, metadata endpoints and runner memory.
- Propagation: stolen publishing rights are used to enumerate and republish the victim's packages.
- Exfiltration and persistence: secrets leave through public repositories, webhooks or takedown-resistant channels, and backdoor workflows or runners keep access.
Known families compared
The table summarises the campaigns NHI Mgmt Group has analysed. Families are listed by first appearance.
| Family | When | Ecosystems | Execution trigger | Propagation credential | Distinctive trait |
|---|---|---|---|---|---|
| Shai-Hulud (ATT&CK S9008) | From September 2025; second wave November 2025 | npm, GitHub | postinstall, then preinstall | npm tokens; GitHub PATs for exfiltration | CISA reported more than 500 packages compromised in the first wave; secrets dumped to public repositories named Shai-Hulud |
| GlassWorm | From October 2025, waves to April 2026 | Open VSX, VS Code Marketplace, npm | Extension activation | Leaked extension publishing tokens, then harvested npm, GitHub and Open VSX credentials | Invisible Unicode code; command addresses read from Solana transactions |
| Mini Shai-Hulud (S9043) | 2026 | npm, GitHub Actions | Install hooks, AI agent configuration hooks | Short-lived CI identity tokens scraped from runner memory | ATT&CK attributes it to TeamPCP (G1056) |
| CanisterSprawl | April 2026 | npm, PyPI | postinstall ending in || true; Python .pth | Any npm publish token found; PyPI credentials | Versions published without matching git tags; exfiltration to an Internet Computer canister |
| Miasma and Hades | 1 to 7 June 2026 | npm, PyPI, GitHub | preinstall, binding.gyp, .pth, AI tool trigger files | Hijacked GitHub account pushing to repos whose CI used trusted publishing | Reached Microsoft and Red Hat repositories; GitHub disabled 73 repositories |
| ChainDrop | 4 August 2026 | npm | Install-time payload | Maintainer GitHub takeover, project release workflow via trusted publishing, then stolen npm tokens | 444 packages and 2,212 malicious versions in about four hours; Ethereum contract for command addresses |
Read the incident analyses: Shai-Hulud npm Worm 2025, GlassWorm Campaign 2025, CanisterSprawl npm Worm 2026, Miasma and Hades Worms 2026 and ChainDrop npm Worm 2026. The Top 10 Supply Chain Attacks page puts worms alongside the other supply chain attack types.
How worms map to ATT&CK
The techniques below are recorded in ATT&CK v20 against Shai-Hulud and Mini Shai-Hulud and recur across the other families.
| Loop link | Technique | Control that breaks it |
|---|---|---|
| Entry | T1195.001 Compromise Software Dependencies and Development Tools | Release cooldown, lockfiles, phishing-resistant MFA for maintainers |
| Execution | T1546.016 Installer Packages; T1059.007 JavaScript | Install scripts disabled in CI, allow-list for the few packages that need build steps |
| Harvest | T1552.001 Credentials In Files; T1528 Steal Application Access Token | No long-lived tokens on disk or in environment variables; short-lived, workload-bound credentials |
| Propagation | T1550.001 Application Access Token | Trusted publishing, tokens disallowed for publishing, single-package scoping, release approval |
| Persistence in pipelines | T1677 Poisoned Pipeline Execution | Branch protection, code owner review of workflow files, alerts on new self-hosted runners |
| Exfiltration | T1567.001 Exfiltration to Code Repository | Org policy blocking public repository creation and visibility changes; egress allow-lists on runners |
| Impact | T1098 Account Manipulation; T1485 Data Destruction | Audit log alerts on visibility flips; workstation backups |
Breaking the execution link: install-time and open-time code
- Disable lifecycle scripts in CI: Set npm's
ignore-scriptsfor dependency installs in CI and maintain an explicit allow-list of packages that genuinely need a build step. Recent package managers increasingly block dependency build scripts by default; check yours. See the npm Install-Time Security Guide. - Remember the other triggers: Miasma used
binding.gypto run during native builds, CanisterSprawl and Hades dropped Python.pthfiles that execute at interpreter start-up, and several families planted AI coding tool configuration that runs when a repository is opened. Scanning onlypackage.jsonscripts misses these. - Install in a throwaway environment: Run dependency resolution and builds in ephemeral containers with no credentials mounted, then pass only the built artefact to later stages.
- Control editor extensions: Use an allow-list and pinned versions for IDE extensions, as GlassWorm showed that extension marketplaces carry the same risk as package registries. See the GlassWorm VS Code Extension Worm Guide.
Breaking the harvest and propagation links: publishing identities
A publishing credential is both the worm's loot and its engine, so the strongest single control is making publishing rights unreachable from any environment that runs third-party code.
| Publishing approach | What a worm can do with it | Residual risk |
|---|---|---|
Long-lived token on a laptop (.npmrc, .pypirc) | Read it and republish every package it covers | Highest: the first-wave Shai-Hulud and CanisterSprawl path |
| Granular token, single package, short expiry | Republish one package until expiry | Limits blast radius; still stealable |
| Local publish with 2FA required, tokens disallowed | Cannot publish without the second factor | Phishing of TOTP codes; prefer FIDO-based 2FA |
| Trusted publishing (OIDC from CI) | Only by running inside the authorised workflow, or by taking over the account that can change it | Account takeover and poisoned commits (ChainDrop, Miasma); runner memory scraping (Mini Shai-Hulud) |
| Trusted publishing with protected environment and human approval | Must also get a reviewer to approve the release | Lowest; costs release speed |
npm's trusted publishing uses OIDC from GitHub Actions, GitLab CI/CD and CircleCI cloud runners (self-hosted runners are not supported at the time of writing) and needs npm CLI 11.5.1 or later. After it works, npm recommends selecting "Require two-factor authentication and disallow tokens" under the package's publishing access settings. GitHub's September 2025 plan for npm set out local publishing with required 2FA, granular tokens limited to seven days, trusted publishing, deprecation of classic tokens and a move from TOTP to FIDO-based 2FA. PyPI offers an equivalent trusted publisher model. See the npm Trusted Publishing and Provenance Guide, the PyPI API Tokens and Trusted Publishing Security Guide and the Package Registry Security Comparison Guide.
- Bind OIDC trust narrowly: Trust one repository, one workflow file and one protected environment with required reviewers, not "any workflow in the organisation".
- Separate release from build: The job that holds publishing identity should not run
npm installon untrusted dependencies; build in one job, publish the artefact from another. - Protect the human account: Phishing-resistant MFA, session revocation after any suspected compromise, and code owner review on release workflow files. Provenance confirms where a package was built, not whether the person who triggered the build was legitimate.
- Minimise runner secrets: Inject only the secrets a job needs, prefer cloud OIDC federation to stored keys, and require IMDSv2 on cloud runners.
Breaking the entry link for consumers
- Cooldown before adoption: Do not resolve versions younger than a set age (for example 3 to 7 days) in builds. Most worm versions were detected and removed within hours or days. Several package managers and dependency bots now offer a minimum release age setting.
- Lockfiles and pinning: Commit lockfiles, use
npm cirather thannpm installin CI, and pin to known-good versions during an active campaign, as CISA advised. - Proxy through a curated repository: An internal artefact repository lets you block a version once for the whole organisation and search cached copies, another CISA action.
- Watch for anomalies in what you consume: New versions without a matching git tag (the CanisterSprawl tell), sudden maintainer changes, new install scripts and unexpected native build files.
Implementation: a phased programme
- Inventory (week 1 to 2): List every package your organisation publishes, every token that can publish them, every IDE extension in use, and every CI workflow with publishing or cloud rights. See the npm Tokens Security Guide.
- Remove stored publishing tokens (week 2 to 6): Move releases to trusted publishing, then disallow tokens. Pilot on low-download packages first; roll back by re-enabling a short-lived granular token if a release is blocked.
- Disable install scripts in CI (week 3 to 8): Start in report mode by logging which packages run scripts, build the allow-list, then enforce. Keep the previous runner image available to revert.
- Introduce a cooldown (week 4 to 8): Apply the minimum release age to automated dependency updates first, then to all resolution, with a documented override for urgent security fixes.
- Harden release workflows (week 6 to 10): Protected environments, required reviewers, code owners on
.github/workflows, and organisation policy blocking public repository creation by members. - Exercise (quarterly): Run a tabletop on a worm hitting one of your maintainers, timing how long it takes to revoke every token they hold.
Small versus large organisations: A small team that only consumes packages gets most protection from lockfiles, a cooldown and scripts off in CI. Organisations that publish widely used packages need the full publishing controls, because they are the propagation path for everyone downstream.
Detection and measurement
- Monitor: registry publish events for your packages without a matching tag or workflow run; new public repositories or visibility changes in your Git organisation; new self-hosted runners; new or changed workflow files; outbound connections from runners to webhook services (CISA advised blocking webhook.site), blockchain RPC endpoints or unknown domains; secret scanning alerts on your organisation.
- KPI: publishing tokens outstanding. Count of long-lived tokens that can publish your packages; target zero.
- KPI: trusted publishing coverage. Share of published packages released only through OIDC with tokens disallowed.
- KPI: CI installs with scripts enabled. Share of pipeline dependency installs that still run lifecycle scripts.
- KPI: cooldown coverage. Share of builds that enforce a minimum release age.
- KPI: time to revoke. Minutes from a maintainer compromise alert to revocation of every token and session they hold.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | SR-3 Supply Chain Controls and Processes; SR-4 Provenance; SR-11 Component Authenticity | Controlling and verifying what enters the build |
| NIST SP 800-53 Rev. 5 | IA-5 Authenticator Management; SA-10 Developer Configuration Management; CM-7 Least Functionality; SI-7 Software, Firmware, and Information Integrity | Publishing credentials, release workflow control, scripts off, integrity checks |
| NIST CSF 2.0 | GV.SC-07; PR.AA-01; PR.PS-02; DE.CM-09 | Supplier risk, credential management, software maintenance, monitoring of runtime environments |
| MITRE ATT&CK v20 | T1195.001, T1546.016, T1552.001, T1528, T1550.001, T1677, T1567.001 | Worm behaviour, see the table above |
Common failure patterns
- Removing the bad version and stopping: The tokens the worm stole remain valid, so the next wave publishes again. Rotate every credential reachable from the infected host, and clean the host before rotating so new secrets are not stolen too.
- Treating provenance as proof of safety: ChainDrop's malicious keyv release came from the project's own release workflow through trusted publishing after a maintainer GitHub account takeover.
- Publishing from the same job that installs dependencies: Any install-time payload in that job can reach the publishing identity, the path Mini Shai-Hulud used by scraping runner memory.
- Leaked tokens in public code: GlassWorm began with extension publishing tokens developers had committed to public repositories. Run secret scanning on every repository and treat a leaked publishing token as a compromise.
- Watching only npm scripts:
.pthfiles, native build files and AI tool configuration triggered execution in later waves. - Organisation-wide OIDC trust: A trusted publisher bound to any workflow or branch lets one compromised contributor publish.
Practitioner checklist
- Inventory every package you publish and every credential that can publish it, and remove long-lived publishing tokens.
- Move releases to trusted publishing bound to one workflow and a protected environment with reviewers, then disallow tokens.
- Require phishing-resistant MFA on registry and Git hosting accounts for all maintainers.
- Disable dependency lifecycle scripts in CI with an explicit allow-list, and check for
.pth, native build and AI tool trigger files. - Enforce a minimum release age on dependency resolution and automated update bots.
- Separate build jobs that install third-party code from release jobs that hold publishing identity.
- Block runner egress except to required registries, and alert on webhook services and new public repositories.
- Allow-list and pin IDE extensions.
- Rehearse revoking every token and session for one maintainer and measure the time it takes.
Standards and references
- CISA: Widespread Supply Chain Compromise Impacting npm Ecosystem
- npm Docs: Trusted publishing for npm packages
- GitHub: Our plan for a more secure npm supply chain
- MITRE ATT&CK: Shai-Hulud (S9008)
- MITRE ATT&CK: Mini Shai-Hulud (S9043)
- MITRE ATT&CK: Compromise Software Dependencies and Development Tools
- NIST: SP 800-53 Rev. 5
Related NHI Mgmt Group resources: Shai-Hulud npm Worm Guide · Malicious Package Worm Response Playbook · npm Trusted Publishing and Provenance Guide · Top 10 Supply Chain Attacks