[緊急解説] Node.jsアプリに潜むDoSの危機!tomlパッケージの制御不能な再帰脆弱性 (CVE-2026-77465)
はじめに:なぜフロントエンドエンジニアにも関係するのか?
皆さんの中には、Node.jsを使ってバックエンドAPIやバッチ処理、ビルドツールなどを開発している方も多いでしょう。特にTOML形式の設定ファイルやデータを扱う機会がある場合、今回の脆弱性は深刻な影響を及ぼす可能性があります。この記事では、月間数千万ダウンロードを誇る人気のTOMLパーサーパッケージ`toml`に発見された、サービス拒否(DoS)につながる「制御不能な再帰」脆弱性 (CVE-2026-77465 / GHSA-82x6-q7mm-w9cf) について、技術的詳細とフロントエンドエンジニアが取るべき対策を解説します。
脆弱性の概要と深刻度 (High)
`toml`パッケージの`toml.parse()`関数は、TOML形式の文字列をJavaScriptのオブジェクトにパースするための主要なAPIです。この関数に、深くネストされた配列やインラインテーブルを含むTOML文字列を渡すと、Node.jsのコールスタックを使い果たし、「RangeError: Maximum call stack size exceeded」エラーが発生してプロセスがクラッシュします。
特筆すべきは、この脆弱性をトリガーするために必要なペイロードが非常に小さいこと。わずか5〜6KB程度のTOMLデータ(例えば、`a=[[[[[...]]]]]`のように数千レベル深くネストされた配列)で、デフォルトのNode.js環境を確実にクラッシュさせることができます。これは、外部からの認証なしに、アプリケーションをサービス停止に追い込むリモートDoS攻撃に直結する非常に危険な脆弱性です。
技術的詳細:なぜスタックが溢れるのか?
この脆弱性の根本原因は、パーサーがPEG(Parsing Expression Grammar)パーサージェネレーターであるPeggy 5.1.0によって生成された「再帰下降パーサー」である点にあります。具体的には、`value`、`array`、`inline_table`というルールを処理する関数が相互に再帰呼び出しを行うのですが、この再帰に深さの制限が設けられていません。
例えば、`a=[[[[[...]]]]]`のような深くネストされた配列をパースする場合、`toml.parse()`が呼び出されると、パーサー内部では以下のような再帰サイクルが発生します。
```text toml.parse(src) → peg$parsevalue() → peg$parsearray() → peg$parsevalue() ← ここで再び再帰 → ... ```
このサイクルが入力のネスト深度と同じ回数繰り返されるため、Node.jsのV8エンジンのコールスタックの上限に達し、`RangeError`が発生してプロセスが強制終了してしまうのです。パーサーが機械生成されているため、開発者が手書きで再帰に深度カウンターを追加するといった修正はできません。
具体的な攻撃シナリオとフロントエンドエンジニアへの影響
皆さんが開発しているNode.jsアプリケーションが、ユーザーからTOML形式の設定やデータを受け取り、`toml.parse()`で処理している場合、攻撃者は簡単にサービス停止を引き起こせます。
例えば、Expressなどを使ったNode.jsのWebサービスで、ユーザーがTOML形式のコンフィグをPOSTするエンドポイントがあるとします。たとえリクエストボディのサイズを100KBに制限していても、わずか6KB程度の不正なTOMLペイロードを送信するだけで、`toml.parse()`はスタックオーバーフローを起こし、アプリケーションプロセスはクラッシュします。結果として、そのワーカープロセスはダウンし、サービス拒否の状態に陥ります。
さらに厄介なのは、`RangeError`が一般的な構文エラー (`SyntaxError`) とは異なる点です。多くのアプリケーションでは、構文エラーをキャッチして適切にエラーレスポンスを返すように実装されていますが、`RangeError`は予期せぬ例外として扱われ、通常のエラーハンドリングをすり抜けてしまう可能性があります。これにより、Node.jsのプロセス全体が停止する事態につながりかねません。
今すぐできる対策と修正方法
この脆弱性に対しては、以下の対策が考えられます。
`toml.parse()`を呼び出す前に、ユーザーから提供されるTOML文字列のネスト深度をチェックし、深すぎる入力を拒否する方法です。バイト長の制限だけでは不十分なため、以下の両方を組み合わせる必要があります。
簡易的な深度チェックのロジック例:
```javascript function checkTomlNestingDepth(input) { let depth = 0; let maxDepth = 0; const MAX_ALLOWED_DEPTH = 500; // 安全な深度を設定 for (const char of input) { if (char === '[' || char === '{') { depth++; maxDepth = Math.max(maxDepth, depth); } else if (char === ']' || char === '}') { depth--; } } if (maxDepth > MAX_ALLOWED_DEPTH) { throw new Error(`TOML nesting depth exceeds limit (${MAX_ALLOWED_DEPTH})`); } } // Expressのエンドポイントでの使用例 app.post('/config', (req, res) => { try { checkTomlNestingDepth(req.body); const config = toml.parse(req.body); res.json({ status: 'ok', config }); } catch (e) { if (e.message.includes('TOML nesting depth')) { return res.status(400).json({ error: e.message }); } // 他のRangeErrorなどもここでキャッチし、ログを記録するなど適切に処理 console.error('Unhandled error:', e); res.status(500).json({ error: 'Internal Server Error' }); } }); ```
この脆弱性は、`toml`パッケージのメンテナーによって認識されており、今後のバージョンで修正される見込みです。修正版がリリースされ次第、速やかに`npm update toml`または`yarn upgrade toml`を実行し、パッケージを最新バージョンに更新してください。通常、パッケージの更新は最も推奨される解決策です。
メンテナーは、Peggyの文法ファイル(`src/toml.pegjs`)に深度ガードを追加してパーサーを再生成するか、`index.js`のエントリーポイントでパース前にネスト深度をチェックするガードを追加する、という2つのオプションを検討しています。
まとめ
今回の`toml`パッケージの脆弱性は、わずかな悪意ある入力によってNode.jsアプリケーションがリモートからサービス停止に追い込まれる、非常に深刻な問題です。フロントエンドエンジニアの皆さんも、自身が関わるNode.js環境(バックエンドAPI、ビルドツールなど)で`toml`パッケージを使用している場合は、この脆弱性の影響を受けないか確認し、速やかに上記の一時的な緩和策を適用することを強く推奨します。そして、修正版がリリースされ次第、迅速にアップデートを行い、アプリケーションのセキュリティを確保しましょう。
日頃から利用しているライブラリや依存関係のセキュリティ情報に注意を払い、定期的なアップデートとセキュリティチェックを行うことが、安全なソフトウェア開発には不可欠です。