Supply Chain Security

The Bitwarden CLI Supply Chain Hijack: 90 Minutes, Shai-Hulud's Third Coming, and the SMB Dev Pipeline Playbook

On April 22, 2026, between 5:57 PM and 7:30 PM Eastern time, the official @bitwarden/cli package on npm was a credential stealer. For roughly ninety minutes, every npm install -g @bitwarden/cli, every CI/CD pipeline auto-pulling the latest version, every dev workstation that ran an unpinned install pulled down version 2026.4.0 — a poisoned release that fetched the Bun runtime, executed an obfuscated loader, and harvested AWS, Azure, GCP, GitHub, npm, SSH, and AI-tooling credentials from the host. The package draws roughly 70,000 weekly and 250,000 monthly downloads, per BleepingComputer. The worm called itself "Shai-Hulud: The Third Coming."

Bitwarden's vault data was not touched. Production systems were not breached. The thing that broke was the build pipeline. And that is exactly why this matters for every SMB shipping software, running an internal Node service, or letting developers npm install on their laptops.

What actually happened, in order

The clearest forensic write-ups come from Ox Security, Cremit, StepSecurity, and Endor Labs. Stitched together, the attack chain looked like this:

  1. Initial foothold (upstream). Attackers compromised a developer tool tied to Checkmarx's ecosystem — a continuation of the Shai-Hulud campaign that previously hit Trivy and LiteLLM. The threat actor cluster is tracked publicly as TeamPCP.
  2. GitHub Actions abuse. Using stolen credentials, attackers modified workflows in Bitwarden's CI/CD environment so that a release job published @bitwarden/cli@2026.4.0 to npm with a malicious preinstall hook. The upstream source code on GitHub was not the same code that got published — that gap is the whole point.
  3. Stage-one dropper. The preinstall hook ran bw_setup.js, which downloaded the Bun JavaScript runtime outside the Node.js process. Bun was chosen specifically to evade Node-based EDR hooks and runtime monitors.
  4. Stage-two stealer. Bun executed an obfuscated bw1.js that scanned the host for AWS keys, Azure tokens, GCP service accounts, GitHub personal access tokens, npm publish tokens, SSH private keys, shell history, environment variables, and — notably — MCP server configurations and AI agent credential files. The stealer skipped execution if the system language was set to Russian.
  5. Worm propagation. Stolen npm tokens were used to push new poisoned versions of any package the victim could publish. Loot was AES-256-GCM encrypted, then exfiltrated by creating public GitHub repositories under the victim's account with descriptions containing the string "Shai-Hulud: The Third Coming."

npm pulled the malicious version after about ninety minutes. Bitwarden published a statement confirming the npm distribution path was compromised but the vault, the production infrastructure, and the desktop and mobile clients were not.

Why this one is different — and why SMBs should care

Three things make this incident more dangerous than the average npm typosquat:

It was a real, signed package from a real, trusted vendor. Static reputation checks ("is this package from a known org? does it have a million downloads?") all returned green. The blast radius is anyone who trusted Bitwarden's name — which, for a password manager CLI, is essentially the entire security-conscious developer population.

The payload specifically targets the modern AI-augmented dev stack. The stealer enumerates MCP server configs (the connector files that let Claude, Cursor, and other AI agents reach into your environment) and AI agent credential stores. If your engineers use Cursor, Claude Code, Continue, or any MCP-enabled tooling, those tokens were on the menu. This is the first widely-reported supply chain attack that explicitly weaponizes AI tooling artifacts.

The worm exfiltrates through public GitHub repos. That means every compromised victim is also a public IOC. If anyone in your org has a brand-new public repo with the words "Shai-Hulud" in the description created on April 22 or later, you have a confirmed compromise.

The 30-minute SMB triage

If you are reading this on May 16 and you have not yet checked, do the four things below in order. This whole sequence should take a junior engineer less than half an hour.

1. Hunt for the malicious version in your environment

Run this across every dev laptop, build server, and container image:

# Find every package-lock.json or yarn.lock that pinned the bad version
grep -rE '"@bitwarden/cli"\s*:\s*"2026\.4\.0"' \
  ~/ /opt /var/lib/jenkins /home 2>/dev/null

# On Windows / PowerShell
Get-ChildItem -Path C:\,D:\ -Recurse -Include package-lock.json,yarn.lock `
  -ErrorAction SilentlyContinue |
  Select-String -Pattern '"@bitwarden/cli":\s*"2026\.4\.0"'

Any hit is a confirmed install of the malicious version. Any host that hit it after 5:57 PM ET April 22 and before 7:30 PM ET April 22 ran the stealer.

2. Hunt the public-repo IOC across your GitHub org

The cleanest indicator is unexpected public repos created by org members on or after April 22. Use the GitHub CLI:

# Replace ORGNAME with your org
gh api -X GET /search/repositories \
  -f q='org:ORGNAME "Shai-Hulud" in:description created:>=2026-04-22' \
  --jq '.items[] | {full_name, created_at, description, html_url}'

# Also hunt across personal accounts of engineers
gh api -X GET /search/repositories \
  -f q='"Shai-Hulud: The Third Coming" in:description' \
  --jq '.items[] | {full_name, owner: .owner.login, created_at, html_url}'

If anything returns, treat the owning account as compromised: rotate every credential that account had, audit every repo the account could push to, and pull the public exfil repo for forensics before deleting it.

3. Rotate the secrets that were on disk

If any dev host ran the bad version, assume the following are burned and must be rotated:

  • All AWS, Azure, and GCP access keys, including IAM user keys, service principal secrets, and service account JSON files on the host.
  • All GitHub personal access tokens, deploy keys, and SSH keys present in ~/.ssh.
  • All npm publish tokens (~/.npmrc) — and audit every npm package the developer could publish for unexpected new versions.
  • Any API keys or tokens stored in shell history, environment variables, .env files, or ~/.config.
  • MCP server config files and AI agent credential files — review every .cursor, .claude, and .continue directory and the MCP servers they reference.

4. Pin and audit going forward

Add the malicious version to a deny list in your CI:

# package.json
{
  "overrides": {
    "@bitwarden/cli": "<2026.4.0 || >2026.4.0"
  }
}

Then turn on the controls you should have had on April 21.

The SMB dev pipeline playbook

The triage above closes this incident. The playbook below closes the next ten. None of it requires enterprise tooling.

Pin everything in CI. Use npm ci, not npm install, in every pipeline. Commit your package-lock.json. For high-trust packages, pin to exact versions with integrity hashes (--save-exact and npm install --package-lock-only to capture "integrity": "sha512-..."). A pinned hash would have rejected the malicious 2026.4.0 entirely.

Add a cooldown. Configure your registry proxy (Verdaccio, GitHub Packages, Artifactory) to refuse any new package version less than 48 hours old. Roughly every npm supply-chain incident in the last 18 months — Shai-Hulud included — has been remediated within 4 hours of disclosure. A 48-hour cooldown turns "we were patient zero" into "we read the postmortem."

Sandbox preinstall hooks. Set ignore-scripts=true in .npmrc by default, or run installs inside ephemeral containers with no network egress and no credential mounts. The Bitwarden stealer required the preinstall hook to run; sandboxing kills the attack in stage one.

Short-lived, scoped credentials only. No long-lived AWS keys on dev laptops. Use AWS IAM Identity Center, Azure managed identities, or GCP workload identity federation with sessions under eight hours. If a token expires before exfiltration is monetized, you have downgraded a breach to a near-miss.

Egress controls on build runners. Self-hosted GitHub Actions runners and Jenkins agents should only talk to npm, the package registries you use, your VCS, and your artifact store. Block everything else by default. The Bitwarden stealer downloaded Bun from an arbitrary host — egress allowlisting stops that cold.

Monitor for the exfil pattern. Whether or not you were directly hit, build a recurring check that flags any public repo created by org members with anomalous descriptions or content. Gitleaks and TruffleHog both run as scheduled GitHub Actions and cost nothing for small orgs.

The framing for your CTO or your board

Bitwarden did most things right. They published a clear advisory inside 12 hours. Their vault — the asset everyone actually cares about — was never at risk. The thing that failed was a CI/CD job and the trust we all place in npm's distribution channel. If a company whose entire business is secrets management can have its build pipeline weaponized for 90 minutes, the realistic question for an SMB is not "could this happen to us?" It is "would we notice, and how long would it take us to rotate?"

The answer for most SMBs we assess is: no, and weeks. Both of those numbers should be measured in minutes and hours, respectively, and the controls above are how you get there without enterprise budget.

This is a this-month problem, not a next-quarter problem. Shai-Hulud was named after the worm in Dune for a reason: it propagates, and there has now been a first coming, a second coming, and a third. The fourth is being written somewhere right now.

Harden your dev pipeline with our open-source tooling

Red Hound publishes free, practitioner-grade tools on GitHub for authentication log triage, attack surface diff, and engagement scope validation. Start with authlog, portdiff, and scopecheck — and if you want a 30-minute walk-through of how we would harden your CI/CD against the next Shai-Hulud, book a session.