In September 2025 a self-replicating worm tore through the npm registry. It earned the name Shai-Hulud by burrowing from one maintainer to the next: each infected developer machine surrendered its credentials, and the worm used those credentials to republish trojanized versions of every package that maintainer controlled, extending its reach with no central command infrastructure. @nstudio/angular was one of the packages caught in this first wave. Version 20.0.4, published 2025-09-15, shipped with a weaponized package.json whose injected postinstall hook executed a bundled credential harvester the instant anyone ran npm install. The package's prior release line is clean.
Key Findings
- Malicious version: v20.0.4, published 2025-09-15 out-of-band; the prior release line is clean.
- Entry point: an injected
postinstall hook (node bundle.js) fires automatically on npm install, with no user interaction.
- Payload:
bundle.js, a large webpacked JavaScript blob (~3.6 MB) carrying the entire worm, dropped net-new into the package. Several bundle.js variants circulate across the campaign; the exact hash for this package is in the IOC table below, and the shared indicators are the worm's behavior and network endpoints.
- Capabilities: harvests credentials from cloud-metadata (IMDS) endpoints and environment variables, downloads the TruffleHog secret scanner to sweep the host for more secrets, validates stolen AWS credentials, and exfiltrates the results to an attacker-controlled endpoint.
- Worm mechanics: stolen npm tokens are used to self-republish the worm under the victim maintainer's identity, and the GitHub API is abused to spread further -- propagation without any central server.
- Detection: Script-JS.Worm.ShaiHulud, Text.Worm.ShaiHulud.
Package Background
@nstudio/angular has been part of the npm ecosystem since 2019-07-29. Supply chain attackers prize packages like this not for what they do, but for who maintains them and which registries those maintainers can publish to. Stealing a maintainer's npm token at install time is the worm's entire business model: one infected machine unlocks every package that person can publish.
Attack Analysis
The first wave of Shai-Hulud is brazenly simple in its delivery and aggressive in its reach.
package.json is the trigger. The worm injects a single postinstall lifecycle script -- node bundle.js -- into the manifest. npm runs lifecycle scripts automatically during installation, so merely resolving this version on a developer workstation or in a CI pipeline is enough to detonate the payload. This one-line manifest change is the only obvious diff from the legitimate release.
bundle.js is the payload: a ~3.6 MB webpacked JavaScript file added net-new to the package. A self-invoking main() routine drives the whole attack. It reaches out to cloud-metadata (IMDS) endpoints -- 169.254.169.254, 169.254.170.2, and metadata.google.internal -- to lift temporary cloud credentials, enumerates AWS-related environment variables, and downloads the legitimate TruffleHog secret-scanning tool to comb the host filesystem for additional API keys and tokens. Recovered AWS credentials are validated against AWS STS before the worm bundles everything it found and exfiltrates it to an attacker-controlled webhook endpoint. Finally, using any npm tokens it captured, the worm republishes itself into the victim's other packages and abuses the GitHub API to propagate -- the mechanism that turned a single compromise into a registry-wide outbreak.
Because the worm webpacks its payload alongside each package's own build output, several bundle.js variants circulate rather than one constant file; the hash for this package appears in the IOC table below. The reliable campaign-wide indicators are the behavior described above and the network endpoints listed below.
Malicious Files
bundle.js -- the worm payload. A large obfuscated, webpacked JavaScript file executed at install time via the injected postinstall hook. It performs cloud-credential theft (IMDS), environment-variable enumeration, TruffleHog-based secret scanning, AWS credential validation, network exfiltration, and self-propagation through npm and the GitHub API. Classified Script-JS.Worm.ShaiHulud.
package.json -- the weaponized manifest. Its sole malicious modification is the injected postinstall hook that launches bundle.js; the rest is the legitimate package manifest. Classified Text.Worm.ShaiHulud.
Recommendations
- Identify exposure: check whether
@nstudio/angular v20.0.4 appears in your dependency tree, including transitive dependencies. Any pipeline that ran npm install while this version was current should be treated as compromised.
- Rotate credentials immediately: if v20.0.4 was installed on a machine, assume every npm token, GitHub token, cloud credential, and environment secret present at install time was exfiltrated. Revoke and rotate them before anything else, and review cloud audit logs for use of the stolen credentials.
- Audit your npm publishing history: the worm's goal is lateral propagation. If a compromised token had publish rights to other packages, check those packages' recent release history for unexpected versions.
- Pin to a clean release: remove v20.0.4 from all dependency files and lock to a known-good prior version.
- Scan your supply chain: use Spectra Assure Community at https://secure.software/npm/packages/@nstudio/angular to check whether any package in your environment has been flagged as part of this campaign.
Indicators of Compromise
Malicious Files
| Filename | SHA256 | Detection |
|---|
| package.json | 925530e92f2ec1ef0d892cb46a7eac00711996329cd56c051904675c179bd425 | Text.Worm.ShaiHulud |
| bundle.js | dc67467a39b70d1cd4c1f7f7a459b35058163592f4a9e8fb4dffcbba98ef210c | Script-JS.Worm.ShaiHulud |
Network IOCs
| URL / IP | Purpose |
|---|
| webhook.site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7 | Stolen-credential exfiltration endpoint |
| 169.254.169.254 | Cloud-metadata (IMDS) endpoint queried to steal temporary cloud credentials |
| 169.254.170.2 | AWS ECS task-metadata endpoint queried for container credentials |
| metadata.google.internal | Google Cloud metadata endpoint queried for credentials |
| registry.npmjs.org | Abused with stolen npm tokens to self-republish the worm |
| api.github.com | Abused via stolen tokens to propagate through the victim's repositories |
Archive Hashes
| File | SHA256 |
|---|
| angular-20.0.4.tgz | 884b8c9a62eb516295e7afcec733e5acf5a9ee0116c1832a64fc6f230a941874 |