Заголовок HTTP-ответа Connection-Allowlist ограничивает обмен данными документа или воркера только с явным списком конечных точек сети. В отличие от Content Security Policy (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в двойных кавычках для каждой допустимой внешней конечной точки, к которой подключается или переходит контекст (API, CDN изображений/шрифтов и исходящие цели<a href>,<form action>, OAuth или перенаправления платежей). При динамическом создании шаблонов из объектов конфигурации илиURLнормализуйтеws:/wss:доhttp:/https:и удалите косые черты в конце URL, содержащих только источник (посколькуnew URL('https://api.example.com').hrefдобавляет/, что ограничивает сопоставлениеURLPatternтолько корневым путем/). - Ограничьте область действия промежуточного ПО с помощью
Sec-Fetch-Destи охватите воркеры. Браузеры учитываютConnection-Allowlistтолько в ответах на запросы документов и навигации, а также в ответах скриптов воркеров (значенияSec-Fetch-Dest:document,iframe,worker,sharedworkerиserviceworker). При изменении заголовков с помощьюSec-Fetch-DestвключитеVary: Sec-Fetch-Dest, чтобы общие кеши не обслуживали ответы без заголовков. Используйте белый список для скриптов выделенных и общих рабочих процессов, чтобы скрипты взломанных страниц не могли извлекать данные черезworker.postMessage(). Поскольку сервис-воркер выполняет собственные сетевые запросы при отсутствии совпадений в кеше и предварительной загрузке навигации, настройте его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блокирует все HTTP-переадресации3xx(redirects=block, даже при переадресации между URL из белого списка или URL одного источника) и весь трафикRTCPeerConnection(webrtc=block). Добавляйте; redirects=allow, только если доверенным конечным точкам требуется переадресация на стороне сервера (исходный запрос по-прежнему должен соответствовать белому списку), или; webrtc=allow, если требуется WebRTC. На стороне клиента оберните исходящие вызовы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:остаются разрешенными). - НЕ добавляйте косую черту в конце шаблонов URL, относящихся к источнику (
"https://api.example.com"), или используйте подстановочный знак ("https://api.example.com/*"), чтобы разрешить подпути, например/v1/users. Удалите конечные косые черты, добавленныеnew URL(origin).href, перед сериализацией шаблонов. - Добавьте конечные точки
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) и не разделяйте запятыми элементы внутри(...). - НЕ ИСПОЛЬЗУЙТЕ в шаблонах URL специальные группы регулярных выражений, например
(\d+), или относительные пути, например"/api/*". Синтаксический анализатор сетевого сервиса браузера отклоняет группы регулярных выражений и шаблоны без схемы и имени хоста. - НЕ полагайтесь на экспериментальный атрибут
<iframe connectionAllowlist="...">или встроенный заголовок принудительного примененияAllow-Connection-Allowlist-Fromв рабочей среде, поскольку встроенное принудительное применение не включено по умолчанию в браузерах, поставляемых пользователям.
Резервная стратегия
Поскольку Connection-Allowlist применяется на сетевом уровне браузера через заголовок HTTP-ответа, его нельзя заполнить в клиентском JavaScript. Если ваша целевая аудитория использует браузеры, которые пока не поддерживают Connection-Allowlist, используйте Connection-Allowlist вместе с заголовком Content-Security-Policy (CSP) (и элементом <meta http-equiv="Content-Security-Policy"> для ограничений на получение документов и подресурсов, когда заголовки HTTP-ответов нельзя настроить):
- Дополнительная углубленная защита. Поддерживаемые браузеры применяют оба правила одновременно:
Connection-Allowlistблокирует каналы утечки данных, не связанные с CSP (dns-prefetch,preconnect, WebRTC и переадресации), а CSP ограничиваетfetch(), дочерние ресурсы и переходы<form action>в браузерах безConnection-Allowlist. - Явно укажите
form-actionиworker-src. В CSPdefault-srcне используется в качестве резервного варианта для отправки<form action>. Всегда явно указывайтеform-action. Включитеworker-src 'self'(за исключениемblob:), чтобы страницы с ограниченным доступом не могли создаватьblob:SharedWorkers для обхода ограничений на уровне документа. - Сопоставьте разрешенные источники с директивами 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'"
/>