El encabezado de respuesta HTTP Connection-Allowlist restringe un documento o un trabajador para que se comunique solo con una lista explícita de extremos de red. A diferencia de la Política de seguridad del contenido (CSP), que requiere mantener directivas independientes por recurso (connect-src, img-src, script-src, font-src, form-action) y no rige los canales de red especulativos o que no son de recuperación, como <link rel="dns-prefetch">, <link rel="preconnect"> o WebRTC, Connection-Allowlist aplica una sola lista de entidades permitidas de capa de red en todas las conexiones salientes que inicia el contexto (fetch(), XMLHttpRequest, recursos secundarios, navigator.sendBeacon(), <a ping>, navegaciones de nivel superior y de marcos, sugerencias de recursos, reglas de especulación, WebSocket, WebTransport y WebRTC).
Cómo implementarlo
- Serializa la lista de entidades permitidas como una lista interna de RFC 9651
(...): Aplica el formatoConnection-Allowlist(oConnection-Allowlist-Report-Only) como una sola lista entre paréntesis de elementos separados por espacios. Incluye el token sin comillasresponse-originpara permitir el origen del documento o del trabajador, seguido de cadenasURLPatternentre comillas dobles para cada extremo externo legítimo al que se conecta o navega el contexto (APIs, CDN de imágenes o fuentes, y destinos de redireccionamiento de OAuth, pagos,<a href>o<form action>salientes). Cuando compiles patrones de forma dinámica a partir de objetos de configuración oURL, normalizaws:/wss:ahttp:/https:y quita las barras finales en las URLs de solo origen (porquenew URL('https://api.example.com').hrefagrega/, lo que restringe la coincidencia deURLPatternsolo a la ruta raíz/). - Limita el middleware por
Sec-Fetch-Desty abarca los trabajadores: Los navegadores solo respetanConnection-Allowlisten las respuestas de documentos o navegación y en las respuestas de secuencias de comandos de trabajadores (valores deSec-Fetch-Destdocument,iframe,worker,sharedworkeryserviceworker). Cuando varíes los encabezados porSec-Fetch-Dest, incluyeVary: Sec-Fetch-Destpara que las cachés compartidas no entreguen respuestas sin encabezado. Publica una lista de entidades permitidas coincidente en las secuencias de comandos de Dedicated Worker y Shared Worker para que las secuencias de comandos de páginas vulneradas no puedan robar datos a través deworker.postMessage(). Debido a que un Service Worker realiza sus propias recuperaciones de red en caso de errores de caché y precargas de navegación, configura suConnection-Allowlistcomo un superconjunto de las listas de entidades permitidas de todos los documentos que controla. - Configura los informes de incumplimientos y el lanzamiento en etapas o doble: Define un encabezado
Reporting-Endpointsy agrega un parámetro; report-to=<endpoint-name>sin comillas después del)de cierre. Puedes enviar soloConnection-Allowlist-Report-Onlydurante el lanzamiento inicial o enviar ambosConnection-Allowlist(aplicando una lista de entidades permitidas de referencia) yConnection-Allowlist-Report-Only(auditando una lista de entidades permitidas candidata más estricta) en la misma respuesta. Los informes de incumplimiento se entregan con"type": "connection-allowlist"y el cuerpo{ url, connection, allowlist, disposition }(dondeconnectiones la URL de destino bloqueada o"webrtc", ydispositiones"enforce"o"report"). - Ten en cuenta el redireccionamiento predeterminado y el bloqueo de WebRTC, y controla los errores del cliente: De forma predeterminada,
Connection-Allowlistbloquea todos los redireccionamientos HTTP de3xx(redirects=block, incluso cuando se redirecciona entre URLs de la lista de entidades permitidas o del mismo origen) y todo el tráfico deRTCPeerConnection(webrtc=block). Agrega; redirects=allowsolo cuando los extremos de confianza requieran redireccionamientos del servidor (la solicitud inicial aún debe coincidir con la lista de entidades permitidas) o; webrtc=allowcuando se requiera WebRTC. En el cliente, une las llamadas salientes defetch()entry...catchy controla los eventosWebSocketerrorpara que las solicitudes rechazadas conTypeError(net::ERR_NETWORK_ACCESS_REVOKED) devuelvan un estado de error controlado y actualicen el estado de la IU en lugar de causar rechazos no controlados.
Ejemplo de código
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 };
}
}
Prácticas recomendadas
- SÍ se debe dar formato a
Connection-Allowlistcomo una sola lista interna de RFC 9651(...)de elementos separados por espacios. Si un encabezadoConnection-Allowlistno vacío no puede analizar el campo estructurado de nivel superior (por ejemplo, si se omite(...)o se usan comillas simples'...'), el navegador falla y se cierra con una lista de entidades permitidas vacía y bloquea todas las conexiones de red salientes. UsaConnection-Allowlist: ()cuando quieras revocar de forma intencional todo el acceso a la red después de la carga (se siguen permitiendo los esquemas localesdata:,blob:,about:yfilesystem:). - SÍ omitas la barra diagonal final en los patrones de URL de todo el origen (
"https://api.example.com") o usa un comodín explícito ("https://api.example.com/*") para que se permitan las rutas de acceso secundarias como/v1/users. Se quitan las barras finales que agreganew URL(origin).hrefantes de serializar los patrones. - SÍ agrega a la lista de entidades permitidas los extremos
WebSocket(ws://ywss://) con los patrones"http://..."y"https://..."enConnection-Allowlist, ya que el navegador normaliza los esquemas de WebSocket a HTTP/HTTPS antes de la coincidencia. - SÍ incluye los destinos de navegación externos esperados (
<a href>,<form action>, proveedores de OAuth/SSO, confirmaciones de pago) en la lista de entidades permitidas de un documento restringido, y recuerda que elredirects=blockpredeterminado bloquea incluso los redireccionamientos3xxdel mismo origen, a menos que se especifique; redirects=allow. - NO encierres entre comillas los valores de los parámetros
response-originni de las listas internas (; report-to=conn-reports,; redirects=allow,; webrtc=allow), ni separes los elementos dentro de(...)con comas. - NO uses grupos de expresiones regulares personalizados, como
(\d+), ni rutas de acceso relativas, como"/api/*", dentro de los patrones de URL. El analizador de servicios de red del navegador rechaza los grupos y patrones de expresiones regulares sin un esquema y un nombre de host. - NO dependas todavía del atributo experimental
<iframe connectionAllowlist="...">ni del encabezado de aplicación de política incorporadoAllow-Connection-Allowlist-Fromen producción, ya que la aplicación de política incorporada no está habilitada de forma predeterminada en los navegadores de envío.
Estrategia de resguardo
Debido a que la Connection-Allowlist se aplica en la capa de red del navegador a través de un encabezado de respuesta HTTP, no se puede rellenar con polyfill en JavaScript del cliente. Si tu objetivo de Baseline incluye navegadores que aún no admiten Connection-Allowlist, vincula Connection-Allowlist con un encabezado Content-Security-Policy (CSP) (y un elemento <meta http-equiv="Content-Security-Policy"> para las restricciones de recuperación y subrecursos a nivel del documento cuando no se pueden configurar los encabezados de respuesta HTTP):
- Defensa en profundidad adicional: Los navegadores compatibles aplican ambas políticas de forma simultánea:
Connection-Allowlistbloquea los canales de robo de datos que no son de CSP (dns-prefetch,preconnect, WebRTC y redireccionamientos), mientras que la CSP restringefetch(), los recursos secundarios y las navegaciones de<form action>en los navegadores sinConnection-Allowlist. - Incluye
form-actionyworker-srcde forma explícita: En la CSP,default-srcno recurre a<form action>para los envíos; incluye siempreform-actionde forma explícita. Incluyeworker-src 'self'(excluyendoblob:) para que las páginas restringidas no puedan generar SharedWorkers deblob:para omitir las restricciones a nivel del documento. - Asigna orígenes permitidos a directivas de CSP por recurso: A diferencia de
Connection-Allowlist(que usa una sola lista de extremos y no rige el lenguaje de marcado intercalado),default-src 'self'en CSP restringe cada tipo de recurso (connect-src,img-src,style-src,font-src,script-src) y bloquea los bloques<style>y<script>intercalados, a menos que se autoricen a través de hashes o nonces. - Incluye
wss://en la CSPconnect-src: Si bienConnection-Allowlistcompara las conexioneswss://con los patrones"https://...", la CSPconnect-srcen entornos de varios navegadores debe enumerar de forma explícita los orígenes dewss://cuando se usan 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'"
/>