Nuxtプロジェクトのセキュリティレビューと依存関係の脆弱性を全件修正した
pnpm audit を叩いたら、CriticalとHighが並んで画面が赤く染まった。放置していた依存関係の脆弱性を一掃する日が来た。
やったこと
3視点の並列レビュー
claude-code-syncで3つの視点から同時にレビューを走らせた。
- セキュリティ: 秘密情報の漏洩、インジェクション、依存関係の脆弱性
- パフォーマンス: ビルド時間、バンドルサイズ、ランタイム効率
- SRE: 可用性、監視、障害復旧
セキュリティレビューでCriticalとHighが複数見つかり、そこから修正作業に入った。
セキュリティ修正の詳細
.env漏洩 → 誤検知
レビューが .env ファイルの存在を指摘してきたが、.gitignore に含まれており、リポジトリには入っていない。誤検知として対処不要と判断。
content-images.ts にパストラバーサル防御を追加
画像配信ミドルウェアに ../ を使ったディレクトリ外アクセスの防御がなかった。normalize + resolve でパスを正規化した上で、contentDir の配下であることを検証するガードを追加した。
const contentPath = normalize(resolve(contentDir, dirPath, filename))
if (!contentPath.startsWith(contentDir)) return
この2行で content/ ディレクトリ外への読み出しを遮断する。
依存関係の脆弱性修正
直接依存とトランジティブ依存で対処方法が分かれた。
直接依存(pnpm update で解決):
| パッケージ | 脆弱性 | 対処 |
|---|---|---|
| devalue | プロトタイプ汚染 | 最新版に直接更新 |
| h3 | ReDoS | 最新版に直接更新 |
トランジティブ依存(pnpm overrides で対処):
直接依存していないパッケージは pnpm update では更新できない。package.json の pnpm.overrides フィールドで強制的にバージョンを指定した。
{
"pnpm": {
"overrides": {
"fast-xml-parser": ">=4.5.1",
"rollup": ">=4.30.1",
"@isaacs/brace-expansion": ">=3.0.1",
"tar": ">=6.2.1"
}
}
}
overridesはロックファイル全体に作用するので、どのパッケージが間接的に依存していても一律で安全なバージョンに引き上げられる。
修正結果
| 深刻度 | 修正前 | 修正後 |
|---|---|---|
| Critical | あり | 0件 |
| High | あり | 0件 |
| Moderate | - | 3件(残存) |
| Low | - | 4件(残存) |
残った7件はdevDependency経由か、パッチが未リリースのもの。本番には影響しないため許容とした。
テスト結果
修正後、全テストを実行して既存機能への影響がないことを確認した。
- 34ファイル
- 14,817テスト 全パス
overridesで依存バージョンを強制的に変えているので、テストが通ることの確認は省略できない。実際に全件通ったので、互換性の問題はなかった。
学んだこと
- pnpm overrides はトランジティブ依存の脆弱性修正に使える。 npm の
overridesや yarn のresolutionsに相当する。ロックファイル再生成で即座に反映される - パストラバーサルの防御は
resolve→startsWithの2ステップ。normalizeだけでは不十分で、resolveで絶対パスに変換してから基準ディレクトリとの前方一致を取る - セキュリティレビューの誤検知は記録に残す。 「.envは.gitignoreに含まれている」と書き残すだけで、次回の同じ指摘をスキップできる
振り返り
3視点の並列レビューは、1人で順番に見ていくより抜け漏れが減る。特にセキュリティは「見落としたら終わり」の領域なので、別の目が入る価値がある。
CriticalとHighが全て消えて pnpm audit の出力が静かになったとき、ようやく肩の力が抜けた。残りのModerateとLowはパッチが出たら片付ける。