# 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](../../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 末尾に連結:
```json
"build": "tsc -b && vite build && node ../../scripts/sri.mjs ./dist"
```

### Astro Home
[web/home/src/integrations/sri.mjs](../../web/home/src/integrations/sri.mjs) という薄い integration を作り、
`astro:build:done` フックで `web/scripts/sri.mjs` を呼ぶ。

## 検証

ビルド後、出力 HTML の script/link に `integrity="sha384-..."` が付いていれば成功。
```bash
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-inline` か `nonce` で対応する）。
