MDN HTTP Observatory のスキャン結果(scan id 120446917 / algorithm v6 / 2026-09-11)で grade C- / score 45(12 件中 4 件 fail) だった。Worker スクリプトを持たない構成のため、対応手段は public/_headers に限られる。その範囲で直せるものを整理する。
現状のスキャン結果
| テスト |
結果 |
増減 |
_headers で対応可能 |
| content-security-policy |
❌ csp-not-implemented |
-25 |
✅ |
| x-frame-options |
❌ x-frame-options-not-implemented |
-20 |
✅ |
| x-content-type-options |
❌ x-content-type-options-not-implemented |
-5 |
✅ |
| subresource-integrity |
❌ sri-not-implemented-but-external-scripts-loaded-securely |
-5 |
❌(HTML 側の変更、かつ実質対応不可) |
| referrer-policy |
⚪ 未設定(減点なし・加点機会あり) |
0 → +5 |
✅ |
| cross-origin-opener-policy |
⚪ 未設定(減点なし・加点機会あり) |
0 → +10 |
✅ |
| cross-origin-resource-policy |
⚪ 未設定(減点なし・加点機会あり) |
0 → +10 |
✅ |
| strict-transport-security |
✅ hsts-preloaded |
+5 |
— |
| redirection |
✅ redirection-all-redirects-preloaded |
0 |
— |
| cookies / cross-origin-resource-sharing / cross-origin-embedder-policy |
✅ |
0 |
— |
HSTS と redirection は .dev TLD 自体が HSTS preload list に載っているため既にパスしている。http://newt239.dev/ は現状 200 を返す(リダイレクトしない)が、preload により実ブラウザは HTTP で到達しないのでスコア上は問題にならない。
やること
1. 追加するヘッダ(public/_headers の /*)
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-site
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options と Referrer-Policy は副作用がない
Cross-Origin-Opener-Policy: same-origin は window.open 先との参照を切る。外部リンクは target="_blank" のみで opener に依存していないため影響なし
Cross-Origin-Resource-Policy は OG 画像など他サイトからの埋め込みを制限する。クローラ(X / Slack など)はブラウザではないので CORP を解釈せず OG 画像には影響しないが、same-origin ではなく same-site を選んで影響範囲を狭める
Strict-Transport-Security はスコアには効かない(既に preload で加点済み)が、preload list への依存を明示的に外す意味で入れる
2. CSP の導入
減点幅が最大(-25)かつ x-frame-options-implemented-via-csp(+5)も同時に取れる。ただしインラインスクリプト・インラインスタイルの扱いで到達できる水準が変わる。
段階 A: 'unsafe-inline' 込みで導入する(-25 → -20、XFO は -20 → +5)
Content-Security-Policy: default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; manifest-src 'self'; script-src 'self' 'unsafe-inline' https://use.typekit.net https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://use.typekit.net https://p.typekit.net; font-src 'self' https://use.typekit.net https://p.typekit.net; img-src 'self' data: https://img.newt239.dev https://i.scdn.co https://www.googletagmanager.com https://*.google-analytics.com; connect-src 'self' https://api.newt239.dev https://p.typekit.net https://www.googletagmanager.com https://*.google-analytics.com https://*.analytics.google.com
許可オリジンの根拠:
段階 B: script-src から 'unsafe-inline' を外す(-20 → 0、default-src 'none' 込みなら +10)
style-src だけの 'unsafe-inline' は Observatory の満点扱い(csp-implemented-with-unsafe-inline-in-style-src-only)なので、スタイル側はそのままでよい。script 側に必要なのは次の 2 つ。
nuxt.config.ts の onload="Typekit.load({async:true})" というインラインイベントハンドラを外す。ハンドラ属性は hash では許可できず 'unsafe-hashes' が必要になるため、クライアントプラグインからの読み込みへ移す
- プリレンダ HTML に残るインラインスクリプト 3 種(
<script type="importmap">、window.__NUXT_SITE_CONFIG__、window.__NUXT__.config)を hash で許可する。いずれもページ間で同一なので、ビルド後に .output/public から sha256 を計算して _headers を生成するスクリプトで賄える。<script type="application/ld+json"> は CSP の対象外
段階 B はビルド生成物への依存が生まれるので、段階 A を先に入れて実害がないことを確認してから着手する。
3. 対応しないもの
- subresource-integrity (-5): 外部スクリプトは Typekit の kit JS と gtag.js の 2 本。どちらも配信側が内容を更新するため
integrity を固定するとサイトが壊れる。_headers の範囲外でもあり対応しない
見込み
段階 A まで(1 + 2A)で score 45 → 90 前後、段階 B まで実施すると 100 / A+ に届く。
検証
- ローカルで
pnpm run generate → pnpm run serve:static し、CSP 違反が出ないことを DevTools の Console で確認する
- 本番反映後に Observatory を再スキャンして grade を確認する
- CSP は最初
Content-Security-Policy-Report-Only で入れて違反の有無を見てから昇格させてもよい
MDN HTTP Observatory のスキャン結果(scan id
120446917/ algorithm v6 / 2026-09-11)で grade C- / score 45(12 件中 4 件 fail) だった。Worker スクリプトを持たない構成のため、対応手段は public/_headers に限られる。その範囲で直せるものを整理する。現状のスキャン結果
_headersで対応可能csp-not-implementedx-frame-options-not-implementedx-content-type-options-not-implementedsri-not-implemented-but-external-scripts-loaded-securelyhsts-preloadedredirection-all-redirects-preloadedHSTS と redirection は
.devTLD 自体が HSTS preload list に載っているため既にパスしている。http://newt239.dev/は現状 200 を返す(リダイレクトしない)が、preload により実ブラウザは HTTP で到達しないのでスコア上は問題にならない。やること
1. 追加するヘッダ(
public/_headersの/*)X-Content-Type-OptionsとReferrer-Policyは副作用がないCross-Origin-Opener-Policy: same-originはwindow.open先との参照を切る。外部リンクはtarget="_blank"のみで opener に依存していないため影響なしCross-Origin-Resource-Policyは OG 画像など他サイトからの埋め込みを制限する。クローラ(X / Slack など)はブラウザではないので CORP を解釈せず OG 画像には影響しないが、same-originではなくsame-siteを選んで影響範囲を狭めるStrict-Transport-Securityはスコアには効かない(既に preload で加点済み)が、preload list への依存を明示的に外す意味で入れる2. CSP の導入
減点幅が最大(-25)かつ
x-frame-options-implemented-via-csp(+5)も同時に取れる。ただしインラインスクリプト・インラインスタイルの扱いで到達できる水準が変わる。段階 A:
'unsafe-inline'込みで導入する(-25 → -20、XFO は -20 → +5)許可オリジンの根拠:
use.typekit.net/p.typekit.net— nuxt.config.ts の Typekitwww.googletagmanager.com/*.google-analytics.com/*.analytics.google.com— plugins/vue-gtag.client.tsapi.newt239.dev— components/ThemeChanger.vue のテーマ生成と components/MyTopTrackList.vueimg.newt239.dev— content の Markdown 中の画像i.scdn.co—api.newt239.dev/spotify/my-top-tracksが返すサムネイル段階 B:
script-srcから'unsafe-inline'を外す(-20 → 0、default-src 'none'込みなら +10)style-srcだけの'unsafe-inline'は Observatory の満点扱い(csp-implemented-with-unsafe-inline-in-style-src-only)なので、スタイル側はそのままでよい。script 側に必要なのは次の 2 つ。nuxt.config.tsのonload="Typekit.load({async:true})"というインラインイベントハンドラを外す。ハンドラ属性は hash では許可できず'unsafe-hashes'が必要になるため、クライアントプラグインからの読み込みへ移す<script type="importmap">、window.__NUXT_SITE_CONFIG__、window.__NUXT__.config)を hash で許可する。いずれもページ間で同一なので、ビルド後に.output/publicから sha256 を計算して_headersを生成するスクリプトで賄える。<script type="application/ld+json">は CSP の対象外段階 B はビルド生成物への依存が生まれるので、段階 A を先に入れて実害がないことを確認してから着手する。
3. 対応しないもの
integrityを固定するとサイトが壊れる。_headersの範囲外でもあり対応しない見込み
段階 A まで(1 + 2A)で score 45 → 90 前後、段階 B まで実施すると 100 / A+ に届く。
検証
pnpm run generate→pnpm run serve:staticし、CSP 違反が出ないことを DevTools の Console で確認するContent-Security-Policy-Report-Onlyで入れて違反の有無を見てから昇格させてもよい