
## この記事でわかること

- なぜ `'unsafe-inline'` で CSP の XSS 防御効果が大きく落ちるか
- WordPress / 古いテーマで `'unsafe-inline'` が必要になりがちな理由
- 撤去への 4 ステップ移行（棚卸し → 外部ファイル化 → nonce → 撤去）
- 段階移行中の運用ルール

## `'unsafe-inline'` を入れると CSP の主目的が無力化する

CSP（[Content-Security-Policy とは](/blog/csp-content-security-policy-toha)）の最大の目的は **XSS で注入された JavaScript の実行をブロックすること**です。

ところが script-src に `'unsafe-inline'` を含めると、HTML 本文に直書きされたインラインスクリプトの実行を全面許可することになります。攻撃者が XSS 脆弱性を悪用して `<script>...</script>` を注入した場合、ブラウザはそれを CSP の許可下と見なして実行してしまいます。

![なぜ unsafe-inline で XSS 防御効果が落ちるか](/blog/csp-unsafe-inline-mondai/why-dangerous.svg)

CSP を「導入した状態」にはできても、防御の本丸が機能しない状態。`securityheaders.com` 等で grade A になっていても、実態は半開きのドアです。

## なぜ `'unsafe-inline'` が必要になるのか

Web サイトで CSP を導入しようとすると、ほぼ必ず壁になります。

- **WordPress / 古いテーマ**: `<script>jQuery(...)</script>` のようなインラインスクリプトがテーマや管理画面に多数存在
- **タグマネージャ**: GTM が動的にインラインを差し込むケース
- **広告 SDK**: `<script>` タグで埋め込み、内部でインラインを生成
- **メルマガ / フォーム埋め込みコード**: 「コピペするだけ」で動かすため `<script>` をそのまま貼る形式

これらをすべて外部ファイル化するのは、現場では数週間から数ヶ月かかります。だから多くのサイトが `'unsafe-inline'` を入れたまま放置されます。

## 4 ステップで段階的に撤去する

事故を出さず、運用を止めず、計画的に撤去する手順です。

![unsafe-inline 撤去への 4 ステップ移行](/blog/csp-unsafe-inline-mondai/migration-path.svg)

### ステップ 1: インラインスクリプトの棚卸し

CSP を Report-Only モードで設定し、レポートで全インラインを列挙します。

```
Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self';
  report-uri /csp-report
```

レポートには「どのページの何行目で違反が出たか」が記録されます。2〜4 週間で全ページの全インラインがほぼ拾えます。

### ステップ 2: 外部ファイル化

棚卸しで見つかったインラインスクリプトを `.js` ファイルに切り出します。

```html
<!-- 移行前 -->
<script>
  document.querySelector('#cta').addEventListener('click', () => {
    gtag('event', 'cta_click');
  });
</script>

<!-- 移行後 -->
<script src="/static/cta-tracking.js"></script>
```

WordPress なら `wp_enqueue_script()` でテーマに読み込ませる形に。古いテーマで難しい場合はステップ 3 へ。

### ステップ 3: 残ったインラインに nonce / hash を付与

どうしても外部ファイル化できないインラインには nonce（サーバー生成のランダム値）を付けます。

```php
<?php $nonce = bin2hex(random_bytes(16)); ?>
<script nonce="<?php echo $nonce; ?>">
  // 一時的に必要なインライン
</script>
```

CSP ヘッダ側でも同じ nonce を指定:

```
Content-Security-Policy:
  script-src 'self' 'nonce-{{nonce}}';
```

これで「**サーバーが知っている nonce 付きのスクリプトだけ**」実行が許可されます。攻撃者が nonce を予測できないため、XSS 注入は引き続きブロックされます。

### ステップ 4: `'unsafe-inline'` を撤去

ヘッダから `'unsafe-inline'` を削除し、Enforce 切り替え。

```
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{nonce}}' https://www.googletagmanager.com;
  ...
```

ここで初めて XSS 防御が機能する状態になります。

## 段階移行中の運用ルール

### Report-Only と Enforce を並走させる

- **Report-Only**: 厳しい新ポリシー（`'unsafe-inline'` なし）で違反観察
- **Enforce**: 緩い旧ポリシー（`'unsafe-inline'` 入り）で本番運用

ヘッダを 2 つ並べることで、本番影響なしにポリシー検証ができます。

```
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-XXX'
```

### 違反レポートは本物の攻撃と区別する

レポートに出てくる違反のうち、

- **見覚えのある自社 SDK / CDN**: 許可リストに追加して整理
- **見覚えのない出所**: 攻撃 / 不要なタグ / 古いキャンペーンの遺物 → 調査して撤去

の仕分けをします。

### 終わらない覚悟もしておく

WordPress + 多数のプラグインを使っている場合、全インラインを撤去するのは現実的ではないこともあります。その場合は:

- **管理画面（/wp-admin/）以外**で `'unsafe-inline'` を撤去
- **公開ページに限定**して厳しい CSP を適用
- **段階的に削減**（90% 撤去で止めても、放置よりは遥かに良い）

完璧主義より「できるところから着手する」が現実解です。

## まとめ

- `'unsafe-inline'` は CSP を「導入した状態」にできるが、XSS 防御の本丸が機能しない
- 4 ステップ（棚卸し → 外部ファイル化 → nonce → 撤去）で段階移行
- Report-Only と Enforce のヘッダ並走で本番影響なしに検証
- 完璧主義より、撤去率を毎月上げる漸進的アプローチが現実的

## まずは現状を把握しましょう

自社サイトの CSP に `'unsafe-inline'` が含まれているかは、ドメイン番人の [Web セキュリティヘッダ 単発チェック](/security-headers/check) で 30 秒で確認できます。

CSP の段階移行（Report-Only モード立ち上げ → 違反レポート精査 → nonce 化 → Enforce 切り替え）のスポット支援は [Web セキュリティヘッダ診断＋設定支援](/contact)（3 万円〜）でご相談いただけます。判断に迷う場合は [お問い合わせ](/contact) からどうぞ。

CSP の基礎は [CSP（Content-Security-Policy）とは？Web 担当者のための入門](/blog/csp-content-security-policy-toha) を、ブラウザ API（カメラ / マイク / 位置情報 / Payment Request など）を制限する [permissions-policy で API を制限する設定](/blog/permissions-policy-toha) も合わせて検討してください。メール認証や SSL も含めた総合点検は [無料のドメイン診断](/diagnose) をご利用ください。
