Subresource Integrity (SRI)

ビルド成果物の HTML に SHA-384 ベースの integrity 属性を自動注入する仕組み。

なぜ必要か

  • CDN や中間プロキシで JS/CSS が改ざんされた際、ブラウザがハッシュ不一致を検出してロードを拒否する。
  • npm dependency の supply-chain 攻撃で bundle が差し替えられても、デプロイ時のハッシュと一致しない限り実行されない。
  • BFF と分離した SPA を Cloudflare Pages から配信しているため、Pages 側 attack surface を 1 段下げる。

実装

共通ロジック

web/scripts/sri.mjs — ゼロ依存 Node CLI。

  • 引数に dist ディレクトリを取り、*.html を再帰走査
  • <script src="..."> / <link rel="stylesheet|modulepreload|preload" href="..."> のうち ローカル相対 / 同オリジン絶対パスだけ対象
  • 既に integrity= が付いている tag はスキップ(冪等)
  • crossorigin="anonymous" も併せて付与

Vite Portal(admin / owner / marketing)

package.json の build 末尾に連結:

"build": "tsc -b && vite build && node ../../scripts/sri.mjs ./dist"

Astro Home

web/home/src/integrations/sri.mjs という薄い integration を作り、 astro:build:done フックで web/scripts/sri.mjs を呼ぶ。

検証

ビルド後、出力 HTML の script/link に integrity="sha384-..." が付いていれば成功。

grep -o 'integrity="sha384-[^"]*"' web/portal/admin/dist/index.html | head

ブラウザの DevTools → Network → Response Headers で対象アセットが 200 で返り、 Console に Failed to find a valid digest in the 'integrity' attribute が出ていないことを確認。

制約

  • 外部ドメイン(https://...)は対象外。crossorigin SRI は CORS で失敗するケースが多いため。
  • data: / blob: URL も対象外。
  • HTML を再生成すると SRI 再注入が必要。Astro/Vite は build hash を変える前提なので問題なし。

トラブルシュート

SRI mismatch エラー

  • ブラウザに古い HTML がキャッシュされている場合、新しい bundle と integrity が合わない。
  • 対策: Cloudflare Pages の deploy で HTML の Cache-Control を no-cache にする(既存設定)。

inline <script> には付かない

  • src 属性が無い <script>{...}</script> は対象外(CSP の unsafe-inlinenonce で対応する)。
↗ Source markdown (subresource-integrity.md)