Tiêu đề phản hồi HTTP Connection-Allowlist hạn chế một tài liệu hoặc trình thực thi chỉ giao tiếp với một danh sách rõ ràng các điểm cuối mạng. Không giống như Chính sách bảo mật nội dung (CSP) – yêu cầu duy trì các lệnh riêng biệt cho mỗi tài nguyên (connect-src, img-src, script-src, font-src, form-action) và không quản lý các kênh mạng suy đoán hoặc không phải Fetch như <link rel="dns-prefetch">, <link rel="preconnect"> hoặc WebRTC – Connection-Allowlist thực thi một danh sách cho phép duy nhất ở lớp mạng trên tất cả kết nối đi do ngữ cảnh khởi tạo (fetch(), XMLHttpRequest, tài nguyên phụ, navigator.sendBeacon(), <a ping>, thao tác điều hướng cấp cao nhất và khung, gợi ý tài nguyên, Quy tắc suy đoán, WebSocket, WebTransport và WebRTC).
Cách triển khai
- Chuyển đổi danh sách cho phép thành Danh sách nội bộ RFC 9651
(...): Định dạngConnection-Allowlist(hoặcConnection-Allowlist-Report-Only) dưới dạng một danh sách duy nhất trong dấu ngoặc đơn gồm các mục được phân tách bằng dấu cách. Thêm mã thông báo không được trích dẫnresponse-originđể cho phép nguồn gốc của tài liệu hoặc trình chạy, theo sau là chuỗiURLPatternđược trích dẫn kép cho mọi điểm cuối bên ngoài hợp pháp mà ngữ cảnh kết nối hoặc điều hướng đến (API, CDN hình ảnh/phông chữ và mục tiêu chuyển hướng<a href>,<form action>, OAuth hoặc thanh toán ra bên ngoài). Khi tạo mẫu một cách linh động từ cấu hình hoặc đối tượngURL, hãy chuẩn hoáws:/wss:thànhhttp:/https:và loại bỏ dấu gạch chéo ở cuối URL chỉ có nguồn gốc (vìnew URL('https://api.example.com').hrefthêm/, điều này hạn chế việc so khớpURLPatternchỉ với đường dẫn gốc/). - Phạm vi phần mềm trung gian theo
Sec-Fetch-Destvà bao gồm cả worker: Trình duyệt chỉ tuân thủConnection-Allowlisttrên các phản hồi tài liệu/điều hướng và phản hồi tập lệnh worker (giá trịSec-Fetch-Destdocument,iframe,worker,sharedworkervàserviceworker). Khi thay đổi tiêu đề theoSec-Fetch-Dest, hãy thêmVary: Sec-Fetch-Destđể bộ nhớ đệm dùng chung không phân phát phản hồi không có tiêu đề. Cung cấp danh sách cho phép phù hợp trên tập lệnh Worker chuyên dụng và Worker dùng chung để tập lệnh trang bị xâm phạm không thể lấy dữ liệu thông quaworker.postMessage(). Vì Trình chạy dịch vụ thực hiện các hoạt động tìm nạp mạng riêng khi thiếu bộ nhớ đệm và tải trước thành phần điều hướng, hãy định cấu hìnhConnection-Allowlistcủa trình chạy dịch vụ dưới dạng một superset của danh sách cho phép của tất cả tài liệu mà trình chạy dịch vụ kiểm soát. - Định cấu hình báo cáo lỗi vi phạm và triển khai theo giai đoạn hoặc triển khai kép: Xác định tiêu đề
Reporting-Endpointsvà thêm tham số; report-to=<endpoint-name>không có dấu ngoặc kép sau dấu đóng). Bạn có thể chỉ gửiConnection-Allowlist-Report-Onlytrong lần triển khai đầu tiên hoặc gửi cảConnection-Allowlist(thực thi danh sách cho phép cơ sở) vàConnection-Allowlist-Report-Only(kiểm tra danh sách cho phép nghiêm ngặt hơn) trên cùng một phản hồi. Báo cáo lỗi vi phạm được gửi kèm theo"type": "connection-allowlist"và nội dung{ url, connection, allowlist, disposition }(trong đóconnectionlà URL đích bị chặn hoặc"webrtc", còndispositionlà"enforce"hoặc"report"). - Tính đến việc chặn chuyển hướng mặc định và WebRTC, đồng thời xử lý lỗi ứng dụng: Theo mặc định,
Connection-Allowlistchặn tất cả lệnh chuyển hướng HTTP3xx(redirects=block, ngay cả khi chuyển hướng giữa các URL được đưa vào danh sách cho phép hoặc URL gốc) và tất cả lưu lượng truy cậpRTCPeerConnection(webrtc=block). Chỉ thêm; redirects=allowkhi các điểm cuối đáng tin cậy yêu cầu chuyển hướng phía máy chủ (yêu cầu ban đầu vẫn phải khớp với danh sách cho phép) hoặc; webrtc=allowkhi cần có WebRTC. Trên ứng dụng, hãy gói các lệnh gọifetch()gửi đi trongtry...catchvà xử lý các sự kiệnWebSocketerrorđể các yêu cầu bị từ chối bằngTypeError(net::ERR_NETWORK_ACCESS_REVOKED) trả về trạng thái lỗi đã xử lý và cập nhật trạng thái giao diện người dùng thay vì gây ra các trường hợp từ chối chưa xử lý.
Mã mẫu
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 };
}
}
Các phương pháp hay nhất
- NÊN định dạng
Connection-Allowlistdưới dạng một Danh sách bên trong RFC 9651(...)gồm các mục được phân tách bằng dấu cách. Nếu tiêu đềConnection-Allowlistkhông trống không phân tích cú pháp được Trường có cấu trúc cấp cao nhất (ví dụ: bỏ qua(...)hoặc sử dụng dấu nháy đơn'...'), thì trình duyệt sẽ không đóng được bằng danh sách cho phép trống và chặn tất cả kết nối mạng đi. Sử dụngConnection-Allowlist: ()khi bạn muốn thu hồi tất cả quyền truy cập mạng sau khi tải (các lược đồ cục bộdata:,blob:,about:vàfilesystem:vẫn được cho phép). - NÊN bỏ dấu gạch chéo ở cuối mẫu URL trên toàn bộ nguồn gốc (
"https://api.example.com") hoặc sử dụng ký tự đại diện rõ ràng ("https://api.example.com/*") để cho phép các đường dẫn phụ như/v1/users. Xoá dấu gạch chéo ở cuối donew URL(origin).hrefthêm vào trước khi chuyển đổi tuần tự các mẫu. - NÊN đưa các điểm cuối
WebSocket(ws://vàwss://) vào danh sách cho phép bằng cách sử dụng các mẫu"http://..."và"https://..."trongConnection-Allowlist, vì trình duyệt sẽ chuẩn hoá các lược đồ WebSocket thành HTTP/HTTPS trước khi so khớp. - NÊN đưa các mục tiêu điều hướng bên ngoài dự kiến (
<a href>,<form action>, nhà cung cấp OAuth/SSO, quy trình thanh toán) vào danh sách cho phép của tài liệu bị hạn chế và hãy nhớ rằngredirects=blockmặc định sẽ chặn ngay cả các lệnh chuyển hướng3xxcùng nguồn gốc, trừ phi bạn chỉ định; redirects=allow. - KHÔNG gói các giá trị tham số
response-originhoặc danh sách bên trong (; report-to=conn-reports,; redirects=allow,; webrtc=allow) trong dấu ngoặc kép hoặc phân tách các mục bên trong(...)bằng dấu phẩy. - KHÔNG sử dụng các nhóm biểu thức chính quy tuỳ chỉnh như
(\d+)hoặc đường dẫn tương đối như"/api/*"bên trong mẫu URL; trình phân tích cú pháp dịch vụ mạng của trình duyệt sẽ từ chối các nhóm và mẫu biểu thức chính quy không có lược đồ và tên máy chủ. - KHÔNG dựa vào thuộc tính
<iframe connectionAllowlist="...">thử nghiệm hoặc tiêu đề thực thi được nhúngAllow-Connection-Allowlist-Fromtrong phiên bản chính thức, vì tính năng thực thi được nhúng không được bật theo mặc định trong các trình duyệt vận chuyển.
Chiến lược dự phòng
Vì Connection-Allowlist được thực thi ở tầng mạng trình duyệt thông qua tiêu đề phản hồi HTTP, nên không thể polyfill trong JavaScript phía máy khách. Nếu mục tiêu Baseline của bạn bao gồm các trình duyệt chưa hỗ trợ Connection-Allowlist, hãy ghép nối Connection-Allowlist với tiêu đề Content-Security-Policy (CSP) (và phần tử <meta http-equiv="Content-Security-Policy"> cho các quy định hạn chế về tài nguyên phụ và tìm nạp ở cấp tài liệu khi không thể định cấu hình tiêu đề phản hồi HTTP):
- Phòng thủ theo chiều sâu bổ sung: Các trình duyệt hỗ trợ thực thi cả hai chính sách cùng một lúc –
Connection-Allowlistchặn các kênh rò rỉ không phải CSP (dns-prefetch,preconnect, WebRTC và lệnh chuyển hướng), trong khi CSP hạn chếfetch(), tài nguyên phụ và thao tác điều hướng<form action>trong các trình duyệt không cóConnection-Allowlist. - Bao gồm
form-actionvàworker-srcmột cách rõ ràng: Trong CSP,default-srckhông dự phòng cho các lần gửi<form action>; luôn bao gồmform-actionmột cách rõ ràng. Thêmworker-src 'self'(ngoại trừblob:) để các trang bị hạn chế không thể tạo SharedWorkerblob:nhằm vượt qua các quy định hạn chế ở cấp tài liệu. - Liên kết các nguồn gốc được phép với các lệnh CSP cho mỗi tài nguyên: Không giống như
Connection-Allowlist(sử dụng một danh sách điểm cuối duy nhất và không quản lý mã đánh dấu cùng dòng),default-src 'self'trong CSP hạn chế từng loại tài nguyên (connect-src,img-src,style-src,font-src,script-src) và chặn các khối<style>và<script>cùng dòng, trừ phi được uỷ quyền thông qua hàm băm hoặc số chỉ dùng một lần. - Thêm
wss://vào CSPconnect-src: Mặc dùConnection-Allowlistso khớp các kết nốiwss://với mẫu"https://...", nhưng CSPconnect-srctrong môi trường trên nhiều trình duyệt phải liệt kê rõ ràng các nguồn gốcwss://khi sử dụng WebSocket.
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'"
/>