「Mini Shai-Hulud」が出現:Bunベースの窃取型マルウェアがSAPの@cap-jsおよびmbt npmパッケージを標的に
2026年4月29日
39 分で読めます202mbt、@cap-js/db-service、@cap-js/sqlite、@cap-js/postgresです。preinstallフックが
ペイロードには
Snykはsnyk testで
影響を受けるパッケージとSnykアドバイザリ
パッケージ | 侵害された | Snykアドバイザリ | 週あたりの |
|---|---|---|---|
|
| 約52,000 | |
|
| 約260,000 | |
|
| 約250,000 | |
|
| 約10,000 |
週あたりのmbtは、@cap-js/*パッケージは、
悪意のregistry.npmjs.orgと
09:55:25:npmアカウントcloudmtabotからmbt@1.2.48が公開。 10:01:07:最初の被害者の 使い捨て リポジトリが GitHubに 出現 (GitHub APIの タイムスタンプに よると gruposbftechrecruiter/siridar-navigator-935)。11:25:47:@cap-js/sqlite@2.2.2が公開。 12:03 to 12:04:StepSecurityがcap-js/cds-dbs#1588とSAP/cloud-mta-build-tool#1224で情報開示の Issueを 提出。 3件目の 独立した 開示と して、 longieirlがSAP/open-ux-tools#4616を14:02に 提出。 12:14:00:@cap-js/postgres@2.2.2と@cap-js/db-service@2.10.1が公開。 13:31:cap-jsのメンテナー chgeoが返信:「お知らせいただき ありがとう ございます。 最優先で パッケージを 整理し、 根本原因を 調査します。」 13:46:SAPがGitHub Actionsの OIDC信頼済みパブリッシャー cap-npmを通じて、 インシデント後の クリーンな バージョンを 公開。 @cap-js/db-service@2.11.0、@cap-js/sqlite@2.4.0、@cap-js/postgres@2.3.0(調整に合わせて @cap-js/hana@2.8.0も更新)。 14:02:50:SAPのエンジニア patricebenderがcap-js/cds-dbsのPR #1592を 提出。 手動承認環境を 通さないと npmに 公開できないよう 変更し、 前述の 根本原因に 関する 記述を そのまま 引用。 14:24:cloud-mta-build-toolのコラボレーター kbarnoldが返信:「ご報告 ありがとう ございます。 最優先で 対応しています。」
@cap-js/sqlite@2.2.2はmbt@1.2.48には「SECURITY: This version contains malicious code. Do not use.」と@cap-js/*のmbt@1.2.48がmbtのlatest dist-tagにmbtバージョンがnpm install mbtでも
「Mini」Shai-Huludと呼ばれる理由(そして実際の違い)
npmサプライチェーンの@ctrl/tinycolor、ngx-bootstrap、ng2-file-uploadと、HEUR:Worm.Script.Shulud.gen)、securelist.com/shai-hulud-worm-infects-500-npm-packagesおよびsecurelist.com/shai-hulud-2-0で

Shai-Hulud NPM
過去の
4月29日の
認証情報の
窃取:複数の 研究者が 確認し、 詳細を 報告しています。 開発者ツールの
設定への永続化コードの 注入:新たに 確認された 手法で、 詳細は 後述します。 npmへの
自動自己 公開:StepSecurityに よる 静的解析で 難読化を 解除した 結果、 コードが 存在し、 機能する ことが わかっています。 ペイロードは 正規表現 /npm_[A-Za-z0-9]{36,}/gでnpmトークンを 収集し、 registry.npmjs.org/-/npm/v1/tokensで各トークンの 有効性を 検証します ( bypass_2fa: trueかつ組織レベルの 書き込みスコープが ある ものに 絞り込みます)。 アクセス可能な パッケージを 列挙し、 tarballの コピー内に setup.mjsとexecution.jsを追加してから、 npm CLIを 使わず npmレジストリに 直接 PUTして公開します。 記事公開時点で、 最初の 4つ以外に 悪意の ある ペイロードを 含む パッケージは 確認されていません。 ただし、 ワームと しての 機能は 推測ではなく、 実際に 確認された 事実です。 侵入経路と
なった CIパイプラインの 乗っ取り:SAP自身に よる 最初の 公式声明は、 同日14:02 UTCに 提出された patricebenderによる cap-js/cds-dbsのPR #1592に 記されています。「202 6年4月29日、 サプライチェーン攻撃に よって リポジトリが 侵害されました。 権限の ない 攻撃者が 悪意の ある コミットを プッシュして リリースワークフローを 乗っ取り、 許可されていない npmパッケージの 公開を 実行しました。 ワークフローに 手動承認の ゲートが なく 公開権限が 設定されていた ため、 攻撃者は 侵害した パッケージを 公開できました。」対策は、 環境レビューを 必須に して npmへの 公開を 制御する ことです。 npmレジストリの データに よると、 悪意の ある 4つの バージョンは すべて cloudmtabotアカウント(正規の mbtメンテナー)から公開されました。 SAPが 13:46 UTCに 公開した インシデント後の クリーンな バージョンは、 cloudmtabotではなく、GitHub Actionsの OIDC信頼済みパブリッシャーである cap-npmを通じて 公開されています。 cloudmtabotアカウント自体も、その後 npmで 停止されました。メンテナーの 認証情報の 侵害は、 この 種の インシデントで 繰り返し見られる 侵入経路ですが、 SAPの 声明では、 ワークフローに 承認ゲートが なかったことが 構造的な 根本原因と して 明確に 指摘されています。
認証情報の
攻撃の仕組み
侵害のsetup.mjs(4.5 KBのexecution.js(正確にmbtと@cap-js/*パッケージ間でmbtだけは、package.jsonに、axios、tar、unzip-stream)package.jsonにpreinstallフックだけが
インストール時にaxiosを
mbtの1.2.47(クリーン)と1.2.48(悪意の
preinstallは、--ignore-scriptsフラグがsetup.mjsを
(Linuxでの
Alpine/musl検出を 含め) プラットフォームと アーキテクチャを 検出します。 hasCommand("bun")を使って、 PATH上にbunがすでに あるか 確認します。 存在する 場合は ダウンロードを 省略し、 既存の バイナリを 使用します。 存在しない 場合は、 GitHub Releasesから Bun 1.3.13をダウンロードします (Socketの 分析に よると、 ダウンロード先を 検証せずに HTTPリダイレクトを 追跡します)。 ダウンロードした
場合は、 Bunバイナリを 一時 ディレクトリに 展開します。 bun execution.jsを実行し、 難読化された ペイロードを 起動します。
bun execution.jsがBun.gunzipSync()やBun.mainなど
StepSecurityに
ローカルの
認証情報、 GitHubと npmの トークン、 GitHub Actionsの シークレット、 AWS、 Azure、 GCP、 Kubernetesの クラウドシークレット、 パスワードマネージャーCLIの トークン (1Password、 Bitwarden、 LastPass)、 ~/.claude.jsonとMCPサーバーの 設定、 KEY/TOKEN/SECRET/PASSWORDに一致する 環境変数、 ホスト上に ある 暗号資産ウォレットの 秘密鍵を 収集します。 また、 接続可能な 場合は AWSインスタンスメタデータサービス ( 169.254.169.254)にも問い合わせます。 Linux CIランナーでは、
Pythonの 子プロセスを 起動し、 GitHub Actionsの /proc/{pid}/memからRunner.Workerプロセスのメモリを 読み取って ダンプし、 ランナーの メモリから 平文の シークレットを 直接 窃取します。 Windowsでは、
-ExecutionPolicy Bypassを指定して PowerShellを 呼び出します。 窃取した
データを JSONと して シリアル化し、 gzipで 圧縮したうえで AES-256-GCM (32バイトの キー、 12バイトの IV)で 暗号化し、 埋め込まれた RSA-4096 公開鍵 ( execution.jsのファイルオフセット9,429,992に gzip+base64形式の PEMと して 保存され、 実行時に Bun.gunzipSync()で展開)を 使って RSA-OAEP-SHA256で AES鍵を ラップします。 つまり、 デッドドロップの リポジトリを 発見した 防御側は、何かが窃取された ことは 確認できますが、何が窃取されたかは 特定できません。 実質的に、 プロセスが アクセスできた すべての 認証情報が 漏えいした ものとして 扱う 必要が あります。 被害者の
GitHubアカウントに ランダムな 文字列の 名前を 付けた 公開リポジトリを 作成し、 説明に 「A Mini Shai-Hulud has Appeared」と 記載して、 暗号化された 暗号文を デッドドロップと して コミットします。 JavaScriptの
IntlAPIとPOSIX環境変数 ( LC_ALL、LC_MESSAGES、LANGUAGE、LANG)を使って システムの ロケールを 確認します。 ロシア語 ( ru)のロケールが 検出されると、 ペイロードは "Exiting as russian language detected!"をログに 記録して 終了します。 CIS地域を 除外する チェックは、 これらの 地域に おける サイバー犯罪 活動の 既知の 挙動と 一致しています。
永続化の仕組み
この
StepSecurityに
開発者ツール設定への
インジェクション。盗んだ トークンで 書き込み可能な すべての GitHubリポジトリに、 ペイロードは .claude/settings.jsonファイルをコミットします。 この ファイルはClaude Codeの SessionStartフックを使い、 開発者が Claude Codeの セッションを 開始する たびに ペイロードを 再実行します。 また、 VS Codeで プロジェクトを 開いた ときに 実行される "runOn": "folderOpen"を設定した .vscode/tasks.jsonもコミットします。 これらの 注入ファイルは claude@users.noreply.github.comのIDで、 "chore: update dependencies"という メッセージとともに コミットされる ため、 PRレビューでは 定期的な 自動化コミットに 見える 可能性が あります。 GitHub Actionsワークフローへの
インジェクション。盗んだ トークンに workflowスコープがある 場合、 ペイロードは タイポスクワッティングした ブランチ dependabout/github_actions/format/setup-formatter(tが1つ抜けています)を 作成し、 Dependabotサービスアカウント ( dependabot[bot]@users.noreply.github.com、コミットメッセージ "Add formatter workflow")になりすまして .github/workflows/format-check.ymlをコミットします。 注入された ワークフローは ${{ toJSON(secrets) }}を使って リポジトリ内の すべての シークレットを format-results.txtという ビルドアーティファクトに 出力します。 開発者の
マシン上での デーモン化。CI以外の ホストでは、 ペイロードは 環境変数 __DAEMONIZED=1を付けて 自身を 切り離された バックグラウンドプロセスと して フォークし、 親プロセスは 正常に 終了します。 その ため、 npm installは目に 見える エラーなしで ユーザーに 制御を 戻します。 切り離された 子プロセスは、 明確な プロセスの 系譜を 残さずに 実行を 続けます。
これにpackage.jsonの
AIエージェントのtasks.jsonのrunOn: folderOpenをSessionStartフックに
侵害の痕跡(IOC)
このregistry.npmjs.org/mbt/1.2.48、registry.npmjs.org/@cap-js/sqlite/2.2.2、registry.npmjs.org/@cap-js/postgres/2.2.2、registry.npmjs.org/@cap-js/db-service/2.10.1で"preinstall": "node setup.mjs"と
特徴的な
デッドドロップリポジトリの
説明:https://github.com/search?q=%22A+Mini+Shai-Hulud+has+Appeared%22&type=repositories(4月29日15:17 UTC時点で、 GitHub APIの 直接 カウントでは 1,076件。 新しい リポジトリが リアルタイムで 追加されています) P2Pトークンの
デッドドロップに 使われた コミット文 字列:https://github.com/search?q=OhNoWhatsGoingOnWithGitHub&type=commits
各デッドドロップリポジトリには、README.mdファイルと、results/results-<unix-ms>-<counter>.jsonファイルが
ラップに
今すぐ取るべき対応
被害のnpm installを
インストール状況を@cap-js/sqlite@2.2.2は@cap-js/db-service@^2.10.0を@cap-js/sqliteを@cap-js/db-service@2.10.1(悪意ある
Snykをsnyk testとsnyk monitorをmbt、@cap-js/db-service、@cap-js/sqlite、@cap-js/postgresの
侵害された
その
マシンや パイプラインから アクセス可能な 認証情報は すべて、 漏えいした ものとして 扱ってください。 攻撃者が 管理する RSA鍵で 暗号化されている ため、 防御側は デッドドロップの 内容を 読んで 影響範囲を 確認できず、 慎重に 認証情報を ローテーションする 必要が あります。 StepSecurityが ペイロードから 復元した 134種類の ファイルパスパターンには、 少なくとも 以下が 含まれます。 npm 公開トークン (開発者が 管理する 他の パッケージへ ワームのように 拡散する ため、 最優先で 対応)、 GitHub PATと OAuth認可情報、 AWS/Azure/GCPの 認証情報、 および影響を 受けた マシンから 引き受け可能な ロール、 Kubernetesの kubeconfig ( ~/.kube/config、k3s YAML、 Pod内の サービスアカウントトークン)、 Docker設定 ( ~/.docker/config.json)、Terraform Cloudの 認証情報 ( ~/.terraform.d/credentials.tfrc.json)、Helm設定、 SSH鍵 ( ~/.ssh/id*、config、known_hosts、/etc/ssh/配下のホスト鍵)、 パスワードマネージャーの CLIトークン (1Passwordの op、Bitwardenの bw、LastPass)、.npmrc/.pypirc/.yarnrc、.gitconfigと.git-credentials、シェルの 履歴ファイル、 環境変数、 および *_TOKEN、*_KEY、*_SECRET、*_PASSWORDのパターンに 一致する .env*ファイル、~/.claude.jsonとあらゆる MCPサーバー設定 ( ~/.kiro/settings/mcp.jsonのKiro MCP設定を 含む)、 メッセージングアプリの セッションデータ (Signal、 Slackの Cookie、 Discord、 Telegram、 Element、 Pidgin)、 VPN設定 (NordVPN、 ProtonVPN、 OpenVPNなど)、 FileZillaの サーバーリスト、 Ansible設定、 Bitcoin、 Ethereum、 Monero、 Dash、 Zcash、 Dogecoin、 Litecoin、 Electrum、 Exodus、 Ledger Live、 Atomicの 暗号資産ウォレット鍵です。 また、 ペイロードは AWSインスタンスメタデータ ( 169.254.169.254、169.254.170.2、fd00:ec2::254)とECSタスクメタデータも 読み取る ため、 これらの エンドポイントから アクセス可能な IAMロールも 漏えいした ものと 見なす必要が あります。 GitHub Actionsの
シークレット露出を 監査してください。 /proc/{pid}/memを使う 手法では、 Runner.Workerの読み取り可能な メモリアドレス空間全体が 取得される ため、 インストール手順で 直接 参照されていなくても、 その 時 点までに ワークフローで 使用された シークレットは すべて 対象です。 GitHubホストランナーの デフォルト設定では、 この アクセスは ブロックされません。 検出するにはStepSecurity Harden-Runnerのような ランナー側の セキュリティエージェントが 必要です。 上記の
コードブロックに ある IOCを 使って、 GitHub組織全体を 検索してください。 Duneを テーマに した 命名パターンと 「Mini Shai-Hulud」の 説明が 付いた デッドドロップリポジトリ、 claude@users.noreply.github.comの作成者署名、 dependabout/...ブランチ上でのdependabot[bot]@users.noreply.github.comへのなりすまし、 toJSON(secrets)でシークレットを 流出させて format-results.txtアーティファクトに出力する 注入済みの .github/workflows/format-check.ymlワークフロー、P2Pトークンの デッドドロップに 使われた コミット文字列 OhNoWhatsGoingOnWithGitHub、予期しない .claude/settings.jsonまたは.vscode/tasks.jsonの追加を 確認してください。 安全な
バージョンを 固定する。 3つの @cap-jsパッケージでは、SAPが インシデント後に リリースした バージョン ( @cap-js/db-service@2.11.0、@cap-js/sqlite@2.4.0、@cap-js/postgres@2.3.0)を今後の 固定先とし、 インシデント前の バージョン (順に 2.10.0、2.2.1、2.2.1)を最低ラインに してください。 mbtについては、 執筆時点で 修正版が リリースされていません。 SAPが 後継版を 公開するまで、 mbt@1.2.47に固定してください。
基本的な
CI環境では、
デフォルトで npm install --ignore-scriptsを使い、 本当に 必要な パッケージに 限って ライフサイクルスクリプトを 明示的に 許可してください。 これに より、 元の Shai-Hulud、 SHA1-Hulud、そして 今回の キャンペーンで 配布手段と なった、 preinstallによる 認証情報窃取を 全面的に 防げます。 この ライフサイクルフックを 使った 攻撃手法は、 201 6年にnpmに 報告されて 「意図した 動作」と されました。paulirishが Hacker Newsで 再び指摘したとおり、 10年たった 今も エコシステムで 最も 悪用されている 攻撃対象です。 詳しくは、 SnykのNPM Security Best Practicesと、 上流での 防御策を 詳しく 解説したHow to prevent malicious packagesを ご覧ください。 ライフサイクルスクリプトを
デフォルトで 安全に 扱う パッケージマネージャーの 利用も 検討してください。pnpm v10は 明示的に 許可されない 限りライフサイクルスクリプトを ブロックし、 Bunを インストーラーと して 使う 場合は信頼済みパッケージの 許可リストが デフォルトで 用意されています。 今回、 後者は 特に 重要です。 マルウェアは Bunを ランタイムと して ペイロードの 実行に 使いますが、 インストーラーと しての Bunなら、 配布に 使われる preinstallフックをブロックできていました。 Bunコミュニティでは、 クールダウン期間を 設ける 緩和策と して、Yossarianの 依存関係の クールダウンに 関する 提案と 同様に、 minimumReleaseAge設定のリクエスト (#28729)も 検討されています。 PRの
差分で、 予期しない .claude/settings.jsonや.vscode/tasks.jsonの変更が ないか 確認してください。 どちらかの ファイルへの 追加は、 依存関係の 定期的な メンテナンスに 見えても、 サプライチェーン攻撃の 兆候と して 扱ってください。 SOCおよび検知エンジニアリングチーム向けに
公開されたMicrosoft Defenderの KQLクエリ ( m4nbat/100_days_of_kql_2026のDay 17)は、 この キャンペーンが 再利用する Bun + TruffleHog + /proc/memの一連の 手法を 検知します。 SHA1-Hulud向けに 作成された ものですが、 変更を 加えずに Mini Shai-Huludにも 適用できます。Wiz Researchの IOCフィードと gensecaihq/Shai-Hulud-2.0-Detectorは、メンテナーが 新たに 侵害された 4つの パッケージを 反映した 後に、 有効な ブロックリストと して 利用できます。 リスクベースの
優先順位付けを 行い、 影響範囲が 最も 大きい 認証情報を 優先して ローテーションと トリアージを 進めましょう。 対象と なる シークレットが すべて 同じように 機密性の 高い ものとは 限りません。 また、 暗号化された デッドドロップが 使われている ため、 防御側が 得られる 情報は 不完全です。
Pythonアプリの セキュリティ対策を 始めましょう
Snykを
クレジットカードの
またはAzure AD、Docker ID、Bitbucketで
Snykを



