Header respons HTTP Connection-Allowlist membatasi dokumen atau worker agar hanya berkomunikasi dengan daftar endpoint jaringan yang eksplisit. Tidak seperti Kebijakan Keamanan Konten (CSP)—yang memerlukan pemeliharaan perintah per resource yang terpisah (connect-src, img-src, script-src, font-src, form-action) dan tidak mengatur saluran jaringan spekulatif atau non-Fetch seperti <link rel="dns-prefetch">, <link rel="preconnect">, atau WebRTC—Connection-Allowlist menerapkan daftar yang diizinkan lapisan jaringan tunggal di semua koneksi keluar yang dimulai oleh konteks (fetch(), XMLHttpRequest, subresource, navigator.sendBeacon(), <a ping>, navigasi tingkat atas dan frame, petunjuk resource, Aturan Spekulasi, WebSocket, WebTransport, dan WebRTC).
Cara menerapkan
- Serialisasikan daftar yang diizinkan sebagai Daftar Dalam RFC 9651
(...): FormatConnection-Allowlist(atauConnection-Allowlist-Report-Only) sebagai daftar item yang dipisahkan spasi dalam satu tanda kurung. Sertakan token tanpa tanda petikresponse-originuntuk mengizinkan origin dokumen atau worker itu sendiri, diikuti dengan stringURLPatterndalam tanda petik ganda untuk setiap endpoint eksternal yang sah yang terhubung atau dituju oleh konteks (API, CDN gambar/font, dan target pengalihan<a href>,<form action>, OAuth, atau pembayaran keluar). Saat membuat pola secara dinamis dari objek konfigurasi atauURL, normalkanws:/wss:menjadihttp:/https:dan hapus garis miring di akhir URL khusus origin (karenanew URL('https://api.example.com').hrefmenambahkan/, yang membatasi pencocokanURLPatternhanya ke jalur root/). - Cakupan middleware menurut
Sec-Fetch-Destdan mencakup worker: Browser hanya mematuhiConnection-Allowlistpada respons dokumen/navigasi dan respons skrip worker (nilaiSec-Fetch-Destdocument,iframe,worker,sharedworker, danserviceworker). Saat memvariasikan header menurutSec-Fetch-Dest, sertakanVary: Sec-Fetch-Destagar cache bersama tidak menyajikan respons tanpa header. Menyajikan daftar yang diizinkan yang cocok di skrip Dedicated dan Shared Worker sehingga skrip halaman yang disusupi tidak dapat mengeksfiltrasi data melaluiworker.postMessage(). Karena Service Worker melakukan pengambilan jaringan sendiri pada cache tidak ditemukan dan pramuat navigasi, konfigurasikanConnection-Allowlist-nya sebagai superset dari daftar yang diizinkan untuk semua dokumen yang dikontrolnya. - Konfigurasikan pelaporan pelanggaran dan peluncuran bertahap atau ganda: Tentukan header
Reporting-Endpointsdan tambahkan parameter; report-to=<endpoint-name>tanpa tanda petik setelah penutup). Anda dapat mengirimConnection-Allowlist-Report-Onlysaja selama peluncuran awal, atau mengirimConnection-Allowlist(menerapkan daftar yang diizinkan dasar) danConnection-Allowlist-Report-Only(mengaudit daftar yang diizinkan kandidat yang lebih ketat) pada respons yang sama. Laporan pelanggaran dikirim dengan"type": "connection-allowlist"dan isi{ url, connection, allowlist, disposition }(denganconnectionadalah URL tujuan yang diblokir atau"webrtc", dandispositionadalah"enforce"atau"report"). - Perhitungkan pengalihan default dan pemblokiran WebRTC, serta tangani error klien: Secara default,
Connection-Allowlistmemblokir semua pengalihan HTTP3xx(redirects=block, bahkan saat mengalihkan antara URL yang diizinkan atau URL origin yang sama) dan semua trafficRTCPeerConnection(webrtc=block). Tambahkan; redirects=allowhanya jika endpoint tepercaya memerlukan pengalihan sisi server (permintaan awal harus tetap cocok dengan daftar yang diizinkan), atau; webrtc=allowjika WebRTC diperlukan. Di klien, gabungkan panggilanfetch()keluar ditry...catchdan tangani peristiwaWebSocketerrorsehingga permintaan yang ditolak denganTypeError(net::ERR_NETWORK_ACCESS_REVOKED) menampilkan status error yang ditangani dan memperbarui status UI, bukan menyebabkan penolakan yang tidak tertangani.
Kode contoh
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 };
}
}
Praktik terbaik
- LAKUKAN pemformatan
Connection-Allowlistsebagai satu Inner List RFC 9651(...)yang berisi item yang dipisahkan spasi. Jika headerConnection-Allowlistyang tidak kosong gagal mengurai Structured Field tingkat teratas (misalnya, menghilangkan(...)atau menggunakan tanda petik tunggal'...'), browser akan gagal ditutup dengan daftar yang diizinkan yang kosong dan memblokir semua koneksi jaringan keluar. GunakanConnection-Allowlist: ()jika Anda sengaja ingin mencabut semua akses jaringan setelah pemuatan (skema lokaldata:,blob:,about:, danfilesystem:tetap diizinkan). - LAKUKAN penghapusan garis miring di akhir pada pola URL seluruh origin (
"https://api.example.com") atau gunakan karakter pengganti eksplisit ("https://api.example.com/*") sehingga subjalur seperti/v1/usersdiizinkan. Hapus garis miring di akhir yang ditambahkan olehnew URL(origin).hrefsebelum membuat serialisasi pola. - LAKUKAN pemberian izin endpoint
WebSocket(ws://danwss://) menggunakan pola"http://..."dan"https://..."diConnection-Allowlist, karena browser menormalisasi skema WebSocket ke HTTP/HTTPS sebelum pencocokan. - LAKUKAN penyertaan target navigasi eksternal yang diharapkan (
<a href>,<form action>, penyedia OAuth/SSO, checkout pembayaran) dalam daftar yang diizinkan untuk dokumen yang dibatasi, dan ingat bahwaredirects=blockdefault memblokir pengalihan3xxbahkan yang berasal dari origin yang sama, kecuali jika; redirects=allowditentukan. - JANGAN mengapit
response-originatau parameter value daftar dalam (; report-to=conn-reports,; redirects=allow,; webrtc=allow) dalam tanda kutip, atau memisahkan item di dalam(...)dengan koma. - JANGAN menggunakan grup ekspresi reguler kustom seperti
(\d+)atau jalur relatif seperti"/api/*"di dalam pola URL; parser layanan jaringan browser akan menolak grup dan pola ekspresi reguler tanpa skema dan nama host. - JANGAN mengandalkan atribut
<iframe connectionAllowlist="...">eksperimental atau header penerapan tersematAllow-Connection-Allowlist-Fromdalam produksi, karena penerapan tersemat tidak diaktifkan secara default di browser pengiriman.
Strategi penggantian
Karena Connection-Allowlist diterapkan di lapisan jaringan browser melalui header respons HTTP, fitur ini tidak dapat di-polyfill di JavaScript sisi klien. Jika target Dasar Pengukuran Anda mencakup browser yang belum mendukung Connection-Allowlist, pasangkan Connection-Allowlist dengan header Content-Security-Policy (CSP) (dan elemen <meta http-equiv="Content-Security-Policy"> untuk pembatasan pengambilan dan subresource tingkat dokumen saat header respons HTTP tidak dapat dikonfigurasi):
- Defense-in-depth tambahan: Browser pendukung menerapkan kedua kebijakan secara bersamaan—
Connection-Allowlistmemblokir saluran pemindahan data non-CSP (dns-prefetch,preconnect, WebRTC, dan pengalihan), sedangkan CSP membatasifetch(), subresource, dan navigasi<form action>di browser tanpaConnection-Allowlist. - Sertakan
form-actiondanworker-srcsecara eksplisit: Di CSP,default-srctidak di-fallback untuk pengiriman<form action>; selalu sertakanform-actionsecara eksplisit. Sertakanworker-src 'self'(tidak termasukblob:) sehingga halaman yang dibatasi tidak dapat menghasilkan SharedWorkerblob:untuk mengabaikan batasan tingkat dokumen. - Memetakan origin yang diizinkan ke perintah CSP per resource: Tidak seperti
Connection-Allowlist(yang menggunakan satu daftar endpoint dan tidak mengatur markup inline),default-src 'self'di CSP membatasi setiap jenis resource (connect-src,img-src,style-src,font-src,script-src) dan memblokir blok<style>dan<script>inline kecuali jika diizinkan melalui hash atau nonce. - Sertakan
wss://diconnect-srcCSP: MeskipunConnection-Allowlistmencocokkan koneksiwss://dengan pola"https://...",connect-srcCSP di lingkungan lintas browser harus secara eksplisit mencantumkan originwss://saat WebSocket digunakan.
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'"
/>