axiosが乗っ取られた — npm史上最大級のサプライチェーン攻撃の全容と対策

axiosnpmサプライチェーン攻撃セキュリティNode.jsRAT

2026年3月31日(UTC)、週間1億ダウンロードを超えるHTTPクライアントライブラリaxiosのnpmパッケージが乗っ取られました。メインメンテナーのアカウントが侵害され、マルウェアを含む2つのバージョンが公開されています。

Theo氏(t3.gg)のポストが端的に状況を表しています。

Axios just got pwn’d. This is really bad.

npmパッケージのサプライチェーン攻撃としては史上最大級の影響範囲です。何が起きたのか、自分のプロジェクトは安全なのか、今後どう守るべきかを整理します。

何が起きたのか

タイムライン

日時(UTC)イベント
3/30 05:57plain-crypto-js@4.2.0 公開(クリーンなおとり版)
3/30 16:03C2ドメイン sfrclak.com をNameCheapで登録
3/30 23:59plain-crypto-js@4.2.1 公開(マルウェア入り)
3/31 00:21axios@1.14.1 公開(latestタグ)
3/31 01:00axios@0.30.4 公開(legacyタグ)
3/31 01:50GitHub Security Advisory発行
3/31 ~03:00npmが悪意あるバージョンを削除

攻撃者はおとり版を18時間前に仕込み、C2ドメインを8時間前に取得するなど、入念な計画のもとで実行しています。

アカウント乗っ取りの手口

攻撃者はaxiosのメインメンテナー jasonsaayman のnpmアカウントを乗っ取りました。

  • アカウントのメールアドレスを ifstap@proton.me(攻撃者が管理するProtonMailアドレス)に変更
  • 正規オーナーのJason Saayman氏をアカウントからロックアウト
  • GitHub ActionsのCI/CDパイプラインを完全にバイパスして、ローカルから直接npm publishを実行

Elastic Security Labsの分析によれば、正規のaxios@1.14.0はGitHub ActionsのOIDC Trusted Publisher経由で公開されていたのに対し、axios@1.14.1はCLIからの手動公開でした。この「公開方法の断絶」が侵害の決定的な証拠です。

ソースコードは一切変更なし

ここが巧妙な点です。axiosのソースコード自体は1行も変更されていません

変更されたのは package.json のみ。plain-crypto-js という偽パッケージが依存関係として追加されただけです。このパッケージはaxiosのコード内で一度もimportされることなく、postinstallフックを実行するためだけに存在するファントム依存関係です。

{
  "dependencies": {
    "plain-crypto-js": "^4.2.1"
  }
}

npm install axios@1.14.1 を実行すると、plain-crypto-js が自動的にインストールされ、その postinstall スクリプトが node setup.js を実行。ここからマルウェアのダウンロードが始まります。

マルウェアの動作

この攻撃で配布されたマルウェアは**RAT(Remote Access Trojan: 遠隔操作型トロイの木馬)**と呼ばれる種類のものです。

RATはマルウェアの一カテゴリで、攻撃者が感染マシンをリアルタイムに遠隔操作できるのが特徴です。単に情報を盗んで終わるスパイウェアとは異なり、RATは感染後もC2サーバー経由で攻撃者の指令を待ち続けます。つまり感染したマシンはずっと攻撃者の支配下に置かれることになります。

C2(Command and Control)サーバーとは

C2サーバーは、マルウェアが攻撃者と通信するための指令センターです。今回の攻撃ではC2ドメインとして sfrclak.com が使われました。

攻撃の全体像を時系列で整理するとこうなります。

npm install axios@1.14.1

postinstallでsetup.jsが実行される

setup.jsが http://sfrclak.com:8000/6202033 に接続  ← C2サーバー

C2がOSを判定し、macOS/Windows/Linux用のRATを返す

RATがインストールされる

以後60秒ごとにC2に「次の命令ある?」とビーコンを送り続ける

攻撃者はC2から任意のコマンドを送信(ファイル窃取、追加マルウェア投入等)

C2ドメインが攻撃の8時間前に取得されたのには理由があります。あまり早く取得するとドメインの評判スコアが構築され、セキュリティツールに検知されるリスクが上がります。かと言って直前すぎるとDNSが伝播しません。8時間前という取得タイミングは、検知を回避しつつDNSの伝播を確保する計算された選択です。

ドロッパー(setup.js)

setup.js(4,209バイト)はRATを配布するための「運び屋」です。以下の処理を行います。

  1. XOR暗号化(キー: OrDeR_7077、定数値: 333)とBase64でエンコードされたC2 URLをデコード
  2. C2サーバー http://sfrclak.com:8000/6202033 に接続
  3. OSを判定し、プラットフォーム別のRATをダウンロード・実行
  4. 自身を削除fs.unlink
  5. package.jsonを削除し、クリーンなpackage.mdに差し替え

この自己消去により、npm auditでもnode_modules検査でも痕跡が残りません。

プラットフォーム別RAT

3つのOS全てに対応したRATが用意されていました。

OS保存先サイズ永続化
macOS/Library/Caches/com.apple.act.mond657KB(Mach-O universal)なし
WindowsC:\ProgramData\wt.exe + system.bat11,042B(PowerShell)レジストリ Run キー「MicrosoftUpdate」
Linux/tmp/ld.py12,323B(Python)なし

すべてのRATは共通のアーキテクチャを持ちます。

  • 60秒間隔のビーコン送信
  • HTTP POST + Base64エンコードJSON通信
  • 偽装User-Agent: mozilla/4.0 (compatible; msie 8.0...)(IE8/Windows XP偽装 — 2026年にこのUser-Agentが飛んでくること自体が異常)
  • 4つのコマンド: kill(終了)、runscript(任意コマンド実行)、peinject(DLL注入)、rundir(ディレクトリ列挙)

Elastic Security Labsは、このIE8偽装User-Agentを「trivially detectable on any modern network(現代のネットワークで自明に検出可能)」と評しています。特にmacOSやLinuxホストからこのUser-Agentが送信されれば、即座にアラートが上がるべきです。

攻撃者の帰属

Googleの脅威分析チーム(GTIG)は、この攻撃を**UNC1069(北朝鮮系BlueNoroff)**に帰属させています

根拠は2つ。

  1. WAVESHAPER.V2バックドアの使用 — UNC1069が2018年から使用しているWAVESHAPERの更新版
  2. macOS RATの内部プロジェクト名 macWebT — BlueNoroffのRustBucketマルウェアキャンペーンで文書化されているwebTモジュールとの直接的な関連

Andrej Karpathy氏もこの攻撃を「New supply chain attack this time for npm axios, the most…」と取り上げており、AI/ML研究コミュニティにも衝撃が広がりました。

影響範囲

  • axiosの週間ダウンロード数: 約1億
  • 直接依存プロジェクト: 174,000以上
  • クラウド環境とコード環境の**約80%**にaxiosが存在
  • 悪意あるバージョンの公開時間: 約3時間

3時間という短い露出時間にもかかわらず、CI/CDパイプラインでの自動インストールにより、**影響を受けた環境の3%**で実際にRATが実行されたと報告されています。

自分のプロジェクトは大丈夫か?

即座に確認すべきこと

# 影響を受けたバージョンがインストールされていないか確認
npm list axios | grep -E "1\.14\.1|0\.30\.4"

# 悪意ある依存関係が存在しないか確認
ls node_modules/plain-crypto-js && echo "⚠ 感染の可能性あり"

# macOSでのRAT確認
ls -la /Library/Caches/com.apple.act.mond

# WindowsでのRAT確認
dir "%PROGRAMDATA%\wt.exe"
dir "%PROGRAMDATA%\system.bat"
reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v MicrosoftUpdate

# LinuxでのRAT確認
ls -la /tmp/ld.py

lockfileがあれば守られた可能性が高い

package-lock.json(またはyarn.lock)をコミットしていて、CI/CDでnpm ciを使っていた場合、影響を受けていない可能性が高いです。

npm ciはlockfileに記録されたバージョンを厳密に使用するため、lockfileが悪意あるバージョンの公開前に生成されていれば、新バージョンが自動的にインストールされることはありません。

逆に、以下のケースは危険です。

  • npm installyarn install--frozen-lockfileなし)を使っていた
  • lockfileをコミットしていなかった
  • 攻撃時間帯(3/31 00:21〜03:00 UTC)にインストールコマンドを実行した
  • ^1.14.0のような範囲指定をしていた

パッケージマネージャーの混在に要注意

見落としがちなのが、プロジェクト内でパッケージマネージャーが混在しているケースです。

例えば、こんな状態になっていないでしょうか。

  • 開発環境やCIではnpm ciを使っている(package-lock.jsonで管理)
  • 本番デプロイ用のDockerfileではyarn installを使っている(yarn.lockで管理)

この場合、package-lock.jsonは日々のCIで最新に保たれていても、yarn.lockは更新されないまま放置されている可能性があります。あるいはその逆で、yarn.lockだけが正でpackage-lock.jsonは古いまま、ということもあり得ます。

パッケージマネージャーの移行期にこのような状態が生まれやすく、2つのlockfileが示すバージョンが食い違っていると、片方は安全でも、もう片方経由で悪意あるバージョンがインストールされるリスクがあります。Dockerfileのyarn installにはlockfile固定のフラグ(--frozen-lockfile)が付いていなかった、というのはよくある話です。

確認すべきポイント:

  • lockfileはリポジトリに1種類だけか? package-lock.jsonyarn.lockが両方存在していないか
  • Dockerfileのインストールコマンドにlockfile固定フラグが付いているか? yarn install --frozen-lockfilenpm ciになっているか
  • CI/CDと本番で同じパッケージマネージャーを使っているか?

もし複数のlockfileが存在する場合は、使わない方を.gitignoreに追加するか削除し、パッケージマネージャーを1つに統一してください。

「package.jsonに無いから大丈夫」は危険

「うちのプロジェクトはaxiosを直接使っていないから関係ない」と思うかもしれません。しかし、axiosは間接依存(transitive dependency)として、自分の知らないうちに入っているケースが非常に多いパッケージです。

間接依存とは、自分のプロジェクトが直接依存しているパッケージが、さらにその内部でaxiosに依存しているケースです。

あなたのプロジェクト
  └── some-api-client@2.0.0  ← package.jsonに書いてある
        └── axios@1.14.1     ← package.jsonには書いていない。勝手に入っている

package.jsonを見てもaxiosは出てきません。node_modulesの中を掘るか、専用のコマンドを使わないと存在に気づけません。

調べ方

npm

# axiosが依存ツリーのどこにいるか表示(間接依存も含む)
npm ls axios

# なぜaxiosがインストールされているのか、依存チェーンを表示
npm why axios

Yarn

# Yarn Classic
yarn why axios

# Yarn Berry
yarn why axios

pnpm

pnpm why axios

例えば npm ls axios の出力がこうなった場合:

my-project@1.0.0
└─┬ some-api-client@2.0.0
  └── axios@1.14.1   ← ⚠ 悪意あるバージョン!

some-api-clientが内部でaxios@1.14.1に依存しているため、自分のpackage.jsonにaxiosが無くても感染の可能性があります。

lockfileでも確認できる

npm lsが使えない環境(CIのログしか残っていない等)では、lockfileを直接検索する方法もあります。

# package-lock.jsonから検索
grep -E '"axios".*"1\.14\.1"|"axios".*"0\.30\.4"' package-lock.json

# yarn.lockから検索
grep -A 1 "axios@" yarn.lock | grep -E "1\.14\.1|0\.30\.4"

# pnpm-lock.yamlから検索
grep -E "axios.*1\.14\.1|axios.*0\.30\.4" pnpm-lock.yaml

間接依存を固定する

間接依存のaxiosが危険なバージョンに解決されるのを防ぐには、overrides(npm)やresolutions(Yarn)で強制的にバージョンを固定できます。

// npm — package.jsonに追加
{
  "overrides": {
    "axios": "1.14.0"
  }
}

// Yarn — package.jsonに追加
{
  "resolutions": {
    "axios": "1.14.0"
  }
}
# pnpm — pnpm-workspace.yamlに追加(pnpm v10以降はpackage.jsonのoverrdesも可)
overrides:
  axios: "1.14.0"

この設定により、依存ツリーのどの深さにaxiosが存在しても、強制的に安全なバージョンが使われます。

感染が確認された場合

  1. 即座にネットワークから隔離
  2. 全認証情報をローテーション(npm tokens、SSH鍵、AWSキー、クラウド認証情報、CI/CDシークレット)
  3. 安全なバージョンにダウングレード
npm install axios@1.14.0
  1. マシンを既知良好イメージから再構築(RATの完全な除去を保証するため)

今後同じことを防ぐために

この攻撃から学べる防御策を整理します。

1. lockfileをコミットし、厳密インストールを使う

最も基本的で最も効果的な防御です。すべてのパッケージマネージャーで対応するコマンドがあります。

パッケージマネージャーlockfile厳密インストールpostinstall無効化
npmnpm cinpm ci --ignore-scripts
Yarn Classic (v1)yarn install --frozen-lockfileyarn install --frozen-lockfile --ignore-scripts
Yarn Berry (v2+)yarn install --immutable.yarnrc.ymlenableScripts: falseを設定
pnpmpnpm install --frozen-lockfilepnpm install --frozen-lockfile --ignore-scripts

npm installyarn install(フラグなし)はlockfileを更新する可能性があるため、CI/CDでは絶対に使わない。lockfileは必ずバージョン管理にコミットしてください。

よくある誤解として「yarn installならlockfileを見てくれるから安全」と思いがちですが、Yarn Classic のyarn installはlockfileに存在しない新しいパッケージを解決して追加する場合があります。--frozen-lockfileを付けないと、lockfileとの差分が生じた時点でエラーにならず、サイレントに新バージョンが入る可能性があります。

2. postinstallスクリプトを制御する

今回の攻撃はpostinstallフック経由でした。「とりあえず--ignore-scriptsしておけ」という対策をよく見かけますが、これは何を壊す可能性があるのかを理解した上で判断すべきです。

そもそもpostinstallは何のためにあるのか

postinstallはnpm install完了後に自動実行されるスクリプトです。npm公式ドキュメントでも「install/preinstallスクリプトの唯一の正当な用途は、ターゲットアーキテクチャ上でのコンパイル」と明記されています

実際に正当な理由でpostinstallを使っているパッケージは多数あります。

パッケージpostinstallで行っていること
esbuildOS/アーキテクチャに応じたネイティブバイナリをダウンロード
sharplibvipsのネイティブバインディングをコンパイル(画像処理)
bcryptC++で書かれたハッシュ関数をnode-gyp経由でコンパイル
PrismaPrisma Engineのバイナリを生成
sqlite3SQLiteのネイティブアドオンをコンパイル
canvasCairo/Pangoのバインディングをコンパイル

これらはJavaScriptだけでは実現できない処理(C/C++コンパイル、プラットフォーム固有バイナリの配置)を行うためにpostinstallを必要としています。

特にesbuildの影響は甚大です。esbuildはそれ単体で使うケースよりも、他のツールの内部エンジンとして組み込まれているケースの方が圧倒的に多いためです。

Vite → 内部でesbuildを使用
  ├── Astro(このブログもAstro製)
  ├── Remix
  ├── SvelteKit
  ├── Nuxt
  └── その他Viteベースのフレームワーク全般

tsup → 内部でesbuildを使用
tsx  → 内部でesbuildを使用

ただし、esbuildは現在プリビルド済みバイナリをoptionalDependenciesで配布する方式に移行しているため、--ignore-scriptsでもビルドは壊れません(実際にこのブログで検証済みです)。とはいえ、すべてのパッケージがこの方式に対応しているわけではないため、--ignore-scriptsの導入前には必ず自分のプロジェクトでテストが必要です。

--ignore-scriptsすると何が壊れるか

--ignore-scriptsを設定した場合の影響は、パッケージによって異なります。

壊れないケース: esbuild

実際にこのブログ(Astro製)のプロジェクトでnpm ci --ignore-scriptsを試したところ、ビルドは正常に成功しました

esbuildは現在、2段構えの方式を取っています。

  1. @esbuild/darwin-arm64のようなプラットフォーム別パッケージをoptionalDependenciesで配布(プリビルド済みバイナリ入り)
  2. postinstallinstall.jsはフォールバック用(1が失敗した場合にバイナリをダウンロード)

--ignore-scriptsでpostinstallがスキップされても、1のプリビルド済みバイナリはnpmの通常の依存解決で配置されるため、多くの環境では問題なく動作します。Vite/Astro/Remix等のesbuild依存プロジェクトは--ignore-scriptsでも壊れない可能性が高いです。

壊れないケース: sharp、bcrypt

sharp、bcryptもesbuildと同様に、現在はプリビルド済みバイナリをoptionalDependenciesで配布する方式に移行しています。実際にnpm install --ignore-scriptsで試したところ、どちらも正常に動作しました。

壊れるケース: better-sqlite3、Prisma

一方、すべてのパッケージがプリビルド方式に対応しているわけではありません。

# better-sqlite3の場合 — node-gypコンパイルが必要
const Database = require('better-sqlite3');
// Error: Could not locate the bindings file.

# Prismaの場合 — エンジンバイナリの生成が必要
const { PrismaClient } = require('@prisma/client');
// Error: Cannot find module '.prisma/client/default'

実際にテストした結果をまとめます。

パッケージ--ignore-scriptsで壊れるか理由
esbuild壊れないoptionalDependenciesでプリビルド済みバイナリ配布
sharp壊れない同上
bcrypt壊れない同上
better-sqlite3壊れるnode-gypコンパイルが必須
Prisma壊れるエンジンバイナリ生成が必須

ユーザー数の多いパッケージほど、プリビルド済みバイナリをoptionalDependenciesで配布する方式への移行が進んでいます。以前はnode-gypでユーザーの環境上でC/C++コンパイルする方式が主流でしたが、Python・C++コンパイラ・make等のビルドツールが必要で環境構築トラブルが頻発していたため、主要パッケージが順次プリビルド方式に切り替えました。結果として、--ignore-scriptsの実害は以前より小さくなっています。

Prismaが壊れるのは少し事情が異なります。Prismaはユーザーが定義したschema.prismaに基づいてクライアントコードを生成する必要があるため、単純なバイナリ配布では対応できません。

いずれにせよ、インストール時にはエラーが出ず、実行時に初めて壊れるのが厄介です。「npm ci --ignore-scriptsでインストールが通ったから大丈夫」と思いきや、デプロイ後に本番で落ちる、というパターンに注意が必要です。

結論: まず自分のプロジェクトで--ignore-scriptsを試し、ビルドとテストが通るか確認するのが最も確実です。壊れるパッケージがあれば、後述の許可リスト方式で個別に許可してください。

現実的な対策: 許可リスト方式

全スクリプトを一律に無効化するのではなく、信頼するパッケージのみスクリプト実行を許可するアプローチが現実的です。

pnpm v10のonlyBuiltDependencies(推奨)

pnpm v10はライフサイクルスクリプトをデフォルトでブロックするようになりました(pnpm公式ブログ)。これはaxios攻撃より前の2025年に、Rspackサプライチェーン攻撃を受けて導入された対策です。

# 対話的に許可するパッケージを選択
pnpm approve-builds

これによりpnpm-workspace.yamlに許可リストが生成されます。

onlyBuiltDependencies:
  - esbuild
  - sharp
  - prisma

pnpm 10.26以降では、より柔軟なallowBuilds設定も利用可能です。

npm/Yarn向け: LavaMoat allow-scripts

npm/Yarnには同等のビルトイン機能がないため、LavaMoat の allow-scriptsを使って許可リスト管理ができます。

{
  "lavamoat": {
    "allowScripts": {
      "esbuild": true,
      "sharp": true,
      "prisma": true,
      "plain-crypto-js": false
    }
  }
}

この方式なら、esbuildやsharpは正常に動作しつつ、見知らぬパッケージのpostinstallはブロックされます。今回のaxios攻撃で注入されたplain-crypto-jsは許可リストに存在しないため、実行されることはありません。

3. バージョンを完全固定する

{
  "dependencies": {
    "axios": "1.14.0"
  },
  "overrides": {
    "axios": "1.14.0"
  }
}

^(キャレット)や~(チルダ)を使わず、完全固定にすることで、意図しないバージョンアップを防ぎます。overridesフィールドを使えば、間接依存のバージョンも固定できます。

4. npm provenanceを確認する

npm provenanceは、パッケージがどのCI/CDパイプラインから公開されたかを暗号的に証明する仕組みです。

今回の攻撃では、正規版(1.14.0)はGitHub ActionsのOIDC Trusted Publisher経由で公開されていたのに対し、悪意あるバージョン(1.14.1)はCLIからの手動公開でした。provenanceを確認していれば、この断絶を検知できた可能性があります。

# パッケージのprovenanceを確認
npm audit signatures

5. 新バージョンの冷却期間を設ける

# 公開から3日以上経過したバージョンのみ許可
npm config set min-release-age 3

この設定により、公開直後の「ゼロデイ」的なサプライチェーン攻撃の影響を受けにくくなります。今回の攻撃は約3時間で削除されたため、3日間の冷却期間があれば完全に防げました。

**ただし、これは全パッケージに一律適用される点に注意が必要です。**冷却期間中は、axios以外のライブラリに緊急度の高いセキュリティ脆弱性が見つかった場合でも、そのパッチを3日間インストールできません。「axiosの攻撃は防げたけど、別のライブラリのCriticalな脆弱性パッチが当てられなかった」では本末転倒です。

この問題に対するパッケージマネージャーごとの対応状況は以下の通りです。

パッケージマネージャー冷却期間パッケージ単位の除外
npmmin-release-age(v11.10.0〜)できないIssue #8994で要望中)
pnpmminimumReleaseAge(v10.16〜)できる(除外ルール対応)
Yarn BerrynpmMinimalAgeGate(v4.10〜)できるnpmPreapprovedPackagesで免除リスト)

pnpmとYarn Berryでは「基本は冷却期間を適用しつつ、特定のパッケージだけ除外する」という運用が可能ですが、npmではまだできません。npmを使っている場合、冷却期間の導入は「緊急パッチの適用遅延」というリスクとのトレードオフを理解した上で判断してください。

6. ネットワークエグレスを制御する

CI/CD環境からの外部通信を制限することで、たとえpostinstallが実行されてもC2サーバーへの接続をブロックできます。

StepSecurityのHarden-Runnerは、GitHub Actionsのネットワークエグレスをホワイトリスト方式で制御し、今回の攻撃でもC2サーバーへの通信を検出・ブロックしています。

7. 依存関係の監視を自動化する

  • Snyk: SNYK-JS-AXIOS-15850650 として検出
  • npm audit: 定期的に実行
  • Dependabot / Renovate: セキュリティアップデートの自動PR

npmエコシステムの構造的課題

この攻撃は「1人のメンテナーのアカウントが乗っ取られれば、数億のダウンロードに影響を与えられる」というnpmエコシステムの構造的な脆弱性を浮き彫りにしました。

klöss氏のポストが問題の本質を突いています。

do you understand what just happened to one of the most used npm packages on the internet? → axios gets downloaded over 100 million times a week and today it got compromised → an attacker hijacked the npm credentials of a lead axios maintainer… changed the account email to…

週1億ダウンロードのパッケージが、1つのアカウントの認証情報だけで乗っ取れる。これは個別のプロジェクトの問題ではなく、エコシステム全体の信頼モデルの問題です。

npm provenanceやOIDC Trusted Publisherといった仕組みは存在しますが、まだ「推奨」であり「強制」ではありません。今回のインシデントを機に、トップパッケージに対するprovenanceの必須化や、公開方法の変更検知の仕組みが議論されることを期待します。

IOC(侵害指標)

ネットワーク

項目
C2ドメインsfrclak.com
C2 IP142.11.206.73(Hostwinds VPS)
C2ポート8000
関連ドメインcallnrwise.com

ファイルハッシュ(SHA-256)

ファイルハッシュ
setup.js(ドロッパー)e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09
macOS RAT92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a
Windows RAT617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101
Linux RATfcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf

ホスト指標

OSパス
macOS/Library/Caches/com.apple.act.mond
WindowsC:\ProgramData\wt.exeC:\ProgramData\system.bat
Windows(レジストリ)HKCU\Software\Microsoft\Windows\CurrentVersion\Run\MicrosoftUpdate
Linux/tmp/ld.py