Shai-Hulud: The Self-Propagating npm Worm Stealing Cloud Credentials at Scale

Shai-Hulud: The Self-Propagating npm Worm Stealing Cloud Credentials at Scale

The package you installed yesterday may already be publishing your AWS keys to a public GitHub repo

Between April 21 and April 23, 2026, the npm ecosystem absorbed three near-simultaneous supply-chain hits: a backdoored release of axios (~100M weekly downloads), a malicious pgserve package, and a typosquat of @bitwarden/cli published April 22 by an actor cluster Palo Alto Unit 42 tracks as TeamPCP. CISA published an axios-specific alert on April 20, then expanded scope two days later as researchers correlated the three campaigns into one operation.

What links them is not a dropper, an exploit kit, or even a particularly novel C2 channel. It is a behavior pattern the security community has theorized about for a decade and now has live samples for: a self-propagating npm worm, internally codenamed Shai-Hulud by Unit 42 after the package metadata strings the operators left behind. Every developer it infects becomes a publisher. Every pipeline it lands in becomes a launchpad. And the prize it is hunting is not source code — it is your cloud control plane.

This is the threat-intel deep-dive for everyone running CI/CD against AWS, Azure, or GCP, which today is essentially everyone. We will walk through the mechanics, the indicators, the affected packages, and the detection and response steps that actually move the risk needle.

How Shai-Hulud actually moves

The worm runs in five clearly distinguishable stages. None of them are individually novel — what is novel is welding them into a single autonomous loop.

Stage 1 — Foothold via postinstall. Each variant ships a postinstall hook in package.json that fires the moment a developer runs npm install. There is no user prompt, no interactive step, no SCA tool flag (the packages were not yet on any threat list when the malicious versions shipped). The hook decodes a base64 blob, drops a small JavaScript runtime under ~/.cache/.bw-helper/ on Unix and %APPDATA%\bw-helper\ on Windows, and execs it.

Stage 2 — Local secret harvesting. The runtime walks a defined list of cloud credential locations:

  • ~/.aws/credentials, ~/.aws/config, all AWS_* environment variables
  • ~/.azure/, Azure CLI cached tokens, AZURE_* environment variables
  • ~/.config/gcloud/, GCP service-account JSON keys, GOOGLE_APPLICATION_CREDENTIALS
  • .env, .envrc, .npmrc, ~/.docker/config.json
  • GitHub CLI tokens at ~/.config/gh/hosts.yml

If the host is a CI runner, it additionally reads /proc/self/environ to scrape secrets that orchestrators (GitHub Actions, GitLab CI, CircleCI) inject as environment variables. This is how supposedly-scoped CI secrets leak.

Stage 3 — Exfiltration to public GitHub. The harvested loot is AES-encrypted with a hardcoded public key, then committed as a benign-looking JSON file to a public attacker-controlled GitHub repo using a burner Personal Access Token embedded in the binary. Public repos make takedown harder and analyst attribution noisier; the encryption ensures only the operators can read what was stolen, even after researchers find the dead drop.

Stage 4 — npm publish-token discovery. The runtime parses ~/.npmrc for _authToken entries and queries the npm registry’s /-/whoami and /-/user/<name>/package endpoints to enumerate every package the developer can publish. This is the mechanism that turns one infection into many.

Stage 5 — Self-republishing. For each writable package, the worm pulls the current version, embeds itself into the postinstall script, bumps the patch version (e.g., 4.2.74.2.8), and runs npm publish. Version-pinning consumers who run npm update or npm ci against a ^4.2.x range pull the trojaned release on the next install. The cycle starts again.

There is no human in this loop. A junior dev pulling a malicious package on their laptop today can put a poisoned release into production tomorrow without anyone — including the worm operators — manually doing anything.

Indicators of compromise

Defenders and IR teams should hunt for the following:

Filesystem. Presence of ~/.cache/.bw-helper/, %APPDATA%\bw-helper\, or any *.bw-helper.* artifact. A mid-day modification timestamp on ~/.npmrc on a developer host that did not knowingly authenticate to npm.

Network. Outbound HTTPS to GitHub repos with names matching ^[a-z]{8,12}-temp-store-[0-9]{4}$ from developer hosts or CI runners. Unexpected api.github.com traffic from CI jobs that do not normally interact with GitHub.

Cloud control plane. New STS:GetSessionToken or STS:AssumeRole calls from IPs not previously associated with the principal. Cross-cloud anomalies: an AWS access key suddenly used to enumerate IAM, then an Azure CLI token from the same developer used to list tenants minutes later. gcloud auth list invocations from a CI runner that has never run them before.

Registry. A patch-version bump on a package you maintain that you did not push. Any npm publish event in audit logs without a corresponding CI job.

Affected versions confirmed compromised (not exhaustive, check vendor advisories for the canonical list):

  • axios 1.7.x malicious window (April 22 release window — see CISA advisory)
  • @bitwarden/cli@2026.4.0 (typosquat — Bitwarden’s official CLI is @bitwarden/cli published by the bitwarden org; the malicious clone used a similar but distinct publisher)
  • pgserve@0.x (April 21 release)

Impact assessment

Severity: Critical. The combination of cloud credential harvesting, public GitHub exfiltration, and worm-style propagation means a single infected developer can cascade into hundreds of downstream packages within 24-48 hours. Once cloud keys are in attacker hands, the blast radius shifts from “code repo” to “production cloud account.”

Affected populations: Any organization using npm in development or CI workflows. The risk concentrates on teams that:

  • Cache long-lived cloud credentials on developer laptops
  • Use static (non-OIDC) cloud secrets in CI
  • Permit unscoped npm publish tokens in build agents
  • Skip integrity verification (npm ci --ignore-scripts is not the default)

Likely attacker objectives: Credential-as-a-service resale, ransom of cloud accounts via S3/Blob storage encryption, cryptojacking on stolen GCP/AWS quota, and lateral pivoting into customer tenants of compromised SaaS providers.

Risk rating: Tier 1 (Critical) for any cloud-native engineering organization. CISA has placed the axios component on the Known Exploited Vulnerabilities list with a federal agency patch deadline of May 4, 2026.

The CloudShieldSecure perspective

Shai-Hulud is precisely the kind of threat that exposes the gap between static supply-chain scanning and runtime cloud-context detection. SBOMs and dependency scanners catch the bad package only after it is on a threat-intel feed, which is hours-to-days behind the worm. By then your CI runner has already executed the postinstall hook and shipped your AWS keys.

CloudShieldSecure approaches this layer differently. The platform watches the cloud control plane — the place where stolen credentials must eventually surface to be useful — for the behavioral signatures that matter:

  • Cross-cloud lateral movement. When an AWS access key is used from a new geography, then an Azure CLI token from the same engineer’s machine surfaces in a different region within minutes, that is a worm pattern, not a developer pattern.
  • Anomalous npm publish from CI. Build pipelines have predictable publish cadences. A push from a CI runner outside that pattern, or one that touches a package the runner has never published before, gets flagged before consumers pull it.
  • Outbound-to-GitHub-from-non-developer-hosts. Production servers, container hosts, and Kubernetes nodes have no business committing to public GitHub repos. Egress patterns matching the worm’s exfiltration template are surfaced as high-priority alerts.
  • STS token velocity anomalies. A sudden burst of GetSessionToken calls from a principal that normally averages two per day is a leading indicator of credential resale or automation harvesting.

The thesis is straightforward: assume the worm will eventually slip past your dependency scanners, and make sure the cloud notices the moment it tries to use what it stole.

For engineering leaders and security teams, the practical checklist:

  1. Audit ~/.npmrc on every developer machine and CI runner. Replace any long-lived _authToken with short-lived OIDC-issued tokens scoped to the specific package.
  2. Rotate all cloud credentials cached on developer laptops in the last 14 days. Yes, all of them. The cost of rotation is lower than the cost of an undetected breach.
  3. Pin npm dependencies and run npm ci --ignore-scripts in CI for the next 30 days, then revisit. This blocks the postinstall vector at the cost of some package functionality you can re-enable selectively.
  4. Move CI cloud auth to OIDC federation. GitHub Actions, GitLab CI, and CircleCI all support short-lived credential exchange. Static cloud keys in CI are now a known-bad pattern.
  5. Add allow-list egress on CI runners. They should reach your registry, your cloud control plane, and almost nothing else. Outbound to arbitrary GitHub repos is the smoke that gives away the fire.
  6. Hunt for the worm artifacts listed in the IOC section across developer fleets and runners. EDR rules for bw-helper paths and outbound-to-burner-GitHub-repo patterns are straightforward to deploy.
  7. Subscribe to CISA KEV updates. The axios entry was added April 20; expect more npm packages to follow as researchers complete their tracebacks.

A worm cannot be ignored into stopping. Every day you delay rotation is another patch-version bump in the wild.

Sources and references


CloudShieldSecure is the cloud-native threat detection and posture platform built by CloudKonsult Limited. We help engineering teams catch the cloud-side of supply-chain attacks the static scanners miss. Learn more at cloudshieldsecure.cloudkonsult.cloud.

Assess your security posture today

CloudShield Secure scans, validates, and prioritises threats across your entire attack surface.

Explore CloudShield Secure →
← 5 Kubernetes RBAC Misconfigurations Attackers … When Your Endpoint Goes Quiet: Detecting Defender … →