Connection-Allowlist HTTP レスポンス ヘッダーは、ドキュメントまたはワーカーが明示的なネットワーク エンドポイントのリストとのみ通信するように制限します。コンテンツ セキュリティ ポリシー(CSP)では、リソースごとに個別のディレクティブ(connect-src、img-src、script-src、font-src、form-action)を維持する必要があり、<link rel="dns-prefetch">、<link rel="preconnect">、WebRTC などの投機的または非 Fetch ネットワーク チャネルは管理されません。これとは異なり、Connection-Allowlist では、コンテキスト(fetch()、XMLHttpRequest、サブリソース、navigator.sendBeacon()、<a ping>、トップレベルとフレームのナビゲーション、リソースヒント、投機ルール、WebSocket、WebTransport、WebRTC)によって開始されるすべてのアウトバウンド接続に、単一のネットワーク レイヤの許可リストが適用されます。
導入方法
- 許可リストを RFC 9651 内部リスト
(...)としてシリアル化する:Connection-Allowlist(またはConnection-Allowlist-Report-Only)を、スペース区切りのアイテムの単一の括弧付きリストとしてフォーマットします。ドキュメントまたはワーカー独自のオリジンを許可するには、引用符で囲まれていないトークンresponse-originを含めます。その後に、コンテキストが接続または移動するすべての正当な外部エンドポイント(API、画像 / フォント CDN、アウトバウンド<a href>、<form action>、OAuth、支払いリダイレクト ターゲット)を、二重引用符で囲まれたURLPattern文字列として指定します。構成またはURLオブジェクトからパターンを動的に構築する場合は、ws:/wss:をhttp:/https:に正規化し、オリジンのみの URL の末尾のスラッシュを削除します(new URL('https://api.example.com').hrefは/を追加するため、URLPatternの一致はルートパス/のみに制限されます)。 Sec-Fetch-Destでミドルウェアをスコープ設定し、ワーカーをカバーする: ブラウザは、ドキュメント / ナビゲーション レスポンスとワーカー スクリプト レスポンス(Sec-Fetch-Dest値document、iframe、worker、sharedworker、serviceworker)でのみConnection-Allowlistを尊重します。Sec-Fetch-Destでヘッダーを変更する場合は、共有キャッシュがヘッダーのないレスポンスを返さないようにVary: Sec-Fetch-Destを含めます。専用ワーカー スクリプトと共有ワーカー スクリプトで一致する許可リストを提供し、不正使用されたページ スクリプトがworker.postMessage()を介してデータを引き出せないようにします。Service Worker は、キャッシュミスとナビゲーション プリロードで独自のネットワーク フェッチを実行するため、制御するすべてのドキュメントの許可リストのスーパーセットとしてConnection-Allowlistを構成します。- 違反レポートと段階的または二重のロールアウトを構成する:
Reporting-Endpointsヘッダーを定義し、閉じ括弧)の後に引用符なしの; report-to=<endpoint-name>パラメータを追加します。初期ロールアウト時にConnection-Allowlist-Report-Onlyのみを送信することも、同じレスポンスでConnection-Allowlist(ベースラインの許可リストを適用)とConnection-Allowlist-Report-Only(より厳格な候補許可リストを監査)の両方を送信することもできます。違反レポートは、"type": "connection-allowlist"と本文{ url, connection, allowlist, disposition }で配信されます(connectionはブロックされたリンク先 URL または"webrtc"、dispositionは"enforce"または"report")。 - デフォルトのリダイレクトと WebRTC のブロックを考慮し、クライアント エラーを処理する: デフォルトでは、
Connection-Allowlistはすべての3xxHTTP リダイレクト(redirects=block、許可リストに登録された URL 間または同一オリジン URL 間でリダイレクトする場合でも)とすべてのRTCPeerConnectionトラフィック(webrtc=block)をブロックします。信頼できるエンドポイントでサーバーサイドのリダイレクトが必要な場合にのみ; redirects=allowを追加します(最初のリクエストは許可リストと一致する必要があります)。WebRTC が必要な場合は; webrtc=allowを追加します。クライアントで、送信fetch()呼び出しをtry...catchでラップし、WebSocketerrorイベントを処理します。これにより、TypeError(net::ERR_NETWORK_ACCESS_REVOKED)で拒否されたリクエストは、未処理の拒否を引き起こすのではなく、処理済みのエラー状態を返し、UI ステータスを更新します。
コード例
Vary: Sec-Fetch-Dest
Reporting-Endpoints: conn-reports="https://app.example.com/api/security-reports"
Connection-Allowlist: (response-origin "https://api.example.com" "https://realtime.example.com" "https://*.cdn.example.com/*"); report-to=conn-reports
Connection-Allowlist-Report-Only: (response-origin "https://api.example.com/v2/*"); report-to=conn-reports
const ALLOWLIST_DESTS = new Set(['document', 'iframe', 'worker', 'sharedworker', 'serviceworker']);
// Normalize a config URL or URLPattern string for Connection-Allowlist:
// - Rewrites ws:// and wss:// to http:// and https:// (WebSocket matching rule).
// - Strips a bare trailing slash on origin-only URLs (e.g. from new URL(...).href)
// so all subpaths on the origin are permitted instead of only "/".
export function normalizeAllowlistPattern(rawPattern) {
const trimmed = String(rawPattern).trim().replace(/^ws(s?):\/\//i, 'http$1://');
if (!/^https?:\/\//i.test(trimmed)) {
throw new Error(`Pattern must include an http:// or https:// scheme: ${rawPattern}`);
}
if (/[\s,]/.test(trimmed)) {
throw new Error(`Pattern must be a single URLPattern without spaces or commas: ${rawPattern}`);
}
if (/[()]/.test(trimmed)) {
throw new Error(`Custom regex groups are rejected by Connection-Allowlist: ${rawPattern}`);
}
return trimmed.replace(/^(https?:\/\/[^/?#]+)\/$/i, '$1');
}
// Build an RFC 9651 Inner List value for Connection-Allowlist or Connection-Allowlist-Report-Only.
export function buildConnectionAllowlistHeader({
allowSelf = true,
patterns = [],
reportTo,
redirects = 'block',
webrtc = 'block',
} = {}) {
const items = allowSelf ? ['response-origin'] : [];
for (const pattern of patterns) {
const raw = String(pattern).trim();
if (raw === 'response-origin') {
if (!items.includes('response-origin')) items.push('response-origin');
continue;
}
const normalized = normalizeAllowlistPattern(raw);
items.push(`"${normalized.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`);
}
let header = `(${items.join(' ')})`;
if (redirects === 'allow') header += '; redirects=allow';
if (webrtc === 'allow') header += '; webrtc=allow';
if (reportTo) {
const endpointToken = String(reportTo).trim().replace(/^"+|"+$/g, '');
header += `; report-to=${endpointToken}`;
}
return header;
}
// Server or middleware helper applying Connection-Allowlist and Reporting-Endpoints
// on document and worker responses.
export function setConnectionAllowlistHeaders(res, options = {}) {
const {
secFetchDest = 'document',
patterns = ['https://api.example.com', 'https://realtime.example.com', 'https://*.cdn.example.com/*'],
reportOnlyPatterns,
reportEndpointName = 'conn-reports',
reportEndpointUrl = 'https://app.example.com/api/security-reports',
redirects = 'block',
webrtc = 'block',
reportOnly = false,
} = options;
const setHeader = (name, value) =>
typeof res.setHeader === 'function' ? res.setHeader(name, value) : res.headers.set(name, value);
if (typeof res.appendHeader === 'function') {
res.appendHeader('Vary', 'Sec-Fetch-Dest');
} else if (typeof res.headers?.append === 'function') {
res.headers.append('Vary', 'Sec-Fetch-Dest');
} else {
setHeader('Vary', 'Sec-Fetch-Dest');
}
if (secFetchDest && !ALLOWLIST_DESTS.has(String(secFetchDest).toLowerCase())) return;
setHeader('Reporting-Endpoints', `${reportEndpointName}="${reportEndpointUrl}"`);
setHeader(
reportOnly ? 'Connection-Allowlist-Report-Only' : 'Connection-Allowlist',
buildConnectionAllowlistHeader({ allowSelf: true, patterns, reportTo: reportEndpointName, redirects, webrtc }),
);
if (!reportOnly && Array.isArray(reportOnlyPatterns)) {
setHeader(
'Connection-Allowlist-Report-Only',
buildConnectionAllowlistHeader({
allowSelf: true,
patterns: reportOnlyPatterns,
reportTo: reportEndpointName,
redirects,
webrtc,
}),
);
}
}
// Client-side helper that catches blocked outbound requests gracefully without unhandled rejections.
export async function fetchWithPolicyHandling(url, { statusElement, ...fetchOptions } = {}) {
try {
const response = await fetch(url, fetchOptions);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const body = await response.text();
if (statusElement) {
statusElement.textContent = `Allowed: Loaded ${url} (${body.length} bytes).`;
}
return { ok: true, body, error: null };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (statusElement) {
statusElement.textContent = `Blocked: Request to ${url} was rejected by the outbound connection policy (${message}).`;
}
return { ok: false, body: null, error };
}
}
ベスト プラクティス
- 推奨:
Connection-Allowlistは、スペース区切りのアイテムの単一の RFC 9651 内部リスト(...)としてフォーマットします。空でないConnection-Allowlistヘッダーが最上位の構造化フィールドの解析に失敗した場合((...)を省略した場合や、一重引用符'...'を使用した場合など)、ブラウザは空の許可リストでフェイルクローズし、すべてのアウトバウンド ネットワーク接続をブロックします。読み込み後にすべてのネットワーク アクセスを意図的に取り消す場合は、Connection-Allowlist: ()を使用します(ローカル スキームdata:、blob:、about:、filesystem:は引き続き許可されます)。 - 推奨: オリジン全体の URL パターン(
"https://api.example.com")の末尾のスラッシュを省略するか、明示的なワイルドカード("https://api.example.com/*")を使用して、/v1/usersなどのサブパスを許可します。パターンをシリアル化する前に、new URL(origin).hrefによって追加された末尾のスラッシュを削除します。 Connection-Allowlistで"http://..."と"https://..."のパターンを使用して、WebSocket(ws://とwss://)のエンドポイントを許可リストに登録してください。ブラウザは、照合前に WebSocket スキームを HTTP / HTTPS に正規化するためです。- 制限付きドキュメントの許可リストに、想定される外部ナビゲーション ターゲット(
<a href>、<form action>、OAuth / SSO プロバイダ、支払いチェックアウト)を含める。デフォルトのredirects=blockでは、; redirects=allowが指定されていない限り、同一オリジンの3xxリダイレクトもブロックされることに注意してください。 response-originまたは内部リストのパラメータ値(; report-to=conn-reports、; redirects=allow、; webrtc=allow)を引用符で囲んだり、(...)内の項目をカンマで区切ったりしないでください。- URL パターン内で
(\d+)などのカスタム正規表現グループや"/api/*"などの相対パスは使用しないでください。ブラウザのネットワーク サービス パーサーは、スキームとホスト名のない正規表現グループとパターンを拒否します。 <iframe connectionAllowlist="...">属性またはAllow-Connection-Allowlist-From埋め込み型適用ヘッダーは試験運用版であり、埋め込み型適用は出荷されるブラウザでデフォルトで有効になっていないため、本番環境ではまだ使用しないでください。
フォールバック戦略
Connection-Allowlist は HTTP レスポンス ヘッダーを介してブラウザのネットワーク層で適用されるため、クライアントサイドの JavaScript でポリフィルすることはできません。ベースライン ターゲットに Connection-Allowlist をまだサポートしていないブラウザが含まれている場合は、Connection-Allowlist と Content-Security-Policy(CSP)ヘッダーをペアにします(HTTP レスポンス ヘッダーを構成できない場合は、ドキュメント レベルのフェッチとサブリソースの制限に <meta http-equiv="Content-Security-Policy"> 要素を使用します)。
- 多層防御の追加: 対応ブラウザでは、両方のポリシーが同時に適用されます。
Connection-Allowlistは CSP 以外のデータ漏洩チャネル(dns-prefetch、preconnect、WebRTC、リダイレクト)をブロックし、CSP はConnection-Allowlistのないブラウザでfetch()、サブリソース、<form action>ナビゲーションを制限します。 form-actionとworker-srcを明示的にカバーする: CSP では、<form action>の送信に対してdefault-srcはフォールバックしません。常にform-actionを明示的に含めてください。worker-src 'self'(blob:を除く)を含めて、制限付きのページがblob:SharedWorker を生成してドキュメント レベルの制限を回避できないようにします。- 許可されたオリジンをリソースごとの CSP ディレクティブにマッピングする:
Connection-Allowlist(単一のエンドポイント リストを使用し、インライン マークアップを管理しない)とは異なり、CSP のdefault-src 'self'は各リソースタイプ(connect-src、img-src、style-src、font-src、script-src)を制限し、ハッシュまたはノンスを介して認可されていない限り、インライン<style>ブロックと<script>ブロックをブロックします。 - CSP
connect-srcにwss://を含める:Connection-Allowlistは"https://..."パターンに対してwss://接続を照合しますが、クロスブラウザ環境の CSPconnect-srcでは、WebSocket が使用されている場合にwss://のオリジンを明示的にリストする必要があります。
Content-Security-Policy: default-src 'self'; connect-src 'self' https://api.example.com https://realtime.example.com wss://realtime.example.com; img-src 'self' https://*.cdn.example.com data: blob:; form-action 'self'; worker-src 'self'; object-src 'none'; base-uri 'self'; report-to conn-reports
<!-- Document-level fallback when HTTP response headers cannot be set on static hosting -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src 'self' https://api.example.com https://realtime.example.com wss://realtime.example.com; img-src 'self' https://*.cdn.example.com data: blob:; form-action 'self'; worker-src 'self'; object-src 'none'; base-uri 'self'"
/>