Next.js・React Server Componentsが危険?async_hooksの脆弱性CVE-2025-59466を徹底解説
Next.js や React Server Components を使用している環境で、try-catch が効かずサーバーがクラッシュする深刻な脆弱性(CVE-2025-59466)が報告されました。本記事では、この脆弱性の仕組み・影響範囲・防御策を、Next.js 開発者の視点から徹底解説します。
「async_hooks の脆弱性」「Next.js のセキュリティ対策」「AsyncLocalStorage の安全性」を知りたい方に向けた記事です。
async_hooks 脆弱性の影響比較表
| # | 環境 | async_hooks 有効 | try-catch | スタックオーバーフロー時の動作 | 緊急度 |
|---|---|---|---|---|---|
| 1 | Next.js App Router | 自動で有効 | 効かない | 即座にクラッシュ(終了コード7) | 最優先 |
| 2 | Next.js Pages Router | 自動で有効 | 効かない | 即座にクラッシュ(終了コード7) | 最優先 |
| 3 | Express + APM | APM経由で有効 | 効かない | 即座にクラッシュ(終了コード7) | 最優先 |
| 4 | Express(APMなし) | 無効 | 効く | RangeError をキャッチ可能 | 低 |
| 5 | 素の Node.js | 無効 | 効く | RangeError をキャッチ可能 | 低 |
この表は即答用です。各環境での詳細な影響と対策は後段で解説します。
検証環境
- OS: macOS Sonoma 14.7 / Ubuntu 22.04 LTS
- Node.js: 22.21.0(脆弱性確認)→ 22.22.0(修正確認)
- Next.js: 15.1.0
- React: 19.0.0
- 主要パッケージ:
- @opentelemetry/sdk-node: 0.57.0
- dd-trace: 5.30.0
- 検証日: 2026年1月14日
背景:なぜ Next.js 環境で async_hooks が危険なのか
この章でわかること
- async_hooks とは何か(初学者向け)
- Next.js が内部で async_hooks を使用する理由
- 脆弱性が発生するメカニズム
async_hooks の基本(初学者向け)
async_hooks は、Node.js が提供する非同期処理の追跡機能です。Promise や setTimeout などの非同期処理がいつ開始・終了したかを監視できます。
javascriptimport { createHook } from "node:async_hooks";
// 非同期処理の開始・終了を追跡
const hook = createHook({
init(asyncId, type) {
console.log(`非同期処理 ${type} が開始: ID=${asyncId}`);
},
destroy(asyncId) {
console.log(`非同期処理が終了: ID=${asyncId}`);
},
});
hook.enable();
この機能を使うと、「どのリクエストがどの処理を実行しているか」を追跡できるため、APM ツールやフレームワークが活用しています。
Next.js が async_hooks を使用する理由
Next.js の App Router や React Server Components は、AsyncLocalStorage という API を内部で使用しています。AsyncLocalStorage は async_hooks をベースに構築されており、リクエストごとのコンテキスト(認証情報、ロケールなど)を保持するために使われます。
以下の図は、Next.js における async_hooks の利用構造を示しています。
mermaidflowchart TD
subgraph nextjs["Next.js App Router"]
rsc["React Server<br/>Components"]
middleware["Middleware"]
api["API Routes"]
end
subgraph internal["内部実装"]
als["AsyncLocalStorage"]
hooks["async_hooks"]
end
subgraph context["保持されるコンテキスト"]
auth["認証情報"]
locale["ロケール"]
headers["リクエストヘッダー"]
end
rsc --> als
middleware --> als
api --> als
als --> hooks
als --> context
図の補足:Next.js の主要機能は AsyncLocalStorage を経由して async_hooks を使用しており、開発者が意識しなくても自動的に有効化されています。
脆弱性が発生するメカニズム
問題は、async_hooks が有効な状態でスタックオーバーフローが発生した場合です。通常、スタックオーバーフローは RangeError: Maximum call stack size exceeded として try-catch でキャッチできます。
しかし、async_hooks が有効な場合、Node.js は内部で TryCatchScope::kFatal というモードでフック処理をラップしています。このモードでは、エラーが発生すると無条件にプロセスを終了させます。
mermaidflowchart LR
subgraph normal["async_hooks 無効時"]
A1["再帰処理"] --> A2["スタック枯渇"]
A2 --> A3["RangeError"]
A3 --> A4["try-catch で<br/>キャッチ可能"]
end
subgraph vuln["async_hooks 有効時"]
B1["再帰処理"] --> B2["スタック枯渇"]
B2 --> B3["フック内で<br/>RangeError"]
B3 --> B4["kFatal モード<br/>により即終了"]
B4 --> B5["try-catch<br/>無効"]
end
図の補足:async_hooks が有効な場合、スタックオーバーフローエラーは「致命的フックエラー」として扱われ、通常のエラーハンドリングをバイパスしてプロセスが終了します。
つまずきポイント
- 「自分のコードで async_hooks を使っていない」と思っても、Next.js や APM ツールが自動的に有効化しています
- AsyncLocalStorage は async_hooks の上に構築されているため、
new AsyncLocalStorage()を使うだけで影響を受けます
課題:CVE-2025-59466 の詳細と攻撃シナリオ
この章でわかること
- 脆弱性の技術的な詳細
- 攻撃が成立する条件
- 実際の攻撃コード例
脆弱性の技術的詳細
| 項目 | 内容 |
|---|---|
| CVE番号 | CVE-2025-59466 |
| 深刻度 | MEDIUM(ただし影響範囲が広いため実質 HIGH) |
| 影響バージョン | Node.js 20.x〜25.x(修正前) |
| 修正バージョン | 20.20.0, 22.22.0, 24.13.0, 25.3.0 |
| 攻撃種別 | リモート DoS(サービス拒否) |
| 認証要否 | 不要 |
攻撃が成立する条件
以下のすべての条件が揃うと、攻撃が成立します。
- async_hooks が有効(Next.js、APM ツール、AsyncLocalStorage 使用時)
- 再帰的な処理が存在(ネストしたデータの処理など)
- 入力データのネスト深度が制限されていない
Next.js アプリケーションでは、条件1は自動的に満たされるため、条件2と3が揃えば攻撃可能です。
攻撃コード例:Next.js API Route
以下は、脆弱な Next.js API Route の例です。
javascript// app/api/process/route.js(脆弱なコード)
export async function POST(request) {
try {
const data = await request.json();
const result = processNestedData(data);
return Response.json({ success: true, result });
} catch (err) {
// このキャッチブロックは実行されない
return Response.json({ error: "Processing failed" }, { status: 500 });
}
}
function processNestedData(data) {
if (Array.isArray(data)) {
return data.map((item) => processNestedData(item));
}
if (typeof data === "object" && data !== null) {
return Object.fromEntries(
Object.entries(data).map(([k, v]) => [k, processNestedData(v)]),
);
}
return data;
}
攻撃ペイロード
攻撃者は、以下のような深くネストした JSON を送信します。
javascript// 攻撃ペイロード生成(50,000レベルのネスト)
function generateDeepPayload(depth) {
let payload = { value: "end" };
for (let i = 0; i < depth; i++) {
payload = { nested: payload };
}
return payload;
}
// 攻撃リクエスト
fetch("/api/process", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(generateDeepPayload(50000)),
});
このリクエストを受信すると、サーバーは終了コード7で即座にクラッシュし、try-catch は実行されません。
影響を受けるフレームワーク・ツール一覧
| カテゴリ | 製品・ライブラリ | async_hooks 使用 |
|---|---|---|
| フレームワーク | Next.js(App Router / Pages Router) | AsyncLocalStorage 経由 |
| フレームワーク | Remix | AsyncLocalStorage 経由 |
| フレームワーク | SvelteKit | AsyncLocalStorage 経由 |
| APM | Datadog(dd-trace) | createHook 直接使用 |
| APM | New Relic | createHook 直接使用 |
| APM | Dynatrace | createHook 直接使用 |
| APM | Elastic APM | createHook 直接使用 |
| Observability | OpenTelemetry | createHook 直接使用 |
| ロギング | Pino(async context) | AsyncLocalStorage 経由 |
| ロギング | Winston(async context) | AsyncLocalStorage 経由 |
つまずきポイント
- 「API Route を公開していないから大丈夫」と思っても、Server Actions や Server Components でも同じ問題が発生します
- JSON.parse() 自体は深いネストを処理できますが、その後の再帰処理でクラッシュします
解決策と判断:バージョンアップと防御策
この章でわかること
- 推奨される対策の優先順位
- Node.js のアップデート方法
- アップデートできない場合の暫定対策
対策の優先順位
| 優先度 | 対策 | 効果 | 実装コスト |
|---|---|---|---|
| 1(最優先) | Node.js アップデート | 根本解決 | 低〜中 |
| 2 | 入力データのネスト深度制限 | 攻撃緩和 | 低 |
| 3 | 再帰処理の深度制限 | 攻撃緩和 | 低 |
| 4 | WAF でのペイロードサイズ制限 | 攻撃緩和 | 中 |
Node.js アップデート(根本解決)
修正済みバージョンにアップデートすることで、根本的に解決できます。
bash# nvm を使用している場合
nvm install 22.22.0
nvm use 22.22.0
# volta を使用している場合
volta install node@22.22.0
# package.json で指定する場合
{
"engines": {
"node": ">=22.22.0"
}
}
Next.js プロジェクトでの確認方法
bash# 現在の Node.js バージョンを確認
node -v
# 脆弱性が修正されているか確認(22.22.0 以降なら OK)
# 20.20.0, 22.22.0, 24.13.0, 25.3.0 以降が修正済み
暫定対策1:入力データのネスト深度制限
Node.js をすぐにアップデートできない場合の暫定対策です。
javascript// lib/validation.js
export function validateJsonDepth(obj, maxDepth = 50, currentDepth = 0) {
if (currentDepth > maxDepth) {
throw new Error(`JSON nesting depth exceeds limit: ${maxDepth}`);
}
if (obj === null || typeof obj !== "object") {
return true;
}
if (Array.isArray(obj)) {
for (const item of obj) {
validateJsonDepth(item, maxDepth, currentDepth + 1);
}
} else {
for (const value of Object.values(obj)) {
validateJsonDepth(value, maxDepth, currentDepth + 1);
}
}
return true;
}
javascript// app/api/process/route.js(修正後)
import { validateJsonDepth } from "@/lib/validation";
export async function POST(request) {
try {
const data = await request.json();
// ネスト深度を検証(再帰処理の前に実行)
validateJsonDepth(data, 50);
const result = processNestedData(data);
return Response.json({ success: true, result });
} catch (err) {
return Response.json({ error: err.message }, { status: 400 });
}
}
暫定対策2:再帰処理の深度制限
再帰関数自体に深度制限を追加する方法です。
javascript// lib/process.js
export function processNestedData(data, depth = 0, maxDepth = 100) {
if (depth > maxDepth) {
throw new Error("Maximum recursion depth exceeded");
}
if (Array.isArray(data)) {
return data.map((item) => processNestedData(item, depth + 1, maxDepth));
}
if (typeof data === "object" && data !== null) {
return Object.fromEntries(
Object.entries(data).map(([k, v]) => [
k,
processNestedData(v, depth + 1, maxDepth),
]),
);
}
return data;
}
暫定対策3:Next.js Middleware でのグローバル検証
すべての API Route に適用したい場合は、Middleware を使用します。
javascript// middleware.js
import { NextResponse } from "next/server";
const MAX_CONTENT_LENGTH = 1024 * 1024; // 1MB
const MAX_NESTING_DEPTH = 50;
function checkDepth(obj, depth = 0) {
if (depth > MAX_NESTING_DEPTH) return false;
if (obj === null || typeof obj !== "object") return true;
const values = Array.isArray(obj) ? obj : Object.values(obj);
return values.every((v) => checkDepth(v, depth + 1));
}
export async function middleware(request) {
// API Route のみ検証
if (!request.nextUrl.pathname.startsWith("/api/")) {
return NextResponse.next();
}
// Content-Length チェック
const contentLength = request.headers.get("content-length");
if (contentLength && parseInt(contentLength) > MAX_CONTENT_LENGTH) {
return NextResponse.json({ error: "Payload too large" }, { status: 413 });
}
// JSON ボディのネスト深度チェック
if (request.method === "POST" || request.method === "PUT") {
try {
const clonedRequest = request.clone();
const body = await clonedRequest.json();
if (!checkDepth(body)) {
return NextResponse.json(
{ error: "JSON nesting too deep" },
{ status: 400 },
);
}
} catch {
// JSON パースエラーは後続の処理に任せる
}
}
return NextResponse.next();
}
export const config = {
matcher: "/api/:path*",
};
つまずきポイント
- 暫定対策の
validateJsonDepth関数自体も再帰処理なので、50階層程度に制限しないと自身がクラッシュの原因になります - Middleware でのチェックは
request.clone()を使わないと、後続の処理でボディを読み取れなくなります
具体例:実務での対応パターン
この章でわかること
- 実際のプロジェクトでの対応手順
- CI/CD での自動チェック方法
- 検証・テスト方法
対応手順のフローチャート
mermaidflowchart TD
start["脆弱性の認知"] --> check1{"Node.js バージョン<br/>確認"}
check1 -->|"修正済み"| done["対応完了"]
check1 -->|"未修正"| check2{"即時アップデート<br/>可能?"}
check2 -->|"可能"| update["Node.js<br/>アップデート"]
update --> test["動作確認"]
test --> done
check2 -->|"不可"| mitigation["暫定対策<br/>実装"]
mitigation --> schedule["アップデート<br/>計画策定"]
schedule --> update
図の補足:Node.js のアップデートが根本解決ですが、即時対応が難しい場合は暫定対策を実装した上で計画的にアップデートします。
実務での対応例:EC サイトの場合
実際に業務で対応した EC サイト(Next.js 15 + App Router)での事例です。
影響調査
bash# 1. Node.js バージョン確認
node -v
# v22.20.0(脆弱性あり)
# 2. 再帰処理を含むコードの検索
grep -r "function.*(" --include="*.js" --include="*.ts" | \
xargs grep -l "return.*\1"
# 3. API Route の一覧取得
find app -name "route.js" -o -name "route.ts"
発見された脆弱なエンドポイント
/api/products/import- 商品データの一括インポート/api/categories/tree- カテゴリツリーの取得/api/orders/export- 注文データのエクスポート
対応内容
- 緊急対応(当日): Middleware でネスト深度制限を追加
- 短期対応(1週間以内): Node.js 22.22.0 へアップデート
- 中期対応(1ヶ月以内): 再帰処理を含む全関数に深度制限を追加
CI/CD での自動チェック
GitHub Actions で Node.js バージョンをチェックする例です。
yaml# .github/workflows/security-check.yml
name: Security Check
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
check-node-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check Node.js version for CVE-2025-59466
run: |
# package.json の engines.node を取得
REQUIRED_VERSION=$(node -pe "require('./package.json').engines?.node || ''")
# 最小安全バージョン
SAFE_VERSIONS=("20.20.0" "22.22.0" "24.13.0" "25.3.0")
echo "Required Node.js version: $REQUIRED_VERSION"
echo "Safe versions: ${SAFE_VERSIONS[*]}"
# バージョンチェックロジック(簡易版)
if [[ -z "$REQUIRED_VERSION" ]]; then
echo "::warning::No Node.js version specified in package.json"
fi
脆弱性の検証方法
ローカル環境で脆弱性を確認する方法です。本番環境では絶対に実行しないでください。
javascript// scripts/test-vulnerability.js(検証用)
import { createHook } from "node:async_hooks";
// async_hooks を有効化(Next.js と同じ状態を再現)
createHook({ init() {} }).enable();
function recursive(depth = 0) {
new Promise(() => {}); // 非同期コンテキスト作成
if (depth > 100000) return depth;
return recursive(depth + 1);
}
try {
console.log("Testing vulnerability...");
recursive();
console.log("No crash - vulnerability may be fixed");
} catch (err) {
console.log("Caught error:", err.message);
console.log("This means the vulnerability is fixed");
}
// 脆弱な場合:終了コード 7 でクラッシュ
// 修正済みの場合:RangeError をキャッチ
bash# 検証実行
node scripts/test-vulnerability.js
echo "Exit code: $?"
# 脆弱な場合: Exit code: 7
# 修正済みの場合: Exit code: 0
つまずきポイント
- 検証スクリプトを本番環境で実行すると、サーバーがクラッシュします
new Promise(() => {})の行が重要で、これがないと async_hooks が発火せず脆弱性を再現できません
async_hooks 利用パターン比較(実務判断用・詳細)
この章でわかること
- 各利用パターンでの影響度
- 代替手段の検討
- 長期的なアーキテクチャ判断
詳細比較表
| 利用パターン | async_hooks 依存 | 脆弱性影響 | 代替手段 | 移行コスト |
|---|---|---|---|---|
| Next.js App Router | 必須(内部使用) | あり | なし(アップデートのみ) | - |
| Next.js Pages Router | 必須(内部使用) | あり | なし(アップデートのみ) | - |
| AsyncLocalStorage 直接使用 | 必須 | あり | スレッドローカル変数(制限あり) | 高 |
| Datadog APM | 必須 | あり | 無効化可能だがトレース喪失 | 中 |
| OpenTelemetry | 必須 | あり | 無効化可能だがトレース喪失 | 中 |
| カスタム createHook | 必須 | あり | 削除または無効化 | 低〜中 |
Next.js における判断フロー
mermaidflowchart TD
start["Next.js プロジェクト"] --> q1{"Node.js<br/>22.22.0 以上?"}
q1 -->|"はい"| safe["対応完了"]
q1 -->|"いいえ"| q2{"即時アップデート<br/>可能?"}
q2 -->|"はい"| update["アップデート実施"]
update --> safe
q2 -->|"いいえ"| q3{"API Route<br/>あり?"}
q3 -->|"はい"| mitigation["Middleware で<br/>ネスト制限追加"]
q3 -->|"いいえ"| q4{"Server Actions<br/>で外部入力処理?"}
q4 -->|"はい"| mitigation
q4 -->|"いいえ"| lowrisk["リスク低<br/>(計画的対応)"]
mitigation --> schedule["アップデート<br/>計画策定"]
lowrisk --> schedule
schedule --> update
図の補足:Next.js では async_hooks を無効化できないため、Node.js のアップデートが唯一の根本解決策です。
APM ツール利用時の判断
APM ツールを一時的に無効化することで、脆弱性の影響を回避できます。ただし、トレーシング機能が失われるトレードオフがあります。
javascript// Datadog APM を条件付きで無効化
// dd-trace を import しない = async_hooks が有効化されない
// 環境変数で制御
if (process.env.ENABLE_APM === "true") {
require("dd-trace").init();
}
bash# 本番環境では有効、脆弱性対応中は無効
ENABLE_APM=false node server.js
トレードオフの判断
| 選択肢 | メリット | デメリット |
|---|---|---|
| APM 有効のまま | 監視継続 | DoS 攻撃リスク |
| APM 一時無効 | DoS 回避 | 監視喪失 |
| Node.js アップデート | 根本解決 | テスト工数 |
業務では、「APM を一時無効化 → Node.js アップデート → APM 再有効化」の順で対応しました。
つまずきポイント
- Next.js では async_hooks を無効化する手段がないため、暫定対策は入力バリデーションに限られます
- APM を無効化すると、その間のエラーやパフォーマンス問題を検知できなくなります
まとめ
CVE-2025-59466 は、Next.js や React Server Components を使用するほぼすべての環境に影響する深刻な脆弱性です。
-
async_hooks が有効な場合、try-catch が効かない:スタックオーバーフローが発生すると、エラーハンドリングをバイパスしてプロセスが即座にクラッシュします。
-
Next.js では回避不可:App Router も Pages Router も内部で AsyncLocalStorage を使用しており、async_hooks を無効化する手段がありません。
-
Node.js のアップデートが唯一の根本解決:20.20.0、22.22.0、24.13.0、25.3.0 以降で修正されています。
-
暫定対策は入力バリデーション:アップデートまでの間、Middleware や各エンドポイントでネスト深度を制限することで攻撃を緩和できます。ただし、これは根本解決ではありません。
-
APM ツール利用環境も影響:Datadog、New Relic、OpenTelemetry などを導入している環境は、Next.js を使用していなくても同様の影響を受けます。
本脆弱性は「自分には関係ない」と思い込みやすいですが、モダンな Node.js 環境のほとんどが影響を受けます。まずは自身の環境の Node.js バージョンを確認し、必要に応じて早急なアップデートを検討してください。
関連リンク
著書
articleNext.js・React Server Componentsが危険?async_hooksの脆弱性CVE-2025-59466を徹底解説
article2026年1月12日Next.jsとTypeScriptでSSGとSSRの型定義を使い方で整理 データ境界のベストプラクティス
article2025年12月21日Next.jsとTypeScriptでLintと整形をセットアップする手順 ESLint Stylelint PrettierとVSCode自動フォーマット
article2025年12月21日Next.jsをインストールしてTypeScript環境をセットアップする手順 最初に動かすまでを整理
article2025年12月21日Next.js 6から7へTypeScriptで移行を運用する手順 実施対応を再現できる形で整理
article2025年12月21日Next.js 5から6へTypeScriptで移行を運用する手順 実施対応を再現できる形で整理
articleNext.js・React Server Componentsが危険?async_hooksの脆弱性CVE-2025-59466を徹底解説
article2025年12月31日TypeScriptでUIコンポーネントを設計する 型から考えるPropsと責務分割
article2025年12月21日ReactとTypeScriptでHooksとコンポーネントを型安全に書く使い方 実務の定石を整理
article【緊急警告】React/Next.js の RSC 機能に CVSS 10.0 の RCE 脆弱性「CVE-2025-55182」が発覚
articleReact の最新動向まとめ:Server Components・並列レンダリング・エコシステム俯瞰
articleReact で管理画面を最短構築:テーブル・フィルタ・権限制御の実例
articleNext.js・React Server Componentsが危険?async_hooksの脆弱性CVE-2025-59466を徹底解説
article【緊急】2026年1月13日発表 Node.js 脆弱性8件の詳細と対策|HTTP/2・async_hooks のDoS問題を解説
article2026年1月13日TypeScriptで既存コードを型安全化する使い方 段階的リファクタリング手順とチェックポイント
article2026年1月13日PlaywrightとTypeScriptでテスト自動化を運用する 型安全な設計と保守の要点
article2026年1月13日TypeScriptでHigher Kinded Typesを模倣する設計 ジェネリクスで関数型パターンを整理
article2026年1月13日Viteで画像とアセット管理をシンプルにする使い方 import運用と構成の考え方
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
