外部CDNからJavaScriptやCSSを読み込むコードで、次のような記述を見かけることがあります。
<script
src="https://cdn.example.com/library.min.js"
integrity="sha384-..."
crossorigin="anonymous"
></script>
integrity 属性が何となくセキュリティに関係していることは知っていても、実際には次のような疑問が残ります。
- HTTPSで配信しているなら十分ではないのか
- npmでインストールしている場合にも必要なのか
- CSPと何が違うのか
- Next.jsやViteでビルドしているアプリにも意味があるのか
- なぜ重要そうなのに、一般的なWeb開発ではあまり見かけないのか
そこで、Subresource Integrity(以下、SRI)の仕様、実際に防げる攻撃、過去のインシデント、現代のビルド環境との関係を調べてみました。
先に結論を書くと、SRIは万能なセキュリティ機能ではありません。
しかし、外部CDNから固定バージョンのJavaScriptやCSSを読み込む場合には、今でも比較的低コストで効果の高い対策です。
一方で、Google Tag Manager、広告、分析ツール、A/Bテストツールのように中身が頻繁に変わる第三者スクリプトには適用しにくく、皮肉にもリスクが高いスクリプトほどSRIを使いづらいという問題があります。
この記事では「SRIは必ず使うべき」と決めつけず、どのような構成で導入価値が高いのかを整理します。
SRIとは何か
SRIは、ブラウザが取得したJavaScriptやCSSの内容が、HTMLに書かれたハッシュ値と一致するかを検証する仕組みです。
例えば、次のようなscriptタグがあるとします。
<script
src="https://cdn.example.com/library.min.js"
integrity="sha384-BASE64_HASH"
crossorigin="anonymous"
></script>
ブラウザは大まかに次の順序で処理します。
srcに指定されたファイルを取得する- 取得した内容からハッシュ値を計算する
integrity属性のハッシュ値と比較する- 一致した場合だけJavaScriptを実行する
- 一致しない場合は読み込みを失敗させる
ハッシュが一致しなかった場合、警告だけを出して実行するわけではありません。リソースはネットワークエラーとして扱われ、JavaScriptなら実行されず、CSSなら適用されません。
つまりSRIは、ブラウザ側で行われる「このファイルは本当に事前に承認した内容なのか」という検証です。
W3CのSRI仕様でも、取得したリソースが想定外に改変されていないことをユーザーエージェントが検証する仕組みとして定義されています。
実際に使うとどうなるか
Bootstrapなどを外部CDNから読み込む場合は、次のような形になります。
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
integrity="sha384-..."
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
integrity="sha384-..."
crossorigin="anonymous"
></script>
対応している主なハッシュアルゴリズムは次の3つです。
- SHA-256
- SHA-384
- SHA-512
実務ではSHA-384がよく使われています。
ローカルファイルのハッシュを生成する場合は、例えば次のように計算できます。
openssl dgst -sha384 -binary library.min.js | openssl base64 -A
出力結果の先頭に sha384- を付けて、integrity 属性へ指定します。
integrity="sha384-生成されたBase64文字列"
crossorigin="anonymous" はなぜ必要なのか
外部オリジンのリソースにSRIを使う場合、通常は crossorigin="anonymous" も指定します。
<script
src="https://cdn.example.com/library.js"
integrity="sha384-..."
crossorigin="anonymous"
></script>
SRI付きのクロスオリジンリクエストでは、ブラウザはCORSを利用してレスポンスを取得します。
配信元のCDNも、次のようなCORSレスポンスヘッダーを返す必要があります。
Access-Control-Allow-Origin: *
これは、クロスオリジンのopaque responseに対して自由にハッシュ照合を許すと、攻撃者がハッシュの一致・不一致を利用して別オリジンのコンテンツを推測できる可能性があるためです。
したがって、CDN側がCORSに対応していない場合、正しいハッシュを指定してもSRI付きリソースを読み込めないことがあります。
HTTPSがあればSRIは不要なのか
最初に疑問に感じたのはここでした。
HTTPSは通信経路を暗号化し、通信途中での盗聴や改ざんを防ぎます。それならSRIは不要にも見えます。
ただし、HTTPSとSRIが守る対象は少し違います。
HTTPSが守るもの
HTTPSは、主に次のことを保証します。
- 通信相手が証明書に対応したホストであること
- 通信途中で内容を盗み見られにくいこと
- 通信途中で内容を書き換えられにくいこと
SRIが守るもの
SRIは、次のことを検証します。
- 取得したファイルが、開発者が事前に承認した内容と一致すること
例えば、正規のCDN自体が侵害され、正規のHTTPS接続を通して悪意あるJavaScriptを返してきた場合、HTTPS上は正常です。
通信相手も正規のCDNであり、通信経路も暗号化されています。
しかし、返ってきたファイルの中身は悪意あるものに変わっています。
このケースでSRIを指定していれば、ブラウザはハッシュの不一致を検出して実行を止められます。
整理すると、次のように考えると分かりやすいです。
HTTPSは通信経路を信頼するための仕組みで、SRIは配信された内容を検証するための仕組み。
両者は代替関係ではなく、補完関係です。
SRIが防げるもの
SRIが有効なのは、基本的に「URLは正しいが、そのURLから返された内容が変わっている」という状況です。
CDNが侵害された場合
外部CDN上のファイルが攻撃者によって書き換えられた場合、SRIのハッシュ値と一致しなくなるため、ブラウザは実行を止めます。
これはSRIの代表的なユースケースです。
外部サービスの配信元が乗っ取られた場合
第三者サービスのサーバーやアカウントが侵害され、同じURLから不正なJavaScriptが配信された場合も、HTML側のハッシュが更新されていなければ検知できます。
CDNの誤配信や設定ミス
必ずしも悪意ある攻撃だけが対象ではありません。
CDNの設定ミスやキャッシュの不整合により、別のファイルが返された場合にもSRIは読み込みを止めます。
固定バージョンURLの中身が変更された場合
例えば、次のようなURLを利用していたとします。
https://cdn.example.com/library/1.2.3/library.min.js
URL上はバージョンが固定されています。
しかし、配信元が同じURLのファイルを後から差し替えてしまう可能性はゼロではありません。
SRIはURLではなく実際のファイル内容を固定するため、このような変更も検知できます。
DNSハイジャックやキャッシュ汚染
DNS攻撃やCDNキャッシュ汚染により、別サーバーや別コンテンツへ誘導されたとしても、返されたファイルが事前のハッシュと違えばブロックされます。
ただし、HTML自体も同時に書き換えられる状況では防げません。
脅威ごとの整理
| 脅威・事故 | SRIの有効性 | 理由 |
|---|---|---|
| CDN上のJavaScript改ざん | 高い | 内容が変わればハッシュ不一致になる |
| 外部配信事業者の侵害 | 高い | 同じURLから悪意あるファイルが返っても検知できる |
| CDNの誤配信 | 高い | 想定外の内容は原因を問わずブロックされる |
| 固定バージョンURLの差し替え | 高い | URLではなく内容を固定できる |
| CDNキャッシュ汚染 | 条件付き | 配信内容だけが変わる場合は有効 |
| DNSハイジャック | 条件付き | HTMLが安全なままで、外部リソースだけが差し替えられる場合は有効 |
| 中間者攻撃 | 条件付き | HTTPSが第一防御。内容が変わった場合の追加検証になる |
| npmパッケージ侵害 | 原則無効 | ブラウザに届く前のビルド工程で侵害されている |
| HTML自体の改ざん | 無効 | src と integrity を両方書き換えられる |
| XSS | 無効 | 外部ファイルの整合性検証とは別の問題 |
| CSRF | 無効 | リクエスト偽造を防ぐ仕組みではない |
SRIでは防げないもの
SRIの責任範囲を理解するには、防げないものを見る方が分かりやすいかもしれません。
HTML自体の改ざん
SRIのハッシュ値はHTMLに書かれています。
そのため、攻撃者がHTMLを書き換えられる場合は、次の両方を同時に差し替えられます。
<script
src="https://attacker.example/malware.js"
integrity="sha384-攻撃者が計算した正しいハッシュ"
></script>
SRIはHTMLを信頼の起点にしているため、HTML自体が侵害された場合には効果がありません。
正式に公開された悪意あるバージョン
依存ライブラリの開発元が侵害され、悪意あるバージョンがnpmやCDNへ正式に公開されたとします。
そのファイルを開発者が取得し、内容を確認せず新しいハッシュへ更新してしまえば、SRIは正常に通ります。
SRIが検証するのは「承認した内容と一致するか」であって、「そのコードが安全か」ではありません。
npmパッケージの侵害
ua-parser-js や event-stream のように、npmパッケージ自体へ悪意あるコードが含まれたケースでは、SRIは原則として役に立ちません。
なぜなら、悪意あるコードはnpmから取得され、ビルド成果物の一部として自社JavaScriptへ取り込まれるからです。
ブラウザから見ると、それは正規の自社ファイルです。
この領域では、次の対策が必要です。
- lockfile
- npm audit
- DependabotやRenovate
- パッケージ署名やprovenance
- 依存関係レビュー
- CI/CD環境の保護
- ビルド成果物の検証
XSSやCSRF
SRIは外部リソースの内容を検証する機能です。
ユーザー入力からスクリプトが注入されるXSSや、不正なリクエストを送らせるCSRFを直接防ぐものではありません。
XSS対策ではCSP、出力エスケープ、Trusted Typesなどが中心になります。
npmのlockfileとSRIは何が違うのか
package-lock.json や pnpm-lock.yaml にも、依存パッケージのintegrity情報が保存されています。
npmの package-lock.json では、例えば次のような値があります。
{
"integrity": "sha512-..."
}
形式はSRIと似ていますが、検証するタイミングが違います。
lockfileのintegrity
lockfileのintegrityは、npmパッケージをインストールする時点で、取得したtarballが想定したものかを検証します。
つまり守る地点は次の部分です。
npm registry
↓
開発環境・CI
ブラウザのSRI
ブラウザのSRIは、本番環境でユーザーがJavaScriptやCSSを取得する時点で検証します。
CDN・Webサーバー
↓
ユーザーのブラウザ
両者は同じようなハッシュ形式を使いますが、異なる工程を守っています。
| 仕組み | 検証タイミング | 主な目的 |
|---|---|---|
| lockfile integrity | パッケージインストール時 | パッケージ取得の整合性・再現性 |
| SRI | ブラウザのリソース取得時 | 配信ファイルの内容検証 |
したがって、「lockfileがあるからSRIは不要」とは限りません。
ただし、npmで取得したライブラリを自社でバンドルし、自社ドメインから配信する構成では、外部CDNを直接読む場合よりSRIの追加効果は小さくなります。
content hash付きファイル名との違い
ViteやWebpackでは、ビルドされたファイル名にcontent hashが付くことがあります。
assets/index-a8f45c2d.js
一見すると、これもファイル内容の検証に見えます。
しかし主な目的はキャッシュ制御です。
ファイル内容が変わればファイル名も変わるため、ブラウザに新しいファイルを取得させやすくなります。
index-a8f45c2d.js
↓ 内容変更
index-b71d903a.js
ただし、ブラウザはファイル名のハッシュ部分と実際のファイル内容を照合しているわけではありません。
攻撃者やCDNの設定ミスによって、同じURLから別の内容が返っても、content hash付きファイル名だけでは検知できません。
整理すると次の違いです。
| 仕組み | 主な役割 |
|---|---|
| content hash付きファイル名 | キャッシュ無効化・バージョン管理 |
| SRI | ブラウザによる実ファイル内容の検証 |
CSPとSRIの違い
SRIとよく比較されるのがContent Security Policy(CSP)です。
CSP
CSPは、ページがどの配信元からスクリプトや画像などを読み込めるかを制限します。
Content-Security-Policy: script-src 'self' https://cdn.example.com
この例では、自社オリジンと cdn.example.com からのスクリプトを許可しています。
ただし、cdn.example.com 自体が侵害され、同じホストから悪意あるコードが配信された場合、ホストベースのCSPだけでは許可されてしまいます。
SRI
SRIは、許可したホストから取得したファイルの中身が期待どおりかを確認します。
そのため、次のように整理できます。
CSPは「どこから読み込めるか」を制御し、SRIは「読み込んだ内容が何か」を検証する。
両者は代替ではなく補完関係です。
高いセキュリティが必要な画面では、次のように組み合わせます。
- HTTPS
- CSP
- SRI
- 外部スクリプトの削減
- Trusted Types
- 依存関係の監視
require-sri-for は使うべきなのか
過去にはCSPに require-sri-for というディレクティブがありました。
Content-Security-Policy: require-sri-for script style
これは、scriptやstyleにSRIを必須化する意図の機能でした。
しかし、標準化やブラウザ実装上の問題から現在は廃止・非推奨扱いです。新規実装でこれに依存するのは避けるべきです。
一方で、2025年以降は新しいIntegrity-Policy ヘッダーの実装が進んでいます。
Integrity-Policy: blocked-destinations=(script)
Integrity-Policy は、対象リソースに整合性検証を要求したり、違反をReporting API経由で収集したりするための新しい仕組みです。
Chrome 138やFirefox 145以降でscript向けの対応が進んでいます。
SRI自体は2016年にW3C Recommendationとなった技術ですが、2026年時点でも関連仕様の拡張は続いています。
「古くて終わった技術」というより、コア機能は成熟し、周辺の強制・監視機能が追加されている段階と考える方が正確です。
過去のインシデントでSRIは役立ったのか
Polyfill.ioのサプライチェーン攻撃
2024年、Polyfill.ioから悪意あるJavaScriptが配信される問題が発生しました。
外部のJavaScript配信サービスが侵害・悪用され、利用サイトがコードを変更していなくても、訪問者のブラウザで不正なJavaScriptが実行され得る典型的なサプライチェーン攻撃です。
このようなケースでは、対象ファイルが固定内容であり、あらかじめSRIを設定できていれば、不正な差し替えを検知できた可能性があります。
ただしPolyfill.ioはUser-Agentや要求機能によって返す内容を変える動的サービスでした。
同じURLでも利用者ごとに内容が変わるため、固定ハッシュを指定するSRIとは相性がよくありません。
この事例は、SRIの有効性と限界の両方を示しています。
- 固定ファイルの差し替えには強い
- 動的に内容が変わるサービスには適用しにくい
Ticketmasterと第三者JavaScript
Ticketmasterの事例では、決済ページで利用されていた第三者チャットサービスのJavaScriptに不正コードが挿入され、顧客データが漏えいしました。
対象JavaScriptが外部URLから読み込まれる固定ファイルで、HTML側が改ざんされていなかったのであれば、SRIによって防げた可能性があります。
ただし、実際の読み込み方式や更新方法によってはSRIを適用できなかった可能性もあるため、「SRIがあれば確実に防げた」とまでは断定できません。
British AirwaysのMagecart被害
British Airwaysでは、決済情報を盗み取るJavaScriptがWebサイトに注入されました。
このケースではサイト側のスクリプトやHTML自体が侵害された可能性があり、攻撃者がHTMLを変更できる状況ではSRIも書き換えられます。
そのため、SRIだけで防げたと考えるのは難しいです。
ua-parser-jsやevent-stream
これらはnpmパッケージ自体に悪意あるコードが公開された事例です。
侵害されたコードはビルド時にアプリへ取り込まれるため、ブラウザSRIの守備範囲外です。
同じ「サプライチェーン攻撃」でも、SRIが効くものと効かないものがあります。
外部CDN上の実行時ファイル差し替え
→ SRIが有効
npm・CI・ビルド工程の侵害
→ SRIでは原則防げない
SRIが特に役立つユースケース
外部CDNから固定バージョンのライブラリを読む
最も分かりやすいケースです。
例えば次のようなライブラリです。
- Bootstrap
- jQuery
- Prism.js
- Highlight.js
- Font Awesome
- Reactの固定UMDビルド
URLとファイル内容が固定されているなら、更新時にハッシュを変更するだけで運用できます。
導入コストに対して得られる効果が大きいため、原則としてSRIを付ける判断が合理的です。
静的HTML、LP、ドキュメントサイト
ビルド環境がない静的サイトでも、外部CDNのタグにintegrity を加えるだけで導入できます。
特に次のようなサイトとは相性がよいです。
- GitHub Pages
- 静的なLP
- CMSで管理されたページ
- ドキュメントサイト
- 更新頻度の低い企業サイト
WordPressで外部ライブラリを利用する場合
WordPressでも、scriptやstyleを出力するフックを利用してintegrity と crossorigin を追加できます。
外部CDNの固定ファイルを使っている場合は、導入価値があります。
ただし、テーマやプラグインがURLを自動更新する場合は、ハッシュとの整合性に注意が必要です。
管理画面や決済画面
管理者権限や個人情報を扱うページでは、第三者JavaScriptそのものをできるだけ減らすべきです。
それでも外部リソースを読み込む必要がある場合は、SRIの優先度は高くなります。
特に決済画面では、第三者スクリプトが一つ侵害されるだけで入力情報を盗まれる可能性があります。
SRIが使いにくいユースケース
Google Tag Manager、広告、分析ツール
これらのスクリプトは、同じURLでも内容が頻繁に更新されます。
SRIを付けると更新のたびにハッシュが不一致になり、スクリプトが停止します。
そのため、Google Analytics、広告タグ、タグマネージャー、A/BテストツールなどではSRIを現実的に運用できないことが多いです。
これはかなり構造的な問題です。
最もリスクが高い第三者スクリプトほど、中身が頻繁に変わるためSRIを適用しにくい。
この領域ではSRI以外の対策が重要になります。
- 不要なタグを削除する
- タグの追加権限を制限する
- CSPで通信先を制限する
- iframeやsandboxで分離する
- サードパーティスクリプトの変更を監視する
- 決済画面では読み込まない
動的なウィジェットやチャットツール
チャット、カスタマーサポート、パーソナライズ、A/Bテストなどは、ユーザーや設定ごとに配信内容が変わる場合があります。
その場合、単一のハッシュ値で内容を固定できません。
Module Federation
Webpack Module Federationでは、remoteEntry.js や後続チャンクを実行時に動的ロードします。
SRIを適用すること自体は可能ですが、次の仕組みが必要です。
- remoteEntryのバージョン固定
- ハッシュ値の配布
- 動的script挿入時の
integrity設定 - 後続チャンクのハッシュ管理
- ホスト側とremote側のデプロイ整合性
単純なCDN読み込みに比べると、運用難易度はかなり高くなります。
ES ModulesではエントリーポイントだけにSRIを付ければよいのか
次のようなES Moduleがあるとします。
<script
type="module"
src="/assets/main.js"
integrity="sha384-..."
></script>
main.js からさらに別のモジュールをimportしている場合、エントリーポイントのSRIが依存モジュール全体へ自動的に伝播するわけではありません。
import "./feature.js";
import("./lazy.js");
main.js の初回取得は検証されますが、子モジュールまで同じハッシュで検証されるわけではありません。
依存モジュールにも整合性情報を持たせる方法として、import mapの integrity キーが標準化されています。
<script type="importmap">
{
"imports": {
"app": "/assets/main.js"
},
"integrity": {
"/assets/main.js": "sha384-...",
"/assets/feature.js": "sha384-...",
"/assets/lazy.js": "sha384-..."
}
}
</script>
ただし、実際のビルド成果物には多数のチャンクが含まれます。
そのため、Vite、Webpack、Next.jsなどで包括的にSRIを導入する場合は、ビルド時にハッシュを自動生成する仕組みがほぼ必須です。
圧縮してもSRIは壊れないのか
gzipやBrotliによるHTTP圧縮は、通常SRIと両立します。
ブラウザは転送時の圧縮を展開した後のリソース内容を利用するため、同じJavaScriptをgzipで送るかBrotliで送るかによって、通常はSRIハッシュを変える必要はありません。
一方で、配信途中にJavaScriptやCSSの中身そのものを変更する処理は問題になります。
例えば次のような機能です。
- CDN上での自動minify
- JavaScriptの書き換え
- User-Agent別のコード変換
- 地域別のファイル差し替え
- A/Bテストによる内容変更
- 同一URLでの個別最適化
最終的にブラウザが受け取るコードの内容が変われば、ハッシュは一致しません。
CloudflareのRocket Loaderなど、scriptタグやJavaScriptの読み込み方法を変更する機能とも相性問題が起こる可能性があります。
SRIを使うファイルは、原則として次の条件を満たす方が安全です。
- 同じURLなら常に同じ内容を返す
- CDN側でコード変換しない
- immutableなファイルとして配信する
- バージョンまたはcontent hash付きURLを使う
導入すると可用性の問題も増える
SRIはハッシュが一致しなければリソースを実行しません。
セキュリティ上は正しい動作ですが、運用上は障害につながります。
例えば、次のようなデプロイ不整合があるとします。
- 新しいHTMLが配信された
- HTMLには新しいハッシュが書かれている
- CDNエッジには古いJavaScriptが残っている
この場合、ハッシュが一致せずアプリが起動しない可能性があります。
逆に、古いHTMLが新しいJavaScriptを参照する場合も同様です。
特に次の構成では注意が必要です。
- Blue-Green Deployment
- ローリングデプロイ
- HTMLと静的アセットを別々にデプロイ
- HTMLキャッシュが長い
- CDNキャッシュのパージに時間がかかる
- 大量の動的チャンクがある
SRIを導入する場合は、セキュリティだけでなくデプロイの原子性やキャッシュ戦略まで考える必要があります。
CDN障害時のフォールバック
CDNの読み込みに失敗した場合、自社ホスト版へ切り替える方法があります。
<script>
function loadLocalLibrary() {
const script = document.createElement("script");
script.src = "/assets/library.min.js";
script.integrity = "sha384-LOCAL_HASH";
script.crossOrigin = "anonymous";
document.head.appendChild(script);
}
</script>
<script
src="https://cdn.example.com/library.min.js"
integrity="sha384-CDN_HASH"
crossorigin="anonymous"
onerror="loadLocalLibrary()"
></script>
ただし、フォールバックを増やすと次の問題も増えます。
- CDN障害とSRI不一致を区別しにくい
- CSPでフォールバック先も許可する必要がある
- 複数のハッシュを管理する必要がある
- 障害解析が複雑になる
- フォールバック先が古い可能性がある
固定ライブラリを数本読み込む程度なら実用的ですが、大規模SPAの大量チャンクに適用するのは現実的ではありません。
React、Vite、Next.jsでは導入するべきか
React
React自体にはSRIを管理する機能はありません。
ReactをCDNのUMDビルドで直接読み込む場合は、外部CDN利用としてSRIを付ける価値があります。
一方、npmでReactをインストールし、ViteやWebpackでバンドルする場合は、ビルドツール側で対応します。
Webpack
Webpackではwebpack-subresource-integrity が代表的です。
動的チャンクを含めてSRIを生成できますが、WebpackのバージョンやHTML生成プラグインとの互換性確認が必要です。
Vite
Vite本体には、一般用途のSRI自動付与が標準機能として十分統合されているとは言いにくく、コミュニティプラグインを使うケースが中心です。
候補としては次のようなものがあります。
vite-plugin-manifest-srivite-plugin-sri-gen- その他のSRI生成プラグイン
ただし、プラグインによって対象が異なります。
- HTMLに直接
integrityを追加する - manifestへハッシュを追加する
- modulepreloadにも対応する
- 動的チャンクまで対応する
導入時には、更新状況と生成対象を確認する必要があります。
Next.js
Next.jsではApp Router向けに、SRIを利用したhash-based CSPの実験的サポートがあります。
ただしexperimentalな機能であり、ビルド方式やルーター、静的生成との組み合わせに制限があります。
Next.jsが自社配信する通常チャンクすべてにSRIを付ける価値は、外部CDNの固定ライブラリほど明確ではありません。
HTMLとアセットが同じデプロイ基盤・同じ信頼境界にあるなら、攻撃者がHTMLを変更できる場合はSRIも一緒に変更できるためです。
一方で、静的アセットを別CDNへ配信する、エッジ変換を挟む、厳格なCSPと連動させる、といった構成では意味が出てきます。
構成別の導入判断
| 構成 | 推奨度 | 判断 |
|---|---|---|
| 外部CDNから固定バージョンのJS/CSSを読む | 高い | SRIの代表的な適用先。原則付ける |
外部CDNからlatestを読む | 低い | SRI以前にバージョンを固定した方がよい |
| 静的HTMLでBootstrapをCDNから読む | 高い | 導入が簡単で効果も分かりやすい |
| WordPressで固定CDNライブラリを読む | 中〜高 | ハッシュ更新を管理できるなら有効 |
| 決済画面で固定外部スクリプトを読む | 高い | 外部スクリプト削減が第一。必要ならSRI |
| npmで取得して自社ビルドするSPA | 低〜中 | 外部CDN利用時より追加効果は小さい |
| 自社CDNでcontent hash付きファイルを配信 | 低〜中 | 信頼境界や配信経路が分かれる場合に意味がある |
| Google Tag Managerや広告タグ | 低い | 内容が頻繁に変わり固定できない |
| 分析・A/Bテストツール | 低い | 動的配信とSRIの相性が悪い |
| Module Federationのremote | 低〜中 | 実装可能だがハッシュ配布とデプロイ管理が難しい |
| Next.jsの通常チャンク | 中 | 配信構成とCSP要件次第 |
| 管理画面・個人情報入力画面 | 高い | 外部スクリプトを減らし、残る固定資産にはSRI |
結局、外部CDNにはSRIを付けるべきか
今回調べた範囲では、次の条件を満たす場合は、原則として付ける判断でよさそうです。
- 外部オリジンからJavaScriptまたはCSSを読み込む
- URLが特定バージョンに固定されている
- 同じURLから常に同じ内容が返る
- CDNがCORSに対応している
- 更新時にハッシュも変更できる
例えば次のような読み込みです。
<script src="https://cdn.example.com/library/latest.js"></script>
これは、まずバージョン固定を検討した方がよいです。
<script
src="https://cdn.example.com/library/1.2.3/library.min.js"
integrity="sha384-..."
crossorigin="anonymous"
></script>
固定バージョンにしたうえでSRIを付ければ、URLと内容の両方を固定できます。
一方で、次のようなものにはSRIを無理に付けない方がよいでしょう。
- 内容が頻繁に変わるanalyticsスクリプト
- タグマネージャー
- 広告配信タグ
- 利用者ごとに内容が変わるウィジェット
- 動的に解決されるremote
- User-Agentごとに異なるコードを返すサービス
その場合は、SRIの代わりに別の防御を考える必要があります。
実務での優先順位
SRIは単独で導入して終わるものではありません。
優先順位としては、次のように考えるのが現実的です。
1. 不要な第三者スクリプトを削除する
最も安全なのは、そもそも読み込まないことです。
特に管理画面、決済画面、個人情報入力画面では、マーケティングタグやチャットツールを本当に読み込む必要があるか見直すべきです。
2. HTTPSとCSPを整備する
HTTPSは前提です。
CSPによって読み込み元や通信先を制限し、不正なスクリプト注入やデータ送信を難しくします。
3. 固定外部リソースにはSRIを付ける
SRIを適用しやすく、効果も高い領域です。
4. npm依存関係とビルド工程を保護する
lockfile、依存更新、監査、CI/CDの保護はSRIとは別に必要です。
5. 動的な第三者スクリプトを監視・隔離する
SRIを使えない場合は、iframe、sandbox、CSP、変更監視、権限制御などを組み合わせます。
調べてみて感じたこと
SRIについて調べる前は、「外部CDNのタグにハッシュを付けるだけの少し古いセキュリティ機能」という印象がありました。
しかし実際には、SRIが解決しようとしている問題は今でも残っています。
Webページで外部JavaScriptを読み込むということは、その配信元へ自分のページと同等の権限を渡すことに近い行為です。
外部スクリプトは、ページ上で次のようなことができます。
- DOMを読む
- フォーム入力を読む
- Cookie以外のクライアントデータへアクセスする
- APIへリクエストを送る
- 外部サーバーへデータを送る
- 追加のスクリプトを読み込む
そのため、「有名なCDNだから信頼する」だけでなく、「このファイルのこの内容だけを許可する」というSRIの考え方は合理的です。
一方で、現代のWebでリスクが高いGoogle Tag Manager、広告、分析、A/Bテスト、チャットなどは、頻繁に中身が変わるためSRIを適用しにくいです。
SRIがあまり使われていない理由は、技術が無意味だからというより、現代の第三者スクリプトが「固定ファイルを読み込む」というSRIの前提から離れているからだと感じました。
まとめ
SRIの最大の価値は、次の一文に集約できます。
信頼している配信元から取得したリソースが、事前に承認した内容と本当に一致しているかをブラウザ側で検証できる。
SRIはCDN改ざん、外部配信元の侵害、誤配信、固定URLの差し替えに有効です。
一方で、HTML自体の改ざん、XSS、npmパッケージ侵害、CI/CD侵害、悪意ある正式リリースまでは防げません。
実務では次の判断が分かりやすいと思います。
- 外部CDNの固定バージョンJS/CSSには、原則としてSRIを付ける
- npmで自社バンドルする一般的なSPAでは、外部CDN利用時より優先度は低い
- 管理画面や決済画面では、まず第三者スクリプトを減らす
- SRIを使えない動的スクリプトには、CSP・隔離・監視を使う
- SRIをセキュリティ対策全体の代わりにしない
SRIは「すべてのWebサイトで絶対に必要な機能」ではありません。
しかし、適用しやすい場所では、現在でも低コストで明確な効果を得られる防御です。
重要なのは、SRIを付けたかどうかだけではなく、どの配信経路を信頼し、どの工程の改ざんを防ごうとしているのかを明確にすることだと思います。
参考資料
仕様・標準
- W3C Subresource Integrity
- WHATWG HTML Standard
- WHATWG Fetch Standard
- W3C Content Security Policy Level 3
公式ドキュメント
- MDN: Subresource Integrity
- MDN: integrity属性
- MDN: import maps
- MDN: modulepreload
- OWASP Third Party JavaScript Management Cheat Sheet
- web.dev: Why HTTPS matters
フレームワーク・ビルドツール
- Webpack Production Guide
- Vite Build Options
- Next.js Content Security Policy Guide
- Angular CLI ng build
- Laravel Vite
- Vue CLI Configuration Reference