Skip to main content

keyv npm侵害の実態:preinstallマルウェア、信頼された来歴、IDEフック

feature insights context

2026年8月4日

0 分で読めます

2026年8月4日、攻撃者はkeyvと関連npmパッケージのリリース経路を侵害しました。悪意のあるリリースには、アプリケーションコードの開始前に難読化されたローダーを実行するpreinstallフックが追加され、その後、はるかに大規模な第2段階ペイロードが起動します。

これは概念実証ではなく、現在進行中のソフトウェアサプライチェーンインシデントです。Snyk Security Researchは、インストールせずに公開済みtarballを独自にダウンロードして比較し、メンテナーjaredwrayについてnpmが返した全パッケージを列挙して、同じ2つのペイロードファイルを含む悪意のあるリリースを11件特定しました。UTC 11:16時点で、そのうち8件にはまだlatestタグが付いていました。

概要

  • インシデント:正規のnpmパッケージに悪意のあるコードが埋め込まれる

  • 主なパッケージ:keyv@6.0.0

  • Snykが確認した影響リリース:keyv、cacheable関連パッケージ、ectoにまたがる11件

  • 実行方法:"preinstall": "node setup.mjs"

  • ペイロード:setup.mjsが727,680バイトの第2段階ペイロードMath_Symbol.jsを読み込む

  • UTC 11:16時点で確認された状況:悪意のあるリリース8件がlatestタグに残存。3件は削除済み。

  • アドバイザリ:SNYK-JS-KEYV-18515941

  • CVE/GHSA:調査時点ではCVEもGHSAも割り当てられていません

  • 運用上の深刻度:インストールにより、開発者またはCIランナーの権限で攻撃者が制御するコードが実行されるため、Critical

  • 修復:既知の最終安全版にバージョンを固定するか、ダウングレードしてください。keyv@6.0.0の安全な後続版は公開されていませんでした。

  • 直ちに実施すること:影響を受けるバージョンをインストールしないでください。実行してしまった場合は、ホストを隔離し、永続化の痕跡を調査して無効化したうえで、クリーンなシステムから露出した認証情報をローテーションしてください。

深刻度とアドバイザリの状況

この記事の草稿作成中に、SnykはSNYK-JS-KEYV-18515941を公開しました。このアドバイザリでは、keyv@6.0.0をCWE-506に該当する、悪意のあるコードが埋め込まれたパッケージとして分類しています。

調査中、CVEもGitHub Advisoryの記録も割り当てられていませんでした。影響を受けるパッケージをインストールすると、追加の権限やアプリケーションとのやり取りなしに、開発者またはCIプロセスが利用できる権限と認証情報を使って、攻撃者が制御するコードが実行されます。

Snykが独自に確認した内容

jaredwrayがメンテナンスするパッケージをnpmレジストリ検索エンドポイントで照会しました。エンドポイントから61個のパッケージ名が返されました。それぞれについてレジストリのマニフェストを取得し、8月4日に公開された全バージョンを確認して、ライフサイクルスクリプトとtarballのメタデータを調べました。

調査により、以下の悪意のあるリリースが見つかりました。また、@keyvスコープの他のパッケージは侵害されていないことも確認しました。

最後の項目は、インシデントの範囲を特定するうえで重要です。ecto@5.0.1は最初の公開警告の後に登場し、そのsetup.mjsとMath_Symbol.jsはkeyv@6.0.0内のファイルとバイト単位で完全に一致します。ectoを含まない初期のパッケージ一覧は不完全です。

UTC 11:16時点のスナップショットでは、npmはflat-cache@6.1.24、cacheable-request@13.0.20、cache-manager@7.2.10を削除し、それぞれのlatestタグを6.1.23、13.0.19、7.2.9に戻していました。影響を受ける残り8件はまだ公開され、latestタグが付いていました。進行中のインシデントではレジストリの状態が急速に変わることがあります。npmがバージョンを削除した後も、調査にはプライベートミラーとロックファイルの確認が欠かせません。

公開tarballで異なるのは3か所

レジストリからkeyv@6.0.0とリリース候補版keyv@6.0.0-rc.1を直接取得し、SHA-256で全ファイルを比較しました。パッケージのインストールや、いずれかのペイロードの実行は行っていません。

異なるパスは3つだけです。

  • package.jsonでは、バージョンが6.0.0-rc.1から6.0.0に変わり、公開ファイル一覧に2つのペイロードファイルが追加され、preinstallスクリプトが追加されています。

  • setup.mjsが追加されました(29,918バイト)。

  • Math_Symbol.jsが追加されました(727,680バイト)。

dist/配下の全ファイルは、リリース候補版と侵害された安定版でバイト単位まで完全に一致します。ライブラリはインストール後も通常どおり動作する一方、ライフサイクルフックは別途実行されます。この小さな差分は、検出の手がかりになると同時に、効果的な隠蔽手法でもあります。

マニフェストの変更は明白です。

 "scripts": {
   "build": "tsdown",
+  "preinstall": "node setup.mjs"
 },
 "files": [
   "dist",
   "LICENSE",
+  "setup.mjs",
+  "Math_Symbol.js"
 ]

keyv@6.0.0には、次のtarball整合性値が記載されています。

sha512-N/n4R+nD5SC0fYOpAp4ZnbwwxqGVodgEZ9D7Gm/VBocorU0aQimVyleDWSY6/axdO0/temub760n3hnMppZpUg==

独自に算出したハッシュは以下のとおりです。

keyv-6.0.0.tgz
sha256 d584f9b6af48b7ed1f93713944f033783bf149e1c25e1643eb8c0e9df5dc7782

setup.mjs, 29,918 bytes
sha256 54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668

Math_Symbol.js, 727,680 bytes
sha256 9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc

続いて、初回分析時に取得できたすべての影響リリースについて、ペイロードのハッシュを再確認しました。その時点で利用可能だったtarball 9件すべてに、上記と同じハッシュの29,918バイトのsetup.mjsと727,680バイトのMath_Symbol.jsが含まれていました。その後、npmはそのうち1つのバージョンを削除しました。検出には、単一のパッケージ名に頼らず、ハッシュとライフサイクルの動作を利用してください。

マルウェアの実行方法

npmは依存関係のインストール時にpreinstallを自動実行します。開発者がkeyvをインポートしたり、アプリケーションを起動したり、脆弱なAPIを呼び出したりする必要はありません。影響を受けるパッケージを解決してインストールするだけで実行されます。

setup.mjsの静的解析から、Linux、macOS、Windowsのプラットフォーム判定、child_process.execFileSyncの使用、ファイルシステム操作、HTTPSアクセス、Bunランタイムの処理が確認できます。.claude/setup.mjsとしてコミットされた、難読化がより少ないローダーには、Bunのバージョン1.3.13と第2段階のファイル名math_init.jsが記載されています。Bunが存在しない場合、ローダーはGitHubから適切なBunリリースを取得し、より大きなペイロードを実行して、一時的なランタイムのファイルを削除できます。

インシデント報告とともに提供された独立したマルウェア分析によると、暗号化された第2段階ペイロードは、GitHubおよびnpmトークン、クラウド認証情報、秘密鍵、データベース接続文字列、Vaultトークン、Kubernetesサービスアカウントトークン、GitHub Actionsランナーのメモリを標的にします。また、盗んだGitHubトークンを監視し、そのトークンが使えなくなったときに指定されたハンドラーを実行する、gh-token-monitorという永続化メカニズムも報告されています。

Math_Symbol.jsは実行も独自の復号も行っていないため、第2段階の機能に関する詳細には、その旨を明記する必要があります。運用上のガイダンスは、同じgh-token-monitor永続化パターンに関するSnykの過去の分析と一致しています。トークンを失効させる前に、まずモニターを封じ込めて無効化してください。

開発ツールを狙う第2の実行経路

npmのライフサイクルフックは、侵入経路の1つにすぎません。コミットd8c850c7のGitHub API記録によると、keyvリポジトリに5つのファイルが追加されています。

  • .claude/settings.json

  • .claude/setup.mjs

  • .claude/math_init.js

  • .vscode/tasks.json

  • .vscode/setup.mjs

Claudeの設定にはSessionStartコマンドが登録されており、.vscode/setup.mjsを実行します。VS CodeのタスクはrunOn: "folderOpen"を使用して.claude/setup.mjsを実行します。開発者やコーディングエージェントがプロジェクトローカルの設定を信頼して有効化すると、これらのファイルによって別の実行経路が生まれます。ワークスペースの信頼設定やユーザー設定によっては、VS Codeが自動タスクの許可を求める場合があります。そのため、チェックアウトを開くことはコードが実行された証拠ではなく、実行にさらされる条件です。

このコミットはGitHubによって暗号学的に検証されており、作成者としてgithub-actions[bot]のIDを使用しています。検証済みバッジは、GitHubがコミットオブジェクトに署名したことを証明しますが、その変更がプロジェクトメンテナーによって承認されたことを示すものではありません。証拠からは、アカウント、認証情報、セッション、またはリリース経路が侵害された可能性が示されています。実行者を特定するものではなく、メンテナーはインシデントの被害者として扱うべきです。

悪意のあるリリースにも正当な来歴情報が署名されていた

npmのマニフェストはkeyv@6.0.0の信頼できる公開元としてGitHub Actionsを示し、リリースのnpm attestationにリンクしています。タグ付けされたリポジトリの状態に悪意のあるソースコードが含まれていたため、正規のワークフローが悪意のある成果物をビルドし、attestationを付与しました。

リリースコミットのパッチには、setup.mjsを介してexecFileSyncを実行するテストも追加されていました。その後のコミットでは、そのテストのみが削除されています。そのため、リリースのテストスイートを実行すると、公開前にCI内でマルウェアが実行されていた可能性があります。

来歴情報は、ビルドの出所を示す重要な証拠であり続けます。このインシデントはその限界を示しています。ソースコードやワークフローの環境がすでに侵害されていれば、来歴情報は侵害されたビルドを正確に証明してしまうことがあります。

影響と想定される被害範囲

影響を受けた主要パッケージは、非常に多くインストールされています。npmの記録では、7月5日から8月3日までのダウンロード数は、keyvが619,682,667回、flat-cacheが579,751,309回、file-entry-cacheが571,240,025回でした。

これらの数値は、パッケージ同士が依存関係にあり、同じツールチェーンで使われるため、大きく重複しています。エコシステム内での到達範囲を示すものであり、侵害されたホストの数ではありません。また、各悪意のあるバージョンが公開されていた期間は、1か月よりはるかに短いものでした。

Snykの監視対象プロジェクトでは、広範囲に影響が及んでいることを確認しています。推移的な依存関係にも注意が必要です。flat-cacheやfile-entry-cacheは、ESLintなどの開発ツール経由で導入されることがよくあります。開発者のノートPCやCIランナーは、GitHub、npm、クラウド、署名、デプロイ用の認証情報を保持していることが多く、特に価値の高い標的です。

執筆時点で、第2段階の実行が成功した件数や、窃取された認証情報の数について、検証済みの公開情報はありません。パッケージは本番のnpmレジストリに実際に公開され、スナップショット時点で8件がlatestタグに残っていたため、研究室内のエクスプロイトではなく、現実に配布されたことが確認できます。Snykのアドバイザリではエクスプロイトの成熟度を「攻撃実行済み」と分類していますが、被害者数は公表していません。

影響を検出する方法

まず、推移的な依存関係も含め、解決済みの依存関係ツリーを確認します。

npm ls keyv flat-cache file-entry-cache cacheable-request cacheable \
  @cacheable/utils cache-manager @cacheable/net \
  @cacheable/node-cache @cacheable/memory ecto --all

npmがdist-tagを変更した後も、影響を受けるバージョンが固定されたままの場合があるため、ロックファイルを検索します。

rg -n \
  'keyv|flat-cache|file-entry-cache|cacheable-request|cacheable|cache-manager|@cacheable/|ecto' \
  package-lock.json npm-shrinkwrap.json pnpm-lock.yaml yarn.lock

パッケージコードを実行せずに、インストール済みマニフェストから正確なフックを探します。

find node_modules -name package.json -print0 |
  xargs -0 node -e '
    const fs = require("node:fs");
    for (const file of process.argv.slice(1)) {
      try {
        const pkg = JSON.parse(fs.readFileSync(file, "utf8"));
        if (pkg.scripts?.preinstall === "node setup.mjs") {
          console.log(`${pkg.name}@${pkg.version} ${file}`);
        }
      } catch {}
    }
  '

ペイロードと永続化の痕跡を調査します。

find "$HOME" /tmp \
  \( -name setup.mjs -o -name Math_Symbol.js -o -name math_init.js \
     -o -name gh-token-monitor.sh -o -name gh-token-monitor.service \
     -o -name com.user.gh-token-monitor.plist \) \
  -print 2>/dev/null

また、露出したGitHub認証情報でアクセス可能なリポジトリを確認し、予期しない.claude/settings.json、.vscode/tasks.json、toJSON(secrets)を含むワークフローファイル、新規作成されたGitHub Actionsアーティファクトがないか調べてください。

Snykをご利用のお客様は、新しいkeyv@6.0.0のアドバイザリを活用し、インシデント情報の更新に応じて依存関係テストを再実行し、プロジェクトを監視してください。

snyk test --all-projects
snyk monitor --all-projects

正規パッケージの侵害に関するSnyk Learnのレッスンでは、依存関係の完全性とレジストリ制御を通常の開発ワークフローに組み込むための補足情報を紹介しています。

修復

影響を受けるパッケージをインストールした場合

node_modulesをすでに削除していても、そのマシンまたはランナーは侵害されている可能性があるものとして扱ってください。

  1. ホストを通常のネットワークアクセスから隔離します。調査に備え、ログ、プロセスデータ、npmログ、CIジョブの出力、ファイルシステムのタイムスタンプを保全してください。

  2. GitHubの認証情報を失効させる前に、gh-token-monitorの永続化を探してください。~/.local/bin/gh-token-monitor.sh、~/.config/gh-token-monitor/、~/.config/systemd/user/gh-token-monitor.service、~/Library/LaunchAgents/com.user.gh-token-monitor.plistを確認します。

  3. 永続化の痕跡が見つかった場合は無効化してください。インシデント対応担当者と連携して削除し、フォレンジック用のコピーを保管してください。認証情報の失効が監視ツールに検知される可能性があります。

  4. 安全が確認されたシステムから認証情報をローテーションしてください。GitHub PATとAppトークン、npmトークン、AWS、GCP、Azure、Vault、Kubernetes、データベースの認証情報、秘密鍵、影響を受けたCIジョブで利用可能だったすべてのシークレットが対象です。

  5. アカウントとクラウドの監査ログを確認してください。不審なnpmパッケージの公開、リポジトリの作成、ワークフローの変更、Actionsのアーティファクト、クラウドAPI呼び出し、不審な場所からのトークン利用がないか調べます。

  6. 影響を受けたアーティファクトをプライベートレジストリとキャッシュから削除してください。npmから削除しても、社内プロキシや開発者のキャッシュに保存済みのコピーは消えません。

再インストール前に安全性が確認されたバージョンを固定する

確認時点で、以下の過去のリリースにはレジストリマニフェストにインストール時のライフサイクルフックがありませんでした。

{
  "overrides": {
    "keyv": "5.6.0",
    "flat-cache": "6.1.23",
    "file-entry-cache": "11.1.5",
    "cacheable-request": "13.0.19",
    "cacheable": "2.5.0",
    "@cacheable/utils": "2.5.0",
    "cache-manager": "7.2.9",
    "@cacheable/net": "2.1.0",
    "@cacheable/node-cache": "3.1.1",
    "@cacheable/memory": "2.2.0",
    "ecto": "5.0.0"
  }
}

keyv@5.6.0は、調査時点で最も新しい、安全性が確認された安定版の5.xです。keyv@6.0.0から5.xに戻すには、コードの変更が必要になる場合があります。6.0.0-rc.1のdist/出力はバイト単位で一致し、ライフサイクルフックもありませんでした。ただし、リリース候補版を明示的に検証済みの場合を除き、本番環境では確立された安定版を選ぶべきです。

オーバーライドを追加したら、ライフサイクルスクリプトを実行せずにロックファイルを再生成します。

npm install --package-lock-only --ignore-scripts
rm -rf node_modules
npm cache clean --force
npm ci --ignore-scripts
npm ls keyv flat-cache file-entry-cache cacheable-request cacheable \
  @cacheable/utils cache-manager @cacheable/net \
  @cacheable/node-cache @cacheable/memory ecto --all

ライフサイクルスクリプトを無効にすると、この実行経路のリスクを抑えられます。正規のパッケージでもインストールスクリプトが必要な場合があるため、スクリプトを一律に有効化するのではなく、対象を限定した許可リストを管理してください。Snykのnpmセキュリティのベストプラクティスでは、決定論的なインストール、スクリプトの制御、パッケージのレビューについて詳しく解説しています。

インシデントの時系列

以下の時刻はすべて、2026年8月4日のUTCです。

  • 09:02~09:17:コミットee2681a9でkeyv@6.0.0を準備し、ライフサイクルフックとペイロードファイルを追加。ローダーを実行するテストも追加されました。GitHubの記録では、作成者の時刻は09:02、コミッターの時刻は09:17です。

  • 09:04:確認済みのコミットd8c850c7で、ClaudeとVS Codeの実行フックが追加されました。

  • 09:23:コミットf97eabcdで、preinstallテストが削除されました。

  • 09:30~09:32:複数の@keyv/*バージョン6のパッケージが、悪意のあるフックなしで公開されました。

  • 09:35:npmで悪意のあるフックを含むkeyv@6.0.0が公開されました。

  • 09:51:コミット1f79edd8で@keyv/*の各ワークスペースにペイロードファイルが追加され、以降のリリースにもリスクが生じました。

  • 09:49~09:51:GitHubのIssue#2044、#2045、#2046でインシデントが報告されました。後にIssue APIは410 Goneを返しました。

  • 10:09~10:14:悪意のあるCacheable系リリースが公開されました。

  • 10:18と10:20:セキュリティ研究者のCharlie Eriksenが、提供された2件の公開警告を投稿しました。

  • 10:28:同じペイロードを含むecto@5.0.1が公開され、メンテナーに関連するパッケージは11個になりました。

  • 10:39:cacheable-request@13.0.20の削除後、npmレジストリのメタデータが変更されました。

  • 10:42:flat-cache@6.1.24の削除後、npmレジストリのメタデータが変更されました。

  • 11:11:cache-manager@7.2.10の削除後、npmレジストリのメタデータが変更されました。

  • 11:16:Snykのレジストリスナップショットでは、latestタグに悪意のあるリリースが8件残っていることが確認されました。

  • 執筆中:SnykはSNYK-JS-KEYV-18515941と、ほか約70件のアドバイザリを公開しました。

この時系列は、進行中のインシデントを反映しています。公開直前に、npmのマニフェスト、dist-tag、Snykのアドバイザリを再確認してください。

オンデマンドウェビナー

OpenAIは自らの答案を採点し、その後本番環境に侵入した

オンデマンド配信をご覧いただき、自己検証が構造上うまく機能しない理由、複数モデルのスタックが問題を悪化させる理由、そして独立した検証を実践する方法をご確認ください。どの研究機関が開発したかにかかわらず、環境内のあらゆるAI資産を管理するためのフレームワークを学べます。