Subresource Integrity(SRI)は、ブラウザがCDNやサードパーティオリジンから取得したリソースの改ざんを検証するセキュリティ機能です。British AirwaysのMagecart侵害、Polyfill.ioのサプライチェーン侵害など、サプライチェーン攻撃が増加する現代において、SRIは読み込んだリソースが開発者の意図したものであることを暗号学的に保証します。
SRIの仕組み
<script>や<link rel="stylesheet">タグにintegrity属性を追加すると、ブラウザは取得したリソースのハッシュを計算し、属性値と比較します。一致しない場合、ブラウザはリソースの実行や適用を拒否します。
<script
src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"
></script>
複数のハッシュをカンマ区切りで指定すると、複数バージョンが許容される場合のグレースフルフォールバックが可能です:
<script
src="https://cdn.example.com/app.js"
integrity="sha384-ABC... sha512-XYZ..."
crossorigin="anonymous"
></script>
クロスオリジンリソースにSRIを適用するには、crossorigin="anonymous"属性が必須です。適切なCORSヘッダーがない場合、ブラウザはリソースをopaqueモードで読み込み、整合性を検証できません。この場合、リソースはエラーなしで静かに無視されます。
SRIハッシュの生成
SRIハッシュはOpenSSL、オンラインツール、ビルド時プラグインで生成できます:
openssl dgst -sha384 -binary file.js | openssl base64 -A
自動化のためには、ビルドパイプラインにSRI生成を統合します。各ツールの対応状況は以下の通りです:
| ツール | プラグイン / 設定 |
|---|---|
| webpack | webpack-subresource-integrity |
| Vite | vite-plugin-sri または experimental.renderBuiltUrl |
| Next.js | next.config.js の experimental.sri |
| Astro | アセットコンポーネントのビルトイン integrity プロップ |
// Next.js SRI設定
// next.config.js
module.exports = {
experimental: {
sri: {
hash: "sha384",
},
},
};
ハッシュはミニファイ後かつソースマップ生成後に計算します。ハッシュは最終的に配信されるコンテンツと一致する必要があり、開発用ソースとは異なります。
CSPとの連携と違反監視
Content Security PolicyとSRIは補完関係にあります。CSPはリソースの読み込み元を制限し、SRIは許可されたオリジンから読み込んだリソースの改ざんを検証します。
<!-- CSPヘッダー -->
Content-Security-Policy:
script-src https://cdn.example.com 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC';
report-uri https://reporting.example.com/csp-violations
<!-- SRI付きHTML -->
<script
src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"
></script>
CSPのreport-uriやreport-toディレクティブでSRI違反を監視します。ハッシュの不一致はCDN侵害、ビルドパイプラインの問題、ミドルウェアによるコンテンツ変換を示す可能性があります。
// クライアント側SRI違反監視
document.querySelectorAll("script[integrity]").forEach((el) => {
el.addEventListener("error", (event) => {
fetch("/api/sri-violation", {
method: "POST",
body: JSON.stringify({
src: el.src,
integrity: el.getAttribute("integrity"),
timestamp: new Date().toISOString(),
}),
});
});
});
高度なSRIパターン
動的に読み込まれるスクリプトのランタイム検証、Web WorkerのSRI、Import Mapでの整合性チェックなど、高度なパターンも重要です:
// 動的SRI検証
async function loadWithIntegrity(url, integrity) {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
const hash = await crypto.subtle.digest("SHA-384", buffer);
const base64 = btoa(String.fromCharCode(...new Uint8Array(hash)));
if (base64 !== integrity.replace("sha384-", "")) {
throw new Error("Integrity check failed");
}
const blob = new Blob([buffer], { type: "text/javascript" });
const blobUrl = URL.createObjectURL(blob);
const script = document.createElement("script");
script.src = blobUrl;
document.body.appendChild(script);
}
フォールバック戦略も重要です。プライマリCDNからSRI付きで読み込み、検証に失敗した場合はセルフホストコピーにフォールバックします。これにより、CDNが侵害されたりコンテンツが変更されたりしてもアプリケーションの機能を維持できます。
SRIはシンプルに実装できる重要なセキュリティ制御です。ハッシュを生成し、integrity属性を追加し、CORSヘッダーを設定し、CIでハッシュ生成を自動化します。CSPと組み合わせることで、サードパーティリソース読み込みに対する堅牢な保護を提供します。全ての外部リソースにSRIを追加し、プロセスを自動化し、障害をプロアクティブに監視しましょう。
