The npm Worm Era: What Shai-Hulud Started, Who’s Continuing It, and How Defenders Should Adapt
September 2025’s self-replicating npm worm rewrote the supply-chain threat model — bundle.js + TruffleHog harvesting secrets, victim-owned GitHub repos as exfil sinks, and stolen npm publish tokens turning every infected developer into a new release vehicle. Six months later, the playbook is the new normal.
- What: Shai-Hulud is a self-replicating npm worm that scans developer and CI environments with TruffleHog, exfiltrates GitHub PATs / npm tokens / cloud keys to public GitHub repos on the victim’s own account, and uses the stolen
npm publishtokens to trojanize further packages the victim owns. - Who is affected: Any organization that installs JavaScript / TypeScript packages from npm into developer workstations or CI/CD runners with ambient cloud and GitHub credentials — especially shops that allow lifecycle scripts during
npm install. - Exploitation status: In the wild since the September 14–15, 2025 wave that began with
@ctrl/tinycolor; 500+ packages ultimately compromised. The TeamPCP “mini Shai-Hulud” SAP campaign in April 2026 reuses the same exfil-to-victim-repo signature, confirming the playbook is generalizing. - Action this week: Run
npm ci --ignore-scriptsin CI, rotate every long-lived GitHub PAT and npm token any developer or runner has touched in the last 30 days, and audit your GitHub org for public repos namedShai-Huludor matching the mini-Shai-Hulud description. - Action this quarter: Move to
npmOIDC trusted publishing with workflow-scoped trust policies (not org-wide), enforce SBOM completeness as a release gate, and add behavioral detection onnpm install→ child-process spawn patterns.
Executive Summary
In September 2025, the npm registry experienced its first true worm campaign. The malware — dubbed “Shai-Hulud” after a string embedded in its payload (a reference to Frank Herbert’s Dune) — began with a single compromised package (@ctrl/tinycolor, ~2.2M weekly downloads at the time of compromise) and propagated automatically across the ecosystem, ultimately infecting more than 500 packages. The mechanism that distinguished it from prior npm supply-chain attacks was structural: the malware did not just steal credentials, it used the stolen npm publish tokens to push trojanized versions of other packages on which the victim had publish rights. Every infected developer or CI runner became a new release vehicle.
The September 2025 wave is, today, table stakes for how npm attacks work. CISA issued a public alert on September 23, 2025, characterizing it as a “widespread supply chain compromise impacting the npm ecosystem.” Palo Alto Unit 42’s npm threat-landscape analysis (most recently updated May 1, 2026) documents how the post-Shai-Hulud landscape has evolved: the worm pattern has been copied (the TeamPCP “mini Shai-Hulud” SAP @cap-js campaign in April 2026), the exfil-to-victim-GitHub-repo signature has generalized across campaigns, and OIDC trusted-publishing misconfigurations have become the new injection vector. For organizations that haven’t rebuilt their npm threat model since 2024, this article is a current-state snapshot of what defenders need to assume and what controls now matter.
@ctrl/tinycolor at the moment it was first trojanized.Technical Analysis
Root cause: lifecycle scripts as a trusted code-execution primitive
npm packages can declare preinstall, install, and postinstall scripts that execute arbitrary code during installation — a feature designed for legitimate build steps (native-binary compilation, platform-specific bootstrapping, asset generation). For an attacker who can publish a package to the registry, this feature is a trusted RCE primitive on every machine that installs the package. The Shai-Hulud worm exploits this property at the population level: not by social-engineering individual targets, but by infecting maintainers’ own machines, harvesting their npm publish tokens, and using those tokens to trojanize the next round of packages. Each infected developer becomes a new release vehicle, which is why the campaign quickly crossed the threshold from incident to worm.
Threat Actor Profile
Attack-chain execution
The end-to-end chain from a single compromised maintainer to ecosystem-wide propagation has six observable phases:
The exfil-to-victim-repo signature is the deepest defensive failure
Traditional supply-chain malware exfiltrates to an attacker-controlled endpoint — a hardcoded C2 domain, an obscure DNS server, an attacker-owned S3 bucket. Defenders blocklist those endpoints; researchers sinkhole them; attribution flows from the C2 fabric. Shai-Hulud broke that model. The exfil sink is the victim’s own GitHub account. Outbound traffic to github.com from a developer workstation or CI runner is universally allowlisted; the API calls are authenticated with the victim’s own valid PAT; the resulting repository looks (until the contents are inspected) like any other personal project. There is nothing to blocklist, nothing to sinkhole, and nothing to attribute — only a description-string IOC (the literal name Shai-Hulud) to hunt for across thousands of developer accounts.
Affected package families (illustrative, not exhaustive)
The September 2025 wave compromised packages across multiple maintainer namespaces. The most consequential by download volume was the @ctrl/* family (a single maintainer’s portfolio of color utilities, torrent clients, Angular helpers, etc., all sharing publish credentials). Documented affected packages include:
- @ctrl/tinycolor — the index case, ~2.2M weekly downloads at compromise.
- @ctrl/deluge, @ctrl/qbittorrent, @ctrl/transmission, @ctrl/shared-torrent, @ctrl/torrent-file, @ctrl/magnet-link — torrent client libraries.
- @ctrl/ngx-codemirror, @ctrl/ngx-csv, @ctrl/golang-template — Angular and templating helpers from the same namespace.
- angulartics2, encounter, and dozens of unrelated packages whose maintainers were caught in subsequent rounds of credential harvest.
The full list is dynamic — published advisories from StepSecurity, Wiz, Aikido Security, Socket, and Snyk contain rolling enumerations as the post-incident triage continues. Treat any installation between approximately September 14, 2025 09:00 UTC and September 22, 2025 of a package from a known-affected namespace as scoped for compromise pending evidence to the contrary.
Impact Assessment
The Shai-Hulud blast radius is wider than the 500+ packages directly trojanized: every developer who ran npm install against one of the affected versions, on a workstation or CI runner that had ambient GitHub or cloud credentials, exported those credentials to an attacker-controlled bundle. CI runners are the worst-case host: they typically hold elevated cloud roles, signing keys, and write-tier repo PATs, and they run unattended. A single trojanized indirect dependency in a build chain — pulled in three levels deep by a transitive resolver — was enough to compromise the runner.
The follow-on impact is the part defenders should still be triaging today. Cloud credentials stolen in September 2025 may still be live in environments that didn’t rotate; GitHub Actions persistence workflows planted in the victim-repo phase keep re-harvesting on every workflow run; npm tokens that weren’t rotated are still capable of pushing further trojanized releases. The campaign’s “cleanup” is necessarily multi-month, not multi-day.
Detection & Response
Shai-Hulud’s detection surface has four layers: the npm install event itself, the TruffleHog/secret-scan behavior on the host, the exfil-repo creation on GitHub, and the GitHub Actions persistence file. Defenders who instrument all four catch the chain; defenders who only watch one will miss most infections.
High-fidelity IOCs
- GitHub repo description / name match (highest fidelity): A public repository named
Shai-Huludon a user account who didn’t create it intentionally. The TeamPCP spinoff uses the description string“A Mini Shai-Hulud has Appeared.” - Injected GitHub Actions workflow: The presence of
.github/workflows/shai-hulud-workflow.yml(or any workflow that wasn’t committed by a known maintainer) in any repo owned by a developer or org. - npm lifecycle-script anomaly:
bundle.jsdropped on disk by a freshly-installed package, especially a package whose prior versions did not ship one. - TruffleHog binary execution from an npm install context: The TruffleHog binary appearing on a developer host or CI runner immediately after an
npm install— particularly when it was not present before. - Outbound
webhook.sitetraffic duringnpm install: hardcoded webhook URLs in some bundle.js variants. - Cleared / mismatched
npmtoken usage: a token publishing packages outside its owner’s historical maintenance pattern.
Illustrative Detection Rules
Rule 1 — GitHub repository creation signature. The deterministic Shai-Hulud (and mini-Shai-Hulud) detection: a freshly-created public repo on a developer account named Shai-Hulud or carrying the mini-Shai-Hulud description string.
Rule 2 — npm install spawns secret-scanning child process. Catches the active-infection moment: node (or npm) executing a child process that looks like a credential scanner. Tuned with a baseline-by-package allowlist so legitimate lifecycle scripts (esbuild, sharp, node-gyp builds) don’t pollute the signal.
Rule 3 — injected shai-hulud-workflow.yml in a Git repo. Catches the GitHub Actions persistence step.
Rule 4 — KQL hunting query: outbound webhook.site from a CI runner or developer host during npm install. Some bundle.js variants exfil to webhook.site URLs in addition to (or instead of) the GitHub repo channel.
Correlation guidance for the SOC analyst:
- Rule 1 alone is high-confidence compromise — investigate the owner’s developer host, rotate every token associated with that GitHub identity, and treat all packages they maintain on npm as scoped for trojanization until proven otherwise.
- Rule 2 followed within 10 minutes by Rule 1 on the same user’s GitHub is the kill-chain signature — the worm is actively running on the developer host and has just exfiltrated. Isolate the host, kill the running
nodeprocess tree, and force-rotate npm credentials before the next package push. - Rule 3 alone may indicate dormant persistence even if the initial infection has been cleaned. Sweep all repos owned by potentially-compromised accounts.
- Time-window matters. The original September 2025 wave concentrated infections in a roughly 48-hour window; spinoffs like TeamPCP’s mini Shai-Hulud in April 2026 had a 2-hour publish window. Correlate any of the rules above against the known campaign timestamps before assuming a routine alert.
Mitigation & Remediation
There is no “patch” for Shai-Hulud in the traditional sense — npm and Yarn are not broken; the registry was abused. Remediation is a combination of operational hygiene (token rotation, scope tightening, lockfile discipline) and behavioral controls (lifecycle-script policy, audit-log monitoring). The matrix below maps the canonical defender actions to urgency tiers so a SOC can sequence the work.
- Hunt before you harden. Audit every GitHub org and developer account for the
Shai-Huludrepo signature and for the mini-Shai-Hulud description string. Any match means you already have a compromise — rotate first, harden second. - Rotate aggressively, not selectively. Don’t try to identify which credentials were exfiltrated. Assume every long-lived token on every machine that has run
npm installin the last 30 days is compromised, and rotate uniformly. - Disable install scripts in CI by default. Use
npm ci --ignore-scripts(orNPM_CONFIG_IGNORE_SCRIPTS=true) and maintain an explicit allowlist of packages that legitimately need lifecycle scripts to build. This is the single highest-leverage control change. - Pin everything, hash-verify everything. Use lockfiles with integrity hashes. Reject floating semver ranges in
package.jsonfor production builds. - Move to OIDC publishing with workflow-scoped trust. Eliminate long-lived
npm publishtokens; the TeamPCP campaign showed that overly-broad OIDC trust policy is the new injection vector, so scope trust to one workflow on one protected branch. - Sweep for the GitHub Actions persistence file. Check every repo on every potentially-affected account for
.github/workflows/shai-hulud-workflow.yml(or any other workflow file that wasn’t committed by a known maintainer). - Instrument the secret-scan behavior. Behavioral EDR rules that alert when
node/npmspawns a child process resembling TruffleHog (Rule 2) catch the worm during execution — before the exfil step completes. - Make SBOMs a release gate, not a release artifact. Fail the release pipeline if any transitive dependency is absent from the SBOM. SBOM completeness gaps are how omitted-but-vulnerable packages slip through.
Strategic Context
For two decades, defenders treated npm as a developer-productivity dependency: a thing that exists, that you patch when it has a CVE, that you don’t expose to the internet. Shai-Hulud forced a different framing. The registry itself is now an authenticated, attacker-reachable command-and-control fabric. A maintainer’s laptop is a foothold from which trojanized releases can reach millions of downstream installers in hours. A CI runner is a credential vault where every install operation is implicitly trusted code execution. There is no version of npm that fixes this; there are only operational practices that constrain it.
The TeamPCP “mini Shai-Hulud” campaign in April 2026 made the generalization explicit: the original wave’s tradecraft — secret-scan-with-TruffleHog, exfil-to-victim-owned-GitHub, self-propagate-via-stolen-publish-tokens — is now a playbook other actors are running, against other ecosystems and other supply chains. Defenders who built their npm threat model in 2024 are reasoning about a fundamentally different attack surface than the one they have today.
The dev host is the new endpoint — and behavior, not signature, is the only viable defense
Every static control that worked against pre-2025 npm threats fails against Shai-Hulud. There is no malicious IP to blocklist (exfil is to github.com). There is no malicious hash to ban (every bundle.js is freshly minified per release). There is no malicious package list to deny (the trojanized version inherits the legitimate package name). SBOM scanners report “package present, version pinned, no known CVE” while the worm is actively harvesting secrets.
What works is behavioral protection at the developer and CI endpoint: monitoring the process-spawn tree, the file-write pattern, the outbound-network pattern, and the GitHub audit log for the specific behavioral signatures the worm produces — npm install spawning TruffleHog, fresh shai-hulud-workflow.yml commits, a brand-new public repo named Shai-Hulud on a developer account. Karma-X is built around exactly this thesis: behavioral detection at machine speed on the systems that traditionally have no agent and where every install operation is trusted code execution.
Related Karma-X Coverage
- TeamPCP / “mini Shai-Hulud” SAP
@cap-jscampaign (April 2026). Direct lineage: the spinoff that adopted the original wave’s exfil-to-victim-repo signature and added an npm-OIDC-trusted-publishing misconfiguration vector. Read the Karma-X analysis. - Cisco Catalyst SD-WAN (UAT-8616) firmware-downgrade-and-restore persistence (May 2026). Different vector, same architecture: persistent attacker presence in the management plane via behavioral controls that traditional FIM is blind to. Read the Karma-X analysis.
- Frontier AI & the supply-chain attack family (Q1 2026 wave). LiteLLM, Axios, CPU-Z, ChatGPT sandbox — the broader Q1 2026 supply-chain incident pattern that culminates in Shai-Hulud-class worms. Read the Karma-X analysis.
Timeline
@ctrl/tinycolor. Trojanized version published to npm; bundle.js + TruffleHog payload begins secret harvest on every install.@ctrl/*, angulartics2, rxnt-*, and others). Public “Shai-Hulud” repos begin appearing on victim accounts.bundle.js payload, and the GitHub-repo exfil signature.@cap-js and mbt packages compromised via an over-broad npm OIDC trusted-publishing policy; exfil to victim-owned GitHub repos with the description string “A Mini Shai-Hulud has Appeared.” Over 1,100 victim repos created.Sources & References
- Palo Alto Networks Unit 42. The npm Threat Landscape: Attack Surface and Mitigations (Updated May 1, 2026). Link
- CISA. Widespread Supply Chain Compromise Impacting npm Ecosystem (September 23, 2025). Link
- Titan Layer. The npm Threat Landscape: Attack Surface and Mitigations. Link
- NDSS 2025. JBomAudit: Assessing the Landscape, Compliance, and Security Implications of Java SBOMs (cited as parallel evidence; Java-ecosystem study). Link
- StepSecurity, Wiz, Aikido Security, Socket, and Snyk — coordinated September 2025 disclosures of the Shai-Hulud worm. Linked from the Unit 42 and CISA references above.