سرایند پاسخ HTTP Connection-Allowlist سند یا کارگر را محدود میکند تا فقط با فهرست صریحی از نقاط پایانی شبکه ارتباط برقرار کند. برخلاف «خطمشی امنیت محتوا» (CSP) که مستلزم حفظ دستورالعملهای جداگانه برای هر منبع (connect-src، img-src، script-src، font-src، form-action) است و بر کانالهای شبکه گمانهزن یا غیرFetch مانند <link rel="dns-prefetch">، <link rel="preconnect">، یا WebRTC نظارت نمیکند، Connection-Allowlist یک فهرست مجاز تکلایه شبکه را در همه اتصالات خروجی که توسط بافت (fetch()، XMLHttpRequest، زیرمنابع، navigator.sendBeacon()، <a ping>، پیمایشهای سطح بالا و قاب، نکات منبع، «قوانین گمانهزنی»، WebSocket، WebTransport، و WebRTC) آغاز شدهاند اعمال میکند.
نحوه پیادهسازی
- فهرست مجاز را بهصورت «فهرست داخلی» RFC 9651
(...)سریالسازی کنید:Connection-Allowlist(یاConnection-Allowlist-Report-Only) را بهصورت یک فهرست پرانتزدار از موارد جداشده با فاصله قالببندی کنید. کدresponse-originبدون گیومه را برای مجاز کردن مبدأ سند یا کارگر اضافه کنید، سپس رشتههایURLPatternبا گیومه دوتایی را برای هر نقطه پایانی خارجی قانونی که زمینه به آن متصل میشود یا به آن پیمایش میکند (میاناهای برنامهسازی کاربردی، شبکههای توزیع محتوای تصویر/ قلم، و اهداف تغییرمسیر پرداخت، OAuth،<form action>،<a href>برونمرزی) اضافه کنید. هنگام ساختن الگوها بهصورت پویا از پیکربندی یا اشیاءURL،ws:/wss:را بهhttp:/https:نرمالسازی کنید و خطهای مورب انتهایی را در نشانیهای وب فقط مبدأ حذف کنید (زیراnew URL('https://api.example.com').href/را اضافه میکند، که تطابقURLPatternرا فقط به مسیر ریشه/محدود میکند). - میانافزار را براساس
Sec-Fetch-Destمحدود کنید و کارگران را پوشش دهید: مرورگرها فقطConnection-Allowlistرا در پاسخهای سند/پیمایش و پاسخهای اسکریپت کارگر رعایت میکنند (مقادیرSec-Fetch-Destdocument،iframe،worker،sharedworker، وserviceworker). هنگام تغییر سرصفحهها براساسSec-Fetch-Dest،Vary: Sec-Fetch-Destرا اضافه کنید تا حافظههای پنهان مشترک پاسخهای بدون سرصفحه ارائه ندهند. فهرست مجاز مطابقی را در اسکریپتهای Dedicated Worker و Shared Worker ارائه دهید تا اسکریپتهای صفحه بهخطر افتاده نتوانند دادهها را ازطریق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نشانی وب مقصد مسدودشده یا"webrtc"است، وdispositionبرابر با"enforce"یا"report"است) ارسال میشود. - درنظر گرفتن بازهدایت پیشفرض و مسدود کردن WebRTC، و رسیدگی به خطاهای کارخواه: بهطور پیشفرض،
Connection-Allowlistهمه بازهدایتهای HTTP3xx(redirects=block، حتی هنگام بازهدایت بین نشانیهای وب فهرست مجاز یا با مبدأ یکسان) و همه ترافیکRTCPeerConnection(webrtc=block) را مسدود میکند. فقط زمانی; redirects=allowرا اضافه کنید که نقاط پایانی مورداعتماد به بازهدایتهای سمت سرور نیاز داشته باشند (درخواست اولیه همچنان باید با فهرست مجاز مطابقت داشته باشد)، یا زمانیکه WebRTC لازم باشد; webrtc=allowرا اضافه کنید. در کارخواه، تماسهای خروجیfetch()را درtry...catchبپیچید و رویدادهایWebSocketerrorرا مدیریت کنید تا درخواستهای ردشده باTypeError(net::ERR_NETWORK_ACCESS_REVOKED) حالت خطای مدیریتشده را برگردانند و وضعیت واسط کاربر را بهروز کنند، بهجای اینکه باعث رد شدنهای مدیریتنشده شوند.
کد نمونه
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:همچنان مجاز باقی میمانند). - باید خط مورب پایانی را در الگوهای نشانی وب در سطح مبدأ (
"https://api.example.com") حذف کنید یا از نویسه عام صریح ("https://api.example.com/*") استفاده کنید تا زیرمسیرهایی مانند/v1/usersمجاز شوند. Strip trailing slashes added bynew URL(origin).hrefbefore serializing patterns. - باید نقاط پایانی
WebSocket(ws://وwss://) را بااستفاده از الگوهای"http://..."و"https://..."درConnection-Allowlistدر فهرست مجاز قرار دهید، زیرا مرورگر طرحهای WebSocket را قبلاز مطابقت دادن به HTTP/HTTPS نرمالسازی میکند. - حتماً هدفهای پیمایش خارجی موردانتظار (
<a href>،<form action>، ارائهدهندگان OAuth/SSO، تسویهحسابهای پرداخت) را در فهرست مجاز سند محدودشده قرار دهید، و بهخاطر داشته باشید کهredirects=blockپیشفرض حتی تغییرمسیرهای3xxبا مبدأ یکسان را مسدود میکند مگراینکه; redirects=allowمشخص شده باشد. - مقادیر پارامتر
response-originیا فهرست داخلی (; report-to=conn-reports،; redirects=allow،; webrtc=allow) را در علامت نقلقول قرار ندهید، یا موارد داخل(...)را با کاما جدا نکنید. - از گروههای عبارت باقاعده سفارشی مانند
(\d+)یا مسیرهای نسبی مانند"/api/*"در الگوهای نشانی وب استفاده نکنید؛ تجزیهکننده سرویس شبکه مرورگر گروهها و الگوهای عبارت باقاعده بدون طرح و نام میزبان را رد میکند. - هنوز به مشخصه آزمایشی
<iframe connectionAllowlist="...">یا سرصفحه اجرایی جاسازیشدهAllow-Connection-Allowlist-Fromدر تولید اتکا نکنید، زیرا اجرای جاسازیشده بهطور پیشفرض در مرورگرهای ارسال فعال نیست.
راهبرد جایگزین
چون Connection-Allowlist در لایه شبکه مرورگر ازطریق سرایند پاسخهای HTTP اعمال میشود، نمیتوان آن را در جاوا اسکریپت سمت کارخواه شبیهسازی کرد. اگر هدف Baseline شما شامل مرورگرهایی است که هنوز از Connection-Allowlist پشتیبانی نمیکنند، Connection-Allowlist را با Content-Security-Policy (CSP) جفت کنید (و یک عنصر <meta http-equiv="Content-Security-Policy"> برای واکشی در سطح سند و محدودیتهای زیرمنبع زمانی که سرصفحههای پاسخ HTTP قابل پیکربندی نیستند):
- دفاع در عمق افزایشی: مرورگرهای پشتیبانیکننده هر دو خطمشی را بهطور همزمان اجرا میکنند—
Connection-Allowlistکانالهای استخراج غیرCSP (dns-prefetch،preconnect، WebRTC، و تغییرمسیرها) را مسدود میکند، درحالیکه CSPfetch()، زیرمنابع، و پیمایشهای<form action>را در مرورگرهای فاقدConnection-Allowlistمحدود میکند. form-actionوworker-srcرا بهطور صریح پوشش دهید: در CSP،default-srcبرای ارسالهای<form action>به حالت قبلی برنمیگردد؛ همیشهform-actionرا بهطور صریح اضافه کنید.worker-src 'self'(بهاستثنایblob:) را اضافه کنید تا صفحههای محدودشده نتوانند SharedWorkersblob:ایجاد کنند و محدودیتهای سطح سند را دور بزنند.- نگاشت مبدأهای مجاز به دستورالعملهای CSP برای هر منبع: برخلاف
Connection-Allowlist(که از یک فهرست نقطه پایانی واحد استفاده میکند و نشانهگذاری درونخطی را کنترل نمیکند)،default-src 'self'در CSP هر نوع منبع (connect-src،img-src،style-src،font-src،script-src) را محدود میکند و بلوکهای درونخطی<style>و<script>را مسدود میکند، مگر اینکه ازطریق درهمسازی یا نانس مجاز شده باشند. wss://را در CSPconnect-srcاضافه کنید: درحالیکهConnection-Allowlistاتصالاتwss://را با الگوهای"https://..."مطابقت میدهد، CSPconnect-srcدر محیطهای بینمرورگری باید صریحاً مبدأهایwss://را هنگام استفاده از WebSockets فهرست کند.
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'"
/>