---
title: "外部スクリプトとは？script srcの読み込み方とSRI・CSPで安全に使う方法"
url: "https://www.issoh.co.jp/tech/details/1465/"
published: 2023-09-26
updated: 2026-10-11
categories: ["セキュリティ"]
publisher: "株式会社一創"
---

# 外部スクリプトとは？script srcの読み込み方とSRI・CSPで安全に使う方法

外部スクリプトとは、JavaScriptをHTMLとは別の`.js`ファイルに書き、`<script src="...">`で読み込む方式です。HTMLの中に直接コードを書く「インラインスクリプト（内部スクリプト）」と対になります。この記事では、書き方と`async`・`defer`による実行順の違いを押さえたうえで、広告タグや解析タグのように他社のサーバーから読む外部スクリプトを、SRI・Integrity-Policy・CSPでどう守るかまでを扱います。

## まとめ｜外部スクリプトの読み込み方と安全に使う条件

- 外部スクリプトは`<script src="app.js"></script>`で読み込む。`src`を付けたタグの中に書いたコードは実行されない。
- DOMを操作するスクリプトは`defer`、他と依存しない計測タグは`async`。`type="module"`は何も付けなくても`defer`と同じタイミングで動く。
- 他社のサーバーから読んだJavaScriptは自社ページと同じ権限で動く。配信元が乗っ取られると、そのコードを実行する訪問者が被害を受け得る（2024年6月のpolyfill.io事件では10万サイト超で利用）。
- 中身が変わらないライブラリは`integrity`と`crossorigin`を付けてSRIで固定し、`Integrity-Policy`ヘッダで付け忘れを検知する。中身が変わるタグ（GTM・広告・チャット）はSRIが使えないので、CSPのnonceと`strict-dynamic`で読み込み経路を絞る。
- 決済ページはPCI DSS v4.0.1の要件6.4.3と11.6.1で、スクリプトの一覧・許可・完全性の確認と改ざん検知が2025年3月31日から必須になっている。

## 外部スクリプトとインラインスクリプトの違い

JavaScriptをページに組み込む方法は2つです。HTMLの`script`タグの中にコードを直接書くのがインラインスクリプト、コードを別ファイルに置いて`src`属性でURLを指定するのが外部スクリプトです。

### script srcで.jsファイルを読み込む書き方

外部ファイル側には`<script>`タグを書かず、JavaScriptのコードだけを置きます。HTML側は`src`にファイルのパスかURLを書きます。

```
<!-- 外部スクリプト：同じサーバーの.jsファイル -->
<script src="/js/app.js" defer></script>

<!-- 外部スクリプト：他社のサーバーから読む（サードパーティ） -->
<script src="https://cdn.example.com/widget.js" async></script>

<!-- インラインスクリプト：タグの中に直接書く -->
<script>
  document.title = "読み込み完了";
</script>
```

よくある間違いは、`src`を付けたタグの中にもコードを書いてしまうことです。HTML仕様（[WHATWG HTML Living Standard](https://html.spec.whatwg.org/multipage/scripting.html#the-script-element)）では、`src`があるscript要素の中身は空か、コメント程度の説明文だけと定められています。中に書いた処理は実行されないため、外部ファイルとインラインは別々のタグに分けます。

### 外部ファイルに分ける利点とCSPでの扱いの差

外部ファイルにすると、同じ処理を複数ページで使い回せ、ブラウザのキャッシュも効きます。見落とされやすいのはセキュリティ面の差です。CSP（Content Security Policy）でスクリプトに適用される取得元制限を設定すると、明示的に許可していないインラインスクリプトは実行を止められます。XSSで注入されるコードの多くがインラインだからです。

| 観点          | 外部スクリプト          | インラインスクリプト           |
| ----------- | ---------------- | -------------------- |
| 書き方         | <script src=”…”> | <script>コード</script> |
| 複数ページでの再利用  | できる              | ページごとに複製             |
| ブラウザキャッシュ   | 効く               | HTMLと一体              |
| async・defer | 効く               | 効かない（module除く）       |
| CSPで許可する方法  | ホスト名・nonce・hash  | nonce・hashを推奨        |
| SRIでの改ざん検知  | できる              | 対象外                  |

CSPを入れる予定があるなら、インラインのイベント属性（`onclick="..."`）や`<script>`直書きを外部ファイルへ移しておくと、後の移行が楽になります。

## async・defer・type=”module”で変わる実行タイミング

属性を付けない外部スクリプトは、ダウンロードと実行が終わるまでHTMLの解析を止めます。`head`に置くと、その間は画面が描画されません。これを避けるのが`async`と`defer`です（[MDN：script要素](https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/script)）。

| 指定            | ダウンロード   | 実行のタイミング                | 複数ファイルの実行順 |
| ------------- | -------- | ----------------------- | ---------- |
| 属性なし          | 解析を止めて取得 | 取得後すぐ                   | 記述順        |
| async         | 解析と並行    | 取得が終わりしだい               | 保証なし       |
| defer         | 解析と並行    | 解析完了後・DOMContentLoaded前 | 記述順        |
| type=”module” | 解析と並行    | deferと同じ                | 記述順        |
| module＋async  | 依存ごと並行   | 取得が終わりしだい               | 保証なし       |

### deferとasyncを選び分ける条件

迷ったら`defer`です。DOMの要素を探して操作するコードは、解析完了後に動く`defer`なら要素が見つからない事故を避けられます。jQueryのように別のスクリプトが前提にするライブラリは、依存元と利用側の両方を外部ファイルにして同じく`defer`をそろえると、記述順を保ったまま解析と並行して取得できます。ライブラリだけを`defer`にして利用側をインラインで書くと、ライブラリより先にインラインが実行されて「`$ is not defined`」が出ます。`async`が向くのは、アクセス解析タグのように他のどのスクリプトにも依存されず、DOMの完成も待たなくてよいものだけです。

次の3点も覚えておくと事故が減ります。`async`と`defer`を両方付けると`async`として動きます。インラインのクラシックスクリプトには両属性とも効果がありませんが、インラインのモジュールスクリプトにはasyncが有効です。`type="module"`は最初から遅延実行なので`defer`は無意味です。WordPressでは6.3から`wp_enqueue_script()`の第5引数で`'strategy' => 'defer'`を指定でき、テーマやプラグインのタグを直接書き換えずに済みます（[WordPress Developer Resources](https://developer.wordpress.org/reference/functions/wp%5Fenqueue%5Fscript/)）。

## サードパーティの外部スクリプトが持ち込むリスク

Googleタグマネージャー、広告タグ、チャットボット、決済フォームのSDKなど、他社のサーバーから読む外部スクリプトはサードパーティスクリプトと呼ばれます。問題は、どこから読んだかに関係なく、`<script>`で読み込んだコードは自社ページと同じ権限で動くことです。

### 読み込んだJavaScriptの実行権限とアクセス範囲

外部スクリプトはページのDOM全体、フォームに入力中の文字、`HttpOnly`でないCookie、`localStorage`に触れられます。配信元のサーバーやアカウントが乗っ取られて中身が差し替えられると、自社のサーバーには何の変更もないまま、改ざんされたスクリプトを読み込み、実行した訪問者のブラウザで攻撃コードが動き得ます。決済画面のカード番号を盗み出す「Webスキミング」はこの経路で起き、国内でも決済画面の改ざんによる漏えいが続いています（事例は[PhotoGoodsの不正アクセスとカード情報漏えい｜決済画面の改ざんとアップロード機能の締め方](/tech/details/18205/)）。

これに対し、`iframe`で埋め込んだ他社のコンテンツは別オリジンとして隔離され、親ページのDOMに触れません。決済フォームを決済代行会社のiframe型やリダイレクト型にするのは、この隔離を使うためです。iframeの`sandbox`で権限をさらに削る方法は[iframeとは？sandbox属性14種とCookie分割の実装判断を解説](/tech/details/17529/)で扱っています。

### polyfill.io事件（2024年6月）の配信元侵害と不正転送

サードパーティスクリプトのリスクが現実になった代表例が、2024年6月のpolyfill.io事件です。`cdn.polyfill.io`は古いブラウザ向けの補完コードを配るサービスで、Sansecの報告では10万を超えるサイトが埋め込んでいました。2024年2月にドメインとGitHubアカウントが中国の企業（CloudflareによるとFunnull）へ売却され、同年6月25日、モバイル端末の訪問者だけを偽のGoogle Analyticsドメイン経由でスポーツ賭博サイトへ転送するコードが配信されていることが公表されました（[Sansec](https://sansec.io/research/polyfill-supply-chain-attack)）。

Cloudflareは売却直後の2月の時点でcdnjs上に代替のミラーを用意しており、事件の公表後は、自社経由で配信している顧客サイトのpolyfill.ioへの参照を、そのミラーへ自動で書き換える措置を取りました（[Cloudflare Blog](https://blog.cloudflare.com/automatically-replacing-polyfill-io-links-with-cloudflares-mirror-for-a-safer-internet/)）。Sansecの6月27日の追記によると、その後レジストラのNamecheapがドメインを保留状態にしています。

この事件から引き出せる教訓は2つあります。1つは、配信元のドメインの持ち主が変わるだけで、自社のコードを1行も変えずに被害が出ること。もう1つは、polyfill.ioのコードがリクエストヘッダーに応じて毎回生成される方式だったため、次に説明するSRIでは固定できなかったことです。使っていない外部スクリプトを外すことが、最も確実な対策になります。

## SRIで外部スクリプトの改ざんを止める手順

SRI（Subresource Integrity）は、読み込むファイルのハッシュ値を`integrity`属性に書いておき、ブラウザが取得した中身のハッシュと一致しなければ実行しない仕組みです。CDNのファイルが差し替えられても、ハッシュが変わるので読み込みが止まります（[MDN：Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource%5FIntegrity)）。

### integrity値の生成とcrossorigin属性

ハッシュ値はOpenSSLで作れます。使えるアルゴリズムは`sha256`・`sha384`・`sha512`の3つです。

```
cat widget.js | openssl dgst -sha384 -binary | openssl base64 -A
```

出力された値の先頭に`sha384-`を付けて`integrity`に書き、同時に`crossorigin="anonymous"`を付けます。

```
<script src="https://cdn.example.com/widget.js"
        integrity="sha384-（生成した値）"
        crossorigin="anonymous"></script>
```

別オリジンのクラシックスクリプトでは、`crossorigin`を省くとSRI検証に失敗します。同一オリジンでは省略でき、モジュールスクリプトは省略時もCORSで取得されます。クラシックスクリプトで必要になる理由は、他のオリジンのファイルにSRIを使うには、配信側が`Access-Control-Allow-Origin`を返すCORSの読み込みが必要で、`crossorigin`なしのscriptタグはCORSを使わない`no-cors`モードで取得されるためです。配信側がCORSヘッダーを返していない場合は、SRIを付けた時点で読み込めなくなります。CORSの仕組みは[CORSとは？仕組み・プリフライト・サーバー設定例・エラー解決を実装目線で解説](/tech/details/13763/)にまとめています。jQueryの公式CDNのように、`integrity`付きのタグを配布元が用意している場合はそれをそのまま使えます（[jQuery 4.0とは｜4.0.0の変更点・CDNとSRI・3系からの移行手順](/tech/details/10659/)）。

### Integrity-PolicyヘッダーによるSRIの指定漏れ検知

SRIはタグ1つずつに付けるため、後から追加されたタグで付け忘れが起きます。それを拾うのが`Integrity-Policy`レスポンスヘッダーです。指定すると、ブラウザは`integrity`のないスクリプトと`no-cors`モードのスクリプトの読み込みをブロックし、違反をレポートとして送ります（[MDN：Integrity-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Integrity-Policy)）。

```
Reporting-Endpoints: integrity-endpoint="https://example.com/integrity"
Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(integrity-endpoint)
```

最初は`-Report-Only`付きで流し、対応ブラウザでレポートの送信と受信を確認し、必要な全スクリプトがポリシーを満たすことを確かめてから強制モードへ切り替えます。SRIを付けられない動的タグが必要なページでは、この設定の強制適用はできません。MDNの互換性データ（browser-compat-data）では、スクリプトの検査はChrome 138・Firefox 145・Safari 26から対応しています。

### SRIが使えない動的なスクリプトの扱い

SRIはファイルの中身が固定されている前提の仕組みです。Googleタグマネージャー、広告配信タグ、チャットウィジェットのように、提供元が予告なく中身を更新するスクリプトにハッシュを書くと、更新のたびに読み込みが止まります。以下はリスクを減らす対策ですが、nonceで許可したスクリプト自体の改ざんはCSPでは検知できません。

- CSPで、読み込みを許可する起点を自社が出力したタグだけに絞る（次章）。
- 決済やログインなど重要な画面では、そもそも読み込まない。タグマネージャーの配信対象から決済ページを外す。
- 提供元が公式に「自社サーバーから直接読み込むこと」を求めるもの（Stripe.jsは`js.stripe.com`から直接読み込み、バンドルやセルフホストをしないよう[Stripeのドキュメント](https://docs.stripe.com/js)が明記）は、SRIで固定せず提供元の運用に任せる。

## CSPで外部スクリプトを許可する書き方

CSPを入れて外部スクリプトが止まったとき、許可の書き方は2通りあります。読み込み元のホスト名を`script-src`に並べる方法と、nonceで個々のタグを許可する方法です。

ホスト名を並べる方法は手軽ですが、許可したホストに攻撃者が使えるファイルが1つでもあれば迂回されます。広告やタグマネージャーは、そこからさらに別のドメインのスクリプトを読み込むため、許可リストも際限なく伸びます。推奨されるのは、nonceと`'strict-dynamic'`の組み合わせです。

```
Content-Security-Policy: script-src 'nonce-（リクエストごとの乱数）' 'strict-dynamic'; object-src 'none'; base-uri 'none'

<script nonce="（同じ乱数）" src="https://cdn.example.com/widget.js"></script>
```

nonceは「一度だけ使う乱数」の意味で、サーバーがリクエストのたびに推測できない値を作り、ヘッダーとタグの両方に入れます。同じ値を持つタグだけが実行され、`'strict-dynamic'`を付けると、そのタグのコードがcreateElementなどで動的に追加したスクリプトにも許可が引き継がれます。ただし、document.writeで挿入するスクリプトなどは同じ扱いになりません。ヘッダーの文字列を固定値でコピーするとnonceが毎回同じになり、意味がなくなる点に注意してください。Googleタグマネージャーはコンテナコード自体がインラインのため、Googleが公開しているnonce対応のコンテナスニペットを使います（[Google タグプラットフォーム：CSPの使用](https://developers.google.com/tag-platform/security/guides/csp)）。

CSPのディレクティブの全体像、Report-Onlyでの段階導入、nginxやExpressでnonceを生成する実装は[コンテンツセキュリティポリシー（CSP）とは？nonce方式の設定とXSS防御の実装判断を解説](/tech/details/16358/)で詳しく扱っています。CSPが止めるのは注入されたコードの実行で、XSSの穴そのものは塞ぎません。XSSの原因側の対策は[XSSとCSRFの違いとは？仕組み・被害・対策を実装レベルで徹底比較](/tech/details/4109/)を参照してください。

## 外部CDNから読むか自社サーバーに置くかの判断

jQueryやBootstrapのようなライブラリを公開CDNから読むと「他のサイトで読んだキャッシュが使えて速い」と言われてきました。この利点は既に消えています。Chromeは86（2020年）からHTTPキャッシュを表示中のサイトごとに分割しており、同じURLのファイルでも別のサイトで取得したキャッシュは使われません（[Chrome for Developers](https://developer.chrome.com/blog/http-cache-partitioning)）。Firefoxも85（2021年1月）でHTTPキャッシュを訪問先サイトごとに分割し（[Mozilla Security Blog](https://blog.mozilla.org/security/2021/01/26/supercookie-protections/)）、Safariも表示中のサイト単位でキャッシュを分けています。

そのため、中身が固定されたライブラリは自社サーバーに置く（セルフホストする）のが基本です。配信元のドメインの持ち主が変わるリスクがなくなり、他のオリジンへの接続も1つ減ります。公開CDNを使い続けるなら、バージョンを固定したURL（`@latest`のような指定は避ける）とSRIを必ず組み合わせます。逆に、タグマネージャー・広告・決済SDKのように提供元のサーバーから読むことが前提のものは、セルフホストの対象になりません。

## 決済ページの外部スクリプトに課されるPCI DSSの要件

クレジットカード決済を扱うECサイトでは、外部スクリプトの管理がPCI DSS（カード業界のセキュリティ基準）の要件になっています。現行版のv4.0.1（2024年6月公表）では、次の2つが2025年3月31日に発効しました（[PCI SSC Blog](https://blog.pcisecuritystandards.org/important-updates-announced-for-merchants-validating-to-self-assessment-questionnaire-a)）。

| 要件     | 求められること                        | 対応する技術の例               |
| ------ | ------------------------------ | ---------------------- |
| 6.4.3  | 決済ページの全スクリプトの許可・完全性確認・一覧と理由の記録 | CSP・SRI・台帳             |
| 11.6.1 | 決済ページのHTTPヘッダーと内容の無断変更を検知し通知   | HTTPヘッダー・ページ内容の変更検知と通知 |

決済代行会社の決済フォームをiframeで埋め込むだけの加盟店が使う自己問診票SAQ Aでは、2025年1月の改訂で6.4.3と11.6.1が質問項目から外れました。代わりに「自社サイトがECシステムに影響し得るスクリプト攻撃に対して脆弱でないことを確認した」という適格性基準が加わっています。確認の方法は、6.4.3・11.6.1相当の技術を自社か委託先で実装するか、PCI DSS準拠の決済代行会社からスクリプト攻撃の保護を含むサービスである旨の確認を得るかです（[PCI SSC FAQ 1588](https://www.pcisecuritystandards.org/faqs/1588/)）。iframe型にしたから親ページのスクリプトは無関係、とはならない点に注意してください。

日本でも、割賦販売法の実務上の指針であるクレジットカード・セキュリティガイドライン（日本クレジット協会が公表・現行は6.1版）が、EC加盟店に脆弱性対策を求めています（[日本クレジット協会：関連資料](https://www.j-credit.or.jp/security/document/index.html)）。PCI DSSの準拠を求められていないサイトでも、決済ページで動くスクリプトの台帳を作っておくと、改ざんに気付くまでの時間を縮められます。自社サイトに読み込まれている外部スクリプトや改ざんの余地を第三者の目で洗い出すなら、[脆弱性診断・セキュリティ診断](/service/system/vulnerability/)で確認できます。

## 外部スクリプトが読み込まれないときの確認手順

まずブラウザの開発者ツールを開き、ConsoleとNetworkタブを見ます。止まった原因ごとに、出るメッセージが違います。

- **パスの誤り**：Networkタブで該当ファイルが404になっている。`src`の相対パスは文書のベースURLを基準に解決される。通常は文書のURLが基準だが、base要素のhrefで変更される場合がある。
- **CSPによるブロック**：Consoleに「Refused to load the script … because it violates the following Content Security Policy directive」と出る。違反したディレクティブとスクリプトの必要性を確認し、正規のタグだけを許可する。strict-dynamicが有効な場合はホストを追加しても許可されず、nonce方式ではヘッダーとタグの値を一致させる。
- **SRIの不一致**：「Failed to find a valid digest in the ‘integrity’ attribute」と出る。正規の更新やバージョン違いのほか、改ざんの可能性もある。配布元の正規リリースと内容を確認し、承認したファイルについてだけハッシュを更新する。
- **CORSのエラー**：`crossorigin`を付けたのに配信側が`Access-Control-Allow-Origin`を返していない。配信側の設定を直すか、自社サーバーに置く。
- **実行順のエラー**：「`$ is not defined`」のように、依存するライブラリより先に実行されている。依存元と利用側を両方とも外部ファイルにして`defer`にそろえ（`async`は外す）、記述順を依存の順に並べる。

## よくある質問

### 外部スクリプトはheadとbodyの末尾のどちらに書きますか？

`defer`を付けて`head`に書くのが現在の定石です。ダウンロードがHTMLの解析と並行して始まり、実行は解析の完了後になるため、`body`の末尾に置くより早く取得を始められます。属性を付けない場合だけ、描画を止めないよう`body`の末尾に置きます。

### インラインスクリプトとは何ですか？

HTMLの`<script>`タグの中に直接JavaScriptを書く方式で、内部スクリプトとも呼ばれます。スクリプトを制限するCSPでは、明示的に許可しない限り実行が止まります。許可にはnonceかhashを推奨します。

### CSPのnonceとは何ですか？

「一度だけ使う乱数」のことです。サーバーがリクエストごとに推測できない値を作り、CSPヘッダーとscriptタグの`nonce`属性に同じ値を入れると、そのタグだけが実行を許可されます。値を使い回すと攻撃者に推測されるため、毎回作り直します。

### SRIとCSPはどちらを使えばよいですか？

役割が違うので併用します。CSPは「どこから読んだスクリプトを実行してよいか」を決め、SRIは「読んだファイルの中身が想定どおりか」を確かめます。許可したCDNのファイルが差し替えられた場合、CSPだけでは止まらず、SRIがあれば止まります。

### WordPressで外部スクリプトにdeferを付けるには？

WordPress 6.3以降は、`wp_enqueue_script()`の第5引数に`array( 'strategy' => 'defer' )`で遅延実行を指定できます。ただし、実際の属性は依存関係などを考慮して決定され、指定どおりにならない場合があります。`'async'`も指定できます。テーマのテンプレートに`<script>`タグを直書きするより、依存関係も含めてWordPressに管理させるほうが安全です。

## 関連記事

- [コンテンツセキュリティポリシー（CSP）とは？nonce方式の設定とXSS防御の実装判断を解説](/tech/details/16358/)
- [jQuery 4.0とは｜4.0.0の変更点・CDNとSRI・3系からの移行手順](/tech/details/10659/)
- [PhotoGoodsの不正アクセスとカード情報漏えい｜決済画面の改ざんとアップロード機能の締め方](/tech/details/18205/)
- [XSSとCSRFの違いとは？仕組み・被害・対策を実装レベルで徹底比較](/tech/details/4109/)
- [iframeとは？sandbox属性14種とCookie分割の実装判断を解説](/tech/details/17529/)

---

出典: [外部スクリプトとは？script srcの読み込み方とSRI・CSPで安全に使う方法](<https://www.issoh.co.jp/tech/details/1465/>)（株式会社一創）
