Bitwarden's Own CLI Was Backdoored on npm for 93 Minutes
At 5:57 PM ET yesterday, attackers pushed a trojanized @bitwarden/cli@2026.4.0 to npm. It silently stole SSH keys, cloud credentials, and GitHub tokens — then used them to inject itself into every CI/CD pipeline it could reach.
At 5:57 PM ET on April 22, 2026, a new version of @bitwarden/cli landed on npm. It was version 2026.4.0, pushed through Bitwarden's own CI/CD pipeline under Bitwarden's own verified publisher identity. Nothing about it looked wrong. It wasn't a typosquat. It wasn't an unverified publisher. It was the real package, from the real account, with the malware baked in.
For the next 93 minutes, every developer who ran npm install @bitwarden/cli was shipping their SSH keys, AWS credentials, GitHub tokens, and AI tool configurations to criminal infrastructure in real time.
How They Got In
The attackers didn't breach Bitwarden's core systems. They compromised something further up the chain: checkmarx/ast-github-action, a GitHub Actions step that Bitwarden's publish-ci.yml workflow uses during its package release process.
GitHub Actions workflows often chain together third-party actions — pre-built steps from other repositories that handle common tasks like code scanning, testing, and publishing. When Bitwarden's release workflow ran, it invoked the Checkmarx action for static analysis. That action had been compromised. The malicious code injected by the attackers ran inside Bitwarden's build environment, with access to the npm authentication token that workflow uses to publish packages. The rest follows automatically.
Researchers at Aikido and Endor Labs documented this as the first confirmed case of npm Trusted Publishing being compromised via upstream action injection. Trusted Publishing replaced long-lived API tokens with short-lived, ephemeral tokens tied to specific repositories and workflow paths — it was supposed to eliminate the attack surface of a stolen npm token. The problem is that the token is only as trustworthy as the actions that hold it during a workflow run. Compromise the action, compromise the token, compromise the release. The trust model holds until one link in the dependency chain goes bad.
Bitwarden is not the first victim of this particular chain. The same threat actor — tracked by Endor Labs as TeamPCP — used an identical entry point to compromise the Trivy vulnerability scanner and LiteLLM earlier this year. The approach is methodical: find high-trust developer tools that use the Checkmarx action, inject malicious code into that action, wait for a release to run.
What The Malware Actually Did
The malicious 2026.4.0 release introduced a preinstall hook in package.json that executed a file named bw_setup.js. This is a cross-platform bootstrapper — its job is to download the legitimate Bun runtime (a JavaScript runtime) and use it to execute the actual payload: bw1.js, a 10 MB heavily-obfuscated credential harvester with SHA256 18f784b3bc9a0bcdcb1a8d7f51bc5f54323fc40cbd874119354ab609bef6e4cb.
The preinstall hook runs automatically and silently when anyone runs npm install @bitwarden/cli. No additional user interaction. No suspicious prompts. The CLI installs normally in the foreground while the credential harvester runs in parallel.
bw1.js scanned the machine for:
- SSH keys (
~/.ssh/id*,~/.ssh/keys) - AWS credentials — and, notably, it didn't stop at static credential files. It called the AWS SSM Parameter Store, AWS Secrets Manager, Azure Key Vault, and GCP Secret Manager APIs using any ambient cloud credentials present in the environment, actively pulling live secrets rather than just copying files.
- npm authentication tokens (
~/.npmrc) - GitHub personal access tokens from git configuration and environment variables
- GitHub Actions secrets accessible to the running workflow environment
- Shell history and
.envfiles - AI coding tool configurations: specifically
~/.claude.json, Cursor project files, Codex CLI configuration, and Aider settings
The .claude.json and AI tool targeting is new. Earlier Shai-Hulud campaign variants didn't include AI assistant configs in the exfiltration list. That file can contain your Anthropic API key, session memory contents, custom system prompts, and project-specific context. It's now on the shopping list alongside SSH keys.
Everything collected was encrypted with AES-256-GCM and exfiltrated to audit.checkmarx[.]cx — a .cx domain (Christmas Island), not checkmarx.com. The domain was crafted specifically to look like Checkmarx telemetry in proxy logs and SIEM alerts. If that endpoint was unavailable, the malware fell back to dead-dropping encrypted payloads as commits in public GitHub repositories created under the victim's own account, searchable by other infected machines via a known query string against the GitHub API.
The Worm
Credential theft is the obvious part. The propagation mechanism is the part that makes this categorically more dangerous than a simple infostealer.
Stolen GitHub tokens don't just grant access to your repositories. They can push commits to any repository your token has write access to. The malware used that to inject malicious steps into GitHub Actions workflow files across every repository accessible with the victim's token. On the next code push — from any developer on any branch — those injected steps execute in CI. They harvest the pipeline's secrets, environment variables, and any other credentials the workflow has access to. Those get exfiltrated too. The attack expands without the attacker lifting another finger.
Endor Labs named this campaign "Shai-Hulud: The Third Coming." The code references to that name are intentional — they're a fingerprint the threat actor has embedded in every campaign. In the original Shai-Hulud campaign in 2025, the same worm technique was used against a package with 12,000 weekly downloads. In the second iteration, roughly 30,000. The Bitwarden CLI has 78,000 weekly downloads. This is escalation in target value, not a change in technique.
The blast radius math is uncomfortable: one compromised developer with an organizational GitHub token can become the entry point for injected malicious workflows across dozens or hundreds of internal CI/CD pipelines. Every pipeline becomes a new exfiltration opportunity. The attack fans out horizontally before the developer has any idea something happened.
Why The Bitwarden CLI Specifically
Password managers occupy a specific place in developer trust hierarchies. The Bitwarden CLI is commonly used in CI/CD pipelines for exactly the purpose its name suggests — managing secrets. A common pattern looks like this: bw unlock → bw get password "production-db" → feed the result into a deployment step. The CLI is installed globally on developer workstations and in Docker images. It's invoked in pipelines with access to production credentials.
Targeting the CLI means targeting everywhere it's installed. And because it's a password manager — something developers actively chose because they care about security — they're less likely to scrutinize a routine update.
There's a secondary effect worth noting: Bitwarden confirmed that no end-user vault data was accessed and production infrastructure wasn't compromised. That statement is accurate. The malware wasn't trying to compromise Bitwarden's vault service. It was trying to compromise the developer whose machine has the CLI installed. The vault remaining intact doesn't help the developer whose AWS environment just got exfiltrated.
The 78,000 Number In Context
The weekly download figure applies to normal steady-state traffic. The actual exposure window was 93 minutes — from 5:57 PM to 7:30 PM ET on April 22 — so the attack didn't hit all 78,000 weekly users.
But CI pipelines don't just install packages when humans are at their keyboards. Scheduled pipelines run at off-hours. Webhook-triggered pipelines run on every push, including pushes made at 5:57 PM on a Tuesday. Any pipeline that doesn't pin @bitwarden/cli to a specific version hash will install whatever @latest resolves to at runtime. During that 93-minute window, that was 2026.4.0.
This is the compounding problem with unpinned dependencies in CI: a 93-minute compromise window doesn't mean 93 minutes of limited exposure. It means every pipeline that triggered during those 93 minutes, across every organization using the package without version pinning, ran the malware.
What To Do
If you have any possibility of having installed or invoked @bitwarden/cli between 5:57 PM and 7:30 PM ET on April 22, or if any of your CI pipelines ran during that window:
Rotate credentials immediately:
- GitHub Personal Access Tokens: Revoke all PATs present in
~/.gitconfig, environment variables, or CI secrets on potentially affected machines. Create new ones and update pipelines before anything runs with the old tokens. - npm tokens: Revoke tokens from
~/.npmrc. If you're a package maintainer, audit your recent publish history for releases you didn't author. - SSH keys: If keys in
~/.ssh/id*have write access to any internal repositories, rotate them and scan recent commits for anything you didn't push. - Cloud credentials: For any AWS, Azure, or GCP credentials present in the environment — rotate keys, then audit CloudTrail, Azure Activity Log, or GCP Audit Logs for the past 48 hours. Pay particular attention to unexpected API calls to Parameter Store, Secrets Manager, or Key Vault, which the malware queried directly.
- AI tool API keys: If
~/.claude.jsonor Cursor/Aider project files were on an affected machine, rotate your Anthropic API key from the Anthropic console and check your usage dashboard for unexpected activity.
Audit your pipelines:
Look at every GitHub Actions workflow file your token had write access to. Check for modifications to uses: references, unexpected run: steps, or any workflow file changes timestamped April 22-23. The injected steps won't announce themselves — look for run: blocks that reference unfamiliar URLs, curl commands, or any environment variable harvesting.
Check for forensic indicators:
The malware creates public GitHub repositories under the victim's account to store encrypted exfiltration blobs. A repository on your account that you don't recognize, created April 22-23 with a handful of commits and no real content, is a strong indicator of compromise.
For the future:
Pin every GitHub Action in your workflows to a specific commit SHA rather than a mutable tag. uses: checkmarx/ast-github-action@a3f21d9... cannot be silently substituted by an attacker who controls the action's repository. uses: checkmarx/ast-github-action@v3 can. This is in GitHub's own hardening documentation. Most workflows don't do it. This incident is a concrete example of why it matters.
The Pattern
Supply-chain campaigns against developer tooling follow a recognizable arc. They start with less scrutinized packages — obscure utilities, niche analysis tools. When those succeed, the campaigns move toward higher-value targets: security scanners, deployment tools, credential management infrastructure.
The Checkmarx campaign started with KICS, moved to Trivy, and has now hit a password manager with 78,000 weekly downloads. TeamPCP is moving up the value chain deliberately. A tool that more developers trust and install globally is a tool that compromises more pipelines per successful injection.
The specific failure here — Trusted Publishing bypassed by a compromised upstream action — is one that most organizations' threat models haven't caught up to. npm Trusted Publishing was an improvement over long-lived API tokens. It is not, and never claimed to be, a defense against a compromised action in your build graph. The attack surface isn't your packages. It's everything that touches your packages.
Sources: The Hacker News, Aikido Security, BleepingComputer, Endor Labs, Socket.dev