@mastra/core を npm install しても、落ちてくるパッケージのコードは前のバージョンと一字一句変わっていない。差分はたった一行、package.json の依存リストに見慣れない easy-day-js が増えているだけだ。それなのにインストールした瞬間、端末では暗号資産ウォレット拡張の棚卸しとブラウザ履歴の窃取が動き出す。2026年6月17日にAIアプリ構築用のTypeScriptフレームワーク Mastra のnpmスコープで起きたのは、そういう種類の攻撃だった。
「本体が無傷」がいちばん厄介な点
ありがちなサプライチェーン攻撃は、人気パッケージそのものにバックドアを埋め込む。今回はそこが違う。攻撃者はnpmアカウント ehindero(過去のコントリビューターで、退任後も公開権限が残っていた)を使い、17日未明(およそ01:12〜02:39 UTC)に @mastra/core・mastra・create-mastra を含む約140個の @mastra パッケージを一斉に再公開した。中身は正規のまま。唯一の改変が、依存に easy-day-js を一行足したことだった。
easy-day-js は定番の日付ライブラリ dayjs のタイポスクワット(名前を似せた偽物)だ。別アカウント sergey2016 が6月16日に無害な 1.11.21 を撒いて信頼の地ならしをし、翌17日に毒入りの 1.11.22 を出している。汚染された @mastra 側は ^1.11.21 で参照していたため、インストールは自動で最新の 1.11.22 を引いてしまう。
The compromised package versions themselves contain unmodified code; the attack is delivered through an injected dependency.
依存ツリーを一段掘らないと異常が見えない。@mastra/core だけで週918K、汚染スコープ全体で週110万を超えるダウンロードがあったことを考えると、レビューの目をすり抜ける設計としてよくできている。
postinstall が静かに実行したこと
トリガーは npm の postinstall フックだった。easy-day-js の package.json にこう仕込まれていた。
"scripts": {
"postinstall": "node setup.cjs --no-warnings"
}
第一段の setup.cjs(約4.5KB)はローダーで、おおむね次の順に動く。TLSの証明書検証を切り(NODE_TLS_REJECT_UNAUTHORIZED=0)、攻撃者サーバ 23.254.164.92:8000 から第二段を取得し、それを切り離したバックグラウンドプロセスとして起動し、最後に自分自身を削除して痕跡を消す。検知のしにくさはこの自己削除に表れている。
第二段 protocal.cjs(約41KB)はクロスプラットフォームのスティーラーで、Windowsはレジストリの Run、macOSは LaunchAgent、Linuxは systemd のユーザーユニットで永続化する。そのうえで166種の暗号資産ウォレット拡張(MetaMask、Phantom など)を棚卸しし、Chrome・Edge・Brave の履歴を抜き、C2 23.254.164.123:443 と通信する。挙動は Socket の解析と StepSecurity の解析でほぼ一致している。
主なIOC(侵害指標)を整理しておく。
| 種別 | 値 |
|---|---|
| 悪性パッケージ | easy-day-js@1.11.22 |
| 関与アカウント |
ehindero / sergey2016
|
| C2 |
23.254.164.92:8000 / 23.254.164.123:443
|
| ビーコンファイル | 一時ディレクトリ内の .pkg_history .pkg_logs
|
🎯 なぜAIフレームワークが狙われたか
ここが今回いちばん考えさせられる。Mastra はLLMプロバイダやクラウドと密に繋ぐためのフレームワークで、その依存を入れる開発機やCIには、LLMのAPIキー・クラウド認証情報・DB接続文字列が環境変数として転がっていることが多い。加えて今回のペイロードは暗号資産ウォレットまで狙う。攻撃者から見れば、AI開発者の端末は鍵束の宝庫なのだ。
SafeDep は、手口が今年Microsoftが Sapphire Sleet(BlueNoroff、北朝鮮系とされるAPT)に帰属させた Axios npm 汚染と近いと指摘する。ただし今回の帰属は確定していない。ここは推測として扱うべきところだ。確かなのは、根本原因が高度なゼロデイではないことだ。退任済みコントリビューターの公開権限が放置されていたという、アクセス権の衛生問題である。動いているプロジェクトほど「誰がpublishできるか」の棚卸しは後回しになりがちで、これは耳が痛い。
🛡️ 手を動かして守る
即効性があるのはライフサイクルスクリプトを既定で止めることだ。
# postinstall などを実行せずにインストールする
npm install --ignore-scripts
# provenance(署名・attestation)のないパッケージを弾く
npm audit signatures
SafeDep は「署名を検証するインストールなら、この波の全パッケージを拒否できた」としている。CIで公開された正規版が持つ attestation を、汚染版は持っていなかったからだ。すでに @mastra を入れている環境では、該当パッケージを外し、APIキー・クラウド認証情報・トークンを総入れ替えし、一時ディレクトリの痕跡を確認する。
# ビーコンファイルの有無を確認
ls -la "${TMPDIR:-/tmp}"/.pkg_history "${TMPDIR:-/tmp}"/.pkg_logs 2>/dev/null
--ignore-scripts を常用すると一部パッケージのビルドが通らなくなる副作用はある。それでも postinstall が任意コード実行の入り口であり続けている現実を踏まえれば、CIでは既定オフにして必要なものだけ明示的に許可する運用が現実解だと思う。今回の件で覚えておきたいのは、package.json の依存に一行増えることは、新しいコード実行経路が一本増えるのと同じだ、ということだ。
一次情報のまとめは Socket の解析が読みやすい。