Astro 7 アップグレードレポート(npm audit 脆弱性対応)

npm audit の脆弱性対応をきっかけに Astro 5→7 へメジャーアップグレードした際の調査・判断・詰まりどころの記録。

公開
Astro 7 アップグレードレポート(npm audit 脆弱性対応)

注: 本記事は当ブログ自体のメンテナンス作業(AI と協働)の備忘録です。

はじめに

当ブログ(Astro + Cloudflare 構成の静的サイト)で npm audit が 8 件の脆弱性(high 5 / moderate 1 / low 2)を報告するようになりました。これを解消する過程で、単なる依存更新にとどまらず Astro 5 → 7 のメジャーアップグレード、最終的には Cloudflare アダプタの撤去まで行うことになりました。判断の経緯と詰まりどころを残しておきます。

1. まず「対応すべきか」を見極める

脆弱性は件数ではなく 実際の攻撃面で評価します。対象を切り分けると、報告された 8 件はきれいに 2 グループに分かれました。

グループA: 開発・ビルド・デプロイ用ツール

undici / ws / esbuild / wrangler / miniflare の脆弱性です。これらは wrangler(CLI)やローカル開発サーバ・エミュレータの依存であり、astro build の成果物(静的 HTML/CSS/JS)には一切含まれません。攻撃面はローカル開発環境に限られます。esbuild の dev サーバのファイル読取に至っては Windows 限定で、開発機は macOS なので実質無関係です。

グループB: astro / cloudflare アダプタ本体

astro の high 群(define:vars XSS、server island replay、slot 名 XSS、spread props XSS、prerender エラーページの Host SSRF)と、@astrojs/cloudflare の SSRF です。いずれも SSR・動的機能を使っていることが前提ですが、当サイトは:

  • output: 'server' 指定なし・prerender = false のページなし・API エンドポイントなし
  • define:vars / server islands / ユーザー入力由来の spread props を未使用
  • コンテンツコレクションは既に新 Content Layer API(content.config.ts + loader)へ移行済み

という完全静的サイトです。つまりグループ B も実サイトへの影響はほぼゼロでした。

結論: 緊急性は低い。とはいえ衛生的に潰しておく価値はあるので、メジャー更新を含めて対応することにしました。

2. メジャーアップグレードで踏んだ破壊的変更

npm install astro@latest @astrojs/cloudflare@latest @astrojs/mdx@latest ... で一気に上げ、ビルドを通しながら修正していきました。実際に手を入れたのは以下です。

ViewTransitionsClientRouter(v6 で改名)

- import { ViewTransitions } from "astro:transitions";
+ import { ClientRouter } from "astro:transitions";
...
- <ViewTransitions />
+ <ClientRouter />

platformProxy オプションの削除(アダプタ v14)

アダプタ v14 は開発サーバが workerd 上で動くようになり、platformProxy オプションが廃止されました。astro.config.mjs から除去しました。

Node / 各種メジャー

v6 で Node 22 必須・Vite / Zod / Shiki もメジャー更新となりました。開発機は Node 24 だったため問題ありませんでした。Content Layer API へ移行済みだったのも幸いし、コンテンツ周りの修正は不要でした。

3. 最大の落とし穴: アダプタ v14 の静的ビルド破壊

ビルドは exit 0(成功)tscwrangler deploy --dry-run も通ります。しかし生成された HTML を開くと——

$ head -c 20 dist/client/blog/index.html
[object Object]

リダイレクト以外の全ページが 15 バイトの [object Object] になっていました。 dev サーバでは正常に描画されるので、ビルドの HTML 直列化段階のバグです。切り分けると:

  • compressHTML / imageService をどう変えても再現
  • アダプタを外すと正常<!DOCTYPE html>...

つまり @astrojs/cloudflare v14.1.3 が Astro 7 の完全静的サイトで出力を壊す回帰でした。upstream にも同種の報告(Issue #17047「build exits 0 and emits truncated HTML」等)があり、アダプタの静的出力対応は繰り返し問題になっている領域だとわかりました。

4. 解決: そもそもアダプタは不要だった

公式ドキュメントの指針は明快で、静的サイトジェネレーターとして使うなら Cloudflare アダプタは不要です。Astro がビルド時に全ページを事前レンダリングするので、dist/ を Cloudflare の静的アセットとして配信すればよいのです。

やったことは以下のとおりです。

  1. @astrojs/cloudflare を依存から削除
  2. astro.config.mjs から adapter を除去
  3. wrangler.json を静的アセット配信専用に(main を置かず assets.directory だけ)
{
  "name": "astro-static-blog",
  "compatibility_date": "2025-10-08",
  "assets": { "directory": "./dist" },
  "observability": { "enabled": true }
}

これは「バグ回避策」ではなく、Cloudflare にデプロイする静的 Astro サイトの公式推奨構成そのものです。アダプタは元々 SSR 用で、このブログには不要でした。

5. 結果

  • npm audit: 脆弱性 0 件(アダプタ由来の 1 件も依存ごと消滅)
  • ビルド出力: 全 20 ページ正常、View Transitions も動作
  • tsc / wrangler deploy --dry-run: いずれもパス
  • 構成: SSR Worker → 静的アセット配信になり、むしろ簡潔に

まとめ

  • 脆弱性は「件数」ではなく「自分の構成での攻撃面」で評価します。今回は実害が低いと見極めた上で、衛生対応としてメジャー更新しました。
  • ビルドが exit 0 でも成果物が正しいとは限りません。生成物を実際に開いて検証する工程を省かないことが大切です。
  • 詰まったら upstream の issue と公式の推奨構成を確認します。今回は「アダプタを使わない」という、より正しい構成に落ち着きました。