Subresource Integrity (SRI) 完全ガイド|CDN スクリプトの integrity・crossorigin・フォールバック

(更新: 2026年6月22日 ) SRI セキュリティ CDN CSP Web XSS
結論
  • CDN から JavaScript を読み込むなら integrity + crossorigin=“anonymous” は 2026 年時点の最低ラインだ。
  • ハッシュは sha384 ベース64 を推奨し、バージョン固定 URL で更新タイミングを自分で制御する。
  • 検証失敗時は 自前ホストへのフォールバック を用意し、CSP で許可ドメインを絞る。
  • サプライチェーン改ざんは XSS 防御 の文脈で A08(Software and Data Integrity Failures)として扱うべきリスクだ。

Subresource Integrity(SRI)とは

Subresource Integrity(SRI)は、<script><link rel="stylesheet"> など外部オリジンから取得するサブリソースについて、ダウンロードした内容が開発者が期待したバイト列と一致するかをブラウザが検証する仕組みだ。

HTML では integrity 属性に 暗号学的ハッシュ(通常 sha256 / sha384 / sha512) をベース64 エンコードした値を書く。一致すれば通常どおり実行・適用され、不一致ならブラウザはブロックし Console にエラーを出す。

<script
  src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"
  integrity="sha384-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  crossorigin="anonymous"
></script>

SRI は W3C Subresource Integrity として標準化され、Chrome・Firefox・Safari・Edge すべてでサポートされている。2026 年の新規 Web アプリでは、公開 CDN から script を直読みする箇所に SRI が無い状態は、セキュリティレビューで指摘されることが多い。

SRI が防ぐ脅威

SRI 単体では XSS 注入そのものは防げない。しかし次のサプライチェーン改ざんシナリオでは決定的に効く。

脅威説明SRI の効果
CDN アカウント侵害攻撃者が CDN 上のファイルを差し替えハッシュ不一致で実行拒否
中間者攻撃(MITM)TLS 突破または混在コンテンツ経由の改ざん内容検証で検出
npm / パッケージレジストリ事故悪意あるバージョンが CDN ミラーに反映固定バージョン + 固定ハッシュで遮断
キャッシュ汚染共有 CDN キャッシュへの不正レスポンス混入オリジン取得後のハッシュ検証で防御

OWASP Top 10 A08:2021 – Software and Data Integrity Failures は、まさにこの「信頼していた外部コードが想定と異なる」問題を扱う。XSS としての被害像(Cookie 窃取・DOM 操作)は XSS 攻撃と防御の完全ガイド と同じだが、入口が自サイトの入力ではなく第三者 CDN である点が異なる。

SRI は「深層防御」の一層

入力のエスケープ・CSP nonce・HttpOnly Cookie と並べ、SRI は読み込むファイルの中身を固定する層だ。どれか一つだけでは不十分で、多層防御として組み合わせる。

integrity 属性の書式

integrity 値は次の形式になる。

アルゴリズム-ベース64ハッシュ

複数のハッシュをスペース区切りで並べると、ブラウザはいずれか一つが一致すれば受理する。アルゴリズム移行期に旧 hash と新 hash を併記する用途がある。

<link
  rel="stylesheet"
  href="https://cdn.example.com/app.v2.css"
  integrity="sha384-OLD_HASH_BASE64 sha384-NEW_HASH_BASE64"
  crossorigin="anonymous"
/>

推奨アルゴリズム(2026)

アルゴリズム推奨度備考
sha384最推奨W3C 仕様のデフォルト推奨。性能と強度のバランスが良い
sha512やや長いが許容
sha256レガシー互換・短い hash
md5 / sha1非推奨衝突耐性が不十分。新規では使わない

<script type="module">importmap 経由のモジュールにも integrity は指定できるが、動的 import() の URL 文字列には HTML 属性として付けられない。バンドラ(Vite・webpack)が生成する <script type="module"> にビルド時 integrity を埋め込む方式が現実的だ。

crossorigin 属性が必須な理由

クロスオリジンリソースに integrity だけ付けても、検証は行われないことがある。ブラウザは CORS チェックとセットでサブリソースの生バイト列を integrity 計算に使うため、crossorigin が必要になる。

crossorigin の値 挙動
anonymous(または空文字) Cookie を送らない CORS リクエスト。公開 CDN の script / CSS で最も一般的。Access-Control-Allow-Origin: * と相性が良い
use-credentials Cookie 付きで取得。CDN が Access-Control-Allow-Credentials: true と具体オリジンを返す必要がある。認証付き private CDN 向け
属性なし no-cors モードになり、クロスオリジンでは integrity 検証がスキップされる——SRI 無効

CDN 側では次のレスポンスヘッダーが必要になることが多い。

Access-Control-Allow-Origin: *
Timing-Allow-Origin: *

jsDelivr・unpkg・cdnjs など主要 npm CDN は Access-Control-Allow-Origin: * を返すため、crossorigin="anonymous" と組み合わせやすい。自社 CDN を立てる場合は、SRI を使うオリジンすべてで CORS ヘッダーを忘れないこと。

crossorigin 忘れはサイレント失敗

DevTools 上は script が 200 で読み込まれているのに、integrity が効いていない——という状態になり得る。セキュリティ監査用チェックリストに「外部 script に integrity と crossorigin の両方があるか」を入れる。

integrity ハッシュの生成方法

ハッシュは実際に配信されるバイト列に対して計算する。gzip / Brotli 圧縮は転送時のみで、ブラウザが integrity 計算に使うのは展開後の本文だ。CDN から取得したファイルをそのまま hash すればよい。

openssl(Linux / macOS / Git Bash)

curl -fsSL "https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js" \
  -o /tmp/react.production.min.js

openssl dgst -sha384 -binary /tmp/react.production.min.js | openssl base64 -A

出力例(実際の値は上記コマンドで取得する):

integrity="sha384-(ここにベース64文字列)"

Windows PowerShell

$url = "https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"
$bytes = (Invoke-WebRequest -Uri $url -UseBasicParsing).Content
$hash = [System.Security.Cryptography.SHA384]::Create().ComputeHash($bytes)
$base64 = [Convert]::ToBase64String($hash)
Write-Output "sha384-$base64"

Node.js ワンライナー

import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';

const file = readFileSync('/tmp/react.production.min.js');
const base64 = createHash('sha384').update(file).digest('base64');
console.log(`sha384-${base64}`);

shasum(macOS 付属)

shasum -b -a 384 /tmp/react.production.min.js | awk '{print $1}' | xxd -r -p | base64

macOS の base64 は改行を付ける場合がある。HTML 属性用は -A(一行)相当の出力に揃える。

オンライン CDN が integrity を提供する例

jsDelivr はファイルページから SRI 用の integrity 文字列をコピーできる。手動計算と突合して CI に取り込むとミスが減る。

<!-- jsDelivr の例: バージョンとファイルパスを固定 -->
<script
  src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"
  integrity="sha384-(CDN ページまたは openssl で取得)"
  crossorigin="anonymous"
></script>
ビルド成果物にも SRI を

アプリ自身の app.[hash].js を CDN に載せる場合も、デプロイパイプラインでアップロード直後のファイルから hash を計算し、HTML テンプレートまたは manifest に書き戻す。Cloudflare Pages / Vercel などはビルド時にファイル名へ content hash を付けるが、それとは別に Subresource Integrity 用の属性が HTML に必要かどうかはテンプレート設計次第だ。

バージョン固定と CDN URL 設計

SRI と相性が悪い URL パターンがある。

URL パターンSRI との相性理由
/npm/[email protected]/umd/react.production.min.jsパスに semver 固定
/npm/react@18/umd/react.production.min.jsメジャー固定はマイナー更新で中身が変わり得る
/npm/react/latest/...デプロイ後いつでも hash が無効化
/npm/react@^18.3.1/...(semver range)解決結果が変動

原則: SRI を付ける URL は immutable として扱う。 npm なら exact version、自社 CDN なら /assets/app-20260622-a1b2c3.js のようにファイル名にビルド ID を含める。

package.json の dependencies を更新したタイミングで、HTML に埋めた integrity も同じ PR で更新する。Dependabot が package だけ上げて integrity が古いまま、という PR は CI で落とす。

HTML への適用例

複数 CDN スクリプト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <title>SRI サンプル</title>
  <link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/[email protected]/modern-normalize.min.css"
    integrity="sha384-(modern-normalize の hash)"
    crossorigin="anonymous"
  />
</head>
<body>
  <div id="root"></div>
  <script
    src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"
    integrity="sha384-(react の hash)"
    crossorigin="anonymous"
  ></script>
  <script
    src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js"
    integrity="sha384-(react-dom の hash)"
    crossorigin="anonymous"
  ></script>
  <script src="/app.js" defer></script>
</body>
</html>

同一オリジン /app.js に integrity を付けることもできる。自前ホストなら改ざん検知に有効だが、デプロイのたびに hash 更新が必要になる。ビルドツールが自動注入するか、ファイル名に content hash を付けて src 自体を変える方が運用しやすい。

ES modules(importmap + SRI)

<script type="importmap">
{
  "imports": {
    "vue": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm-browser.prod.js"
  }
}
</script>
<script type="module" crossorigin="anonymous">
  import { createApp } from 'vue';
  createApp({ template: '<p>Hello</p>' }).mount('#app');
</script>

importmap 内 URL への integrity は仕様拡張中であり、2026 年時点では importmap の各 URL に integrity を直接書けないケースがある。モジュール CDN を使う SPA は、Vite / webpack でバンドルし <script type="module" integrity="..."> をビルド出力に含める方が確実だ。

検証失敗時のフォールバック戦略

integrity 不一致時、ブラウザは script を実行しない。ユーザーから見ると真っ白な画面機能停止になる。可用性とセキュリティのバランスを取るフォールバックパターンを整理する。

戦略 メリット・注意点
自前ホスト ミラー CDN 障害・改ざん時に同一バージョンを /static/vendor/ から配信。事前に integrity 検証済みファイルをデプロイに含める
onerror 動的読み込み <script onerror="loadFallback()"> で第二候補 URL へ。第二候補にも integrity を付けるか、'self' のみ CSP で限定
Service Worker キャッシュ 過去に検証済みのレスポンスを返す。初回訪問者には効かない。更新戦略が複雑
エラー UI 表示 フォールバックせず「メンテナンス中」を出す。金融系など改ざんより停止を選ぶ場合

onerror フォールバックの実装例

<script
  id="vendor-react"
  src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"
  integrity="sha384-PRIMARY_HASH"
  crossorigin="anonymous"
  onerror="window.__loadVendorFallback('react', '18.3.1')"
></script>
<script>
  window.__loadVendorFallback = function (pkg, version) {
    var s = document.createElement('script');
    s.src = '/vendor/' + pkg + '@' + version + '.min.js';
    s.crossOrigin = 'anonymous';
    s.integrity = 'sha384-FALLBACK_HASH_FOR_SELF_HOSTED_COPY';
    document.head.appendChild(s);
  };
</script>

フォールバック script を無 integrity で読み込むのは、第二 CDN も侵害されている場合に同じリスクを抱える。自前ホストのファイルはデプロイ時に hash を固定し、PRIMARY と FALLBACK で同じバイト列を配るのが理想だ。

監視とアラート

本番で SRI 失敗が起きると、ブラウザ Console に次のようなメッセージが出る。

Failed to find a valid digest in the 'integrity' attribute for resource
'https://cdn.example.com/lib.js' with computed SHA-384 digest '...'.

Reporting APIContent-Security-Policy-Report-Only とは別経路のため、Synthetic monitoring で主要ページの script 読み込み成功率を計測するか、RUM で error イベントを集約する。CDN メンテナンスで一斉に hash がずれる前に、ステージングでバージョンアップ PR を検証する。

フォールバックが CSP を破らないか

動的に追加する script の URL は CSP の script-src に含める必要がある。'strict-dynamic' と nonce ベースのポリシーでは、onerror で挿入した script がブロックされる——設計段階で nonce 付与か hash 許可を決める。

CSP(Content Security Policy)との統合

SRI と CSP は補完関係にある。

レイヤー役割
CSP script-srcどのオリジンから script を読み込んでよいか
SRI integrity許可された URL から取得した中身が期待どおりか

CSP だけでは https://cdn.jsdelivr.net を許可すると、その CDN 上の任意ファイル(攻撃者が差し替えたファイル含む)も実行され得る。SRI を付ければ、特定バージョンの特定ファイルに実行権を限定できる。

推奨 CSP 例(CDN + 自前バンドル)

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.jsdelivr.net 'nonce-RANDOM' 'strict-dynamic';
  style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline';
  connect-src 'self' https://api.example.com;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'self';
  • script-src に CDN ホストを列挙
  • アプリ本体は 'self' + nonce
  • <script src="https://cdn..."> に integrity を付けて中身を固定

CSP の require-sri-for ディレクティブ(require-sri-for script style)は、対応ブラウザで SRI 未設定の外部 script / style をブロックする。ただし 2026 年時点ではブラウザサポートが限定的であり、本番で単独依存するより CI で integrity 必須 lint を回す方が確実だ。

CSP hash ソースとの違い

CSP の 'sha256-...' ソース式はインライン script の内容を許可するためのもの。SRI の integrity は外部 URL 取得結果に対する検証。名前は似ているが用途が異なる。

機能対象指定場所
CSP hash ソースインライン <script> 本文CSP ヘッダー
SRI integrity外部 script / link のレスポンス bodyHTML 属性

両方とも sha256 系だが、混同しないこと。インライン設定用 JSON などは CSP hash、CDN の React UMD などは SRI integrity。

Report-Only との併用

CSP を Report-Only で段階導入 している間も SRI は即座に有効になる。SRI 失敗は CSP レポートには載らないため、別途 E2E テストで確認する。

フレームワーク・ビルドパイプラインでの自動化

手動 integrity 管理はヒューマンエラーの温床だ。2026 年の定石は CI / ビルドで生成する。

1

package-lock.json または pnpm-lock で CDN URL とバージョンを確定する

2

postinstall または deploy 前ジョブで CDN からファイルを取得し sha384 hash を計算する

3

HTML テンプレート・Astro レイアウト・Next.js Document に integrity 属性を注入する

4

pull request で hash 変更と lockfile 変更がセットかをレビューする

5

本番反映後、Synthetic check で script 200 + 実行確認を行う

Astro レイアウト例

---
// src/layouts/Base.astro
const reactVersion = '18.3.1';
const reactIntegrity = import.meta.env.PUBLIC_REACT_SRI; // CI で注入
---
<script
  src={`https://cdn.jsdelivr.net/npm/react@${reactVersion}/umd/react.production.min.js`}
  integrity={reactIntegrity}
  crossorigin="anonymous"
></script>

環境変数 PUBLIC_REACT_SRI はデプロイパイプラインで openssl 出力を設定する。

webpack subresource-integrity プラグイン

// webpack.config.js
import SriPlugin from 'webpack-subresource-integrity';

export default {
  output: { crossOriginLoading: 'anonymous' },
  plugins: [
    new SriPlugin({
      hashFuncNames: ['sha384'],
      enabled: process.env.NODE_ENV === 'production',
    }),
  ],
};

crossOriginLoading: 'anonymous' は output script に crossorigin を付与するために必要。HtmlWebpackPlugin と組み合わせると、バンドルされた <script> に自動で integrity が付く。

CI lint(integrity 欠落検出)

# .github/workflows/sri-check.yml
name: sri-check
on: [pull_request]
jobs:
  lint-html:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Fail if external script lacks integrity
        run: |
          ! grep -RIn '<script[^>]*src="https://' public/ src/ \
            | grep -v 'integrity='

grep ベースは粗いが、PR で「CDN script 追加したのに integrity 無し」を防げる。本格運用では html-validate やカスタム ESLint ルールへ移行する。

2026 年のベストプラクティス checklist

実務でそのまま使えるチェックリスト。

  1. 公開 CDN script にはすべて integrity + crossorigin=“anonymous”
  2. URL は semver または content hash で固定latest 禁止
  3. CSP script-src で CDN ホストを最小限に列挙(設定ガイド参照
  4. 依存更新 PR では lockfile・integrity・E2E を同一 PR で更新
  5. 自前ミラー を用意し、onerror または CDN 障害時の Runbook を文書化
  6. サプライチェーン事故XSS 多層防御 の観点で tabletop 訓練に含める
  7. md5 / sha1 integrity は新規禁止
  8. importmap 直 CDN は SRI 制約を理解した上で、可能ならバンドルに寄せる

よくある失敗パターン

integrity だけ付けて crossorigin が無い

症状: 本番で「SRI 設定済み」と思い込んでいるが、検証がスキップされている。

対策: HTML linter とコードレビューで crossorigin を必須化。

CDN が Content-Encoding だけ変えた

症状: 同一ファイルのはずが Brotli 配信切替で body が変わり hash 不一致。

対策: hash 計算元はブラウザが解釈する解压後 body。CDN 設定変更後に hash を再計算。

開発環境だけ integrity を外している

症状: staging と production で HTML が diverge し、本番だけ動かない。

対策: 開発でも integrity を付け、Vite の server.proxy でローカルミラーを使う。どうしても外すならビルドフラグを明示し、本番 artifact では必ず有効化。

CSP strict-dynamic と onerror フォールバックの競合

症状: フォールバック script が CSP でブロックされ、復旧不能。

対策: フォールバック URL を script-src に事前登録するか、フォールバック用 script に nonce を付与する SSR 設計にする。

ブラウザ DevTools での確認手順

  1. Network タブで対象 script を選択
  2. HeadersOrigin 付きリクエストになっているか(crossorigin 効果)
  3. Console に integrity エラーが無いか
  4. Sources で script が実行されているか
  5. わざと integrity の末尾 1 文字を改ざんし、ブロックされることを確認

改ざんテストはステージングのみで行う。本番 HTML を直接いじるとキャッシュ伝播でユーザー影響が出る。

SRI と関連ヘッダーの関係

ヘッダー / 属性SRI との関係
Content-Security-Policyオリジン許可。SRI は中身固定
Cross-Origin-Resource-PolicyCORP が block だと CDN 取得自体が失敗する——CDN 側設定を確認
Timing-Allow-OriginSRI とは無関係だが CDN パフォーマンス計測用
Cache-Control長期キャッシュ + 固定 URL は SRI と相性良い

セキュリティヘッダー一覧 と合わせ、レスポンスヘッダーと HTML 属性の両面から攻撃面を狭める。

Next.js / Vite での CDN 読み込みパターン

フレームワーク経由で CDN を使う場合も、最終的に HTML に出力される <script> に integrity が載っているかを確認する。next/scriptstrategy や Vite の index.html 直書きは、ビルド後の HTML を grep するのが確実だ。

next/script で integrity を指定

// app/layout.tsx
import Script from 'next/script';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ja">
      <body>
        {children}
        <Script
          src="https://cdn.jsdelivr.net/npm/[email protected]/dist/analytics.min.js"
          integrity="sha384-ANALYTICS_HASH_FROM_OPENSSL"
          crossOrigin="anonymous"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

crossOrigin は React では camelCase だ。next/script はサーバー Components からそのまま integrity を渡せる。App Router では CSP nonce ミドルウェア と併用し、自前バンドル script に nonce、CDN vendor に integrity という住み分けが多い。

Playwright による E2E 検証

デプロイ後に SRI が効いているかを自動テストする例だ。Console の integrity エラーを監視する。

// tests/sri.spec.ts
import { test, expect } from '@playwright/test';

test('CDN scripts load without integrity errors', async ({ page }) => {
  const integrityErrors: string[] = [];
  page.on('console', (msg) => {
    if (msg.type() === 'error' && msg.text().includes('integrity')) {
      integrityErrors.push(msg.text());
    }
  });
  await page.goto('https://staging.example.com/');
  await page.waitForLoadState('networkidle');
  expect(integrityErrors).toEqual([]);
});

ステージングで CDN バージョンを上げた PR では、このテストが hash 更新漏れを検知する。本番 URL ではなく staging を対象にし、テスト用データで vendor script が実際に読み込まれるページを選ぶ。

関連記事

この記事は セキュリティ テーマの一環です。あわせて読むと理解が深まる関連記事をまとめました。

トピック記事
CSRF対策の実装ガイドCSRF対策の実装ガイド|SameSite・Synchronizer Token・Laravel/Expressコード
Double Submit Cookie完全実装Double Submit Cookie完全実装|Synchronizer Token比較・SPA+JWT・サブドメイン攻撃
セキュアなCookie設定ガイドセキュアなCookie設定ガイド|HttpOnly・Secure・SameSiteの使い分けとCSRF対策
CORSエラーの対処法CORSエラーの対処法|「Access-Control-Allow-Origin」で解決する
CORSプリフライトリクエストCORSプリフライトリクエスト|OPTIONSが飛ぶ条件と正しい応答
ローカル開発でCORSエラーが出る原因と3つの回避策ローカル開発でCORSエラーが出る原因と3つの回避策
OAuth2 PKCE SPA実装ガイドOAuth2 PKCE SPA実装ガイド|Authorization Code + S256 + BFFパターン
Passkeys・WebAuthn実装の基礎Passkeys・WebAuthn実装の基礎|FIDO2・Resident Key・@simplewebauthn/server
JWTのデバッグ方法JWTのデバッグ方法|トークンの中身を安全に確認する
JWTの中身を確認する方法JWTの中身を確認する方法|改ざんが検知される仕組み
JWKSとJWT鍵ローテーション運用ガイドJWKSとJWT鍵ローテーション運用ガイド|kid検証・ゼロダウンタイム・Node.js実装
HSTS Preload設定手順HSTS Preload設定手順|nginx・Cloudflare実装とhstspreload.org申請ガイド

まとめ

Subresource Integrity は、CDN・npm ミラー・サードパーティ script 依存を持つ現代 Web アプリにおいて、ソフトウェア供給チェーンの改ざんをブラウザ側で止めるための標準機能だ。

実装の核心は次の三点に集約される。

  1. integrity に sha384 ベース64 ハッシュを書く
  2. crossorigin=“anonymous” を必ず付ける
  3. バージョン固定 URLCI 自動更新 で運用する

検証失敗時のフォールバック、CSP との整合、XSS 防御 としての位置づけまで設計すれば、可用性を維持しつつサプライチェーンリスクを現実的な水準まで下げられる。2026 年以降も CDN compromise のニュースは続くだろう——中身を固定する SRI は、そのニュースが自社ユーザーのセッション窃取に繋がらないための、コスト対効果の高い一手だ。