L'en-tête de réponse HTTP Connection-Allowlist limite un document ou un worker à communiquer uniquement avec une liste explicite de points de terminaison réseau. Contrairement à la Content Security Policy (CSP), qui nécessite de gérer des directives distinctes par ressource (connect-src, img-src, script-src, font-src, form-action) et ne régit pas les canaux réseau spéculatifs ou non Fetch tels que <link rel="dns-prefetch">, <link rel="preconnect"> ou WebRTC, Connection-Allowlist applique une seule liste d'autorisation au niveau de la couche réseau à toutes les connexions sortantes initiées par le contexte (fetch(), XMLHttpRequest, sous-ressources, navigator.sendBeacon(), <a ping>, navigations de premier niveau et de frame, indices de ressources, règles de spéculation, WebSocket, WebTransport et WebRTC).
Mise en œuvre
- Sérialisez la liste d'autorisation en tant que liste interne RFC 9651
(...): formatezConnection-Allowlist(ouConnection-Allowlist-Report-Only) en tant que liste unique d'éléments séparés par des espaces entre parenthèses. Incluez le jeton sans guillemetsresponse-originpour autoriser l'origine du document ou du nœud de calcul, suivi de chaînesURLPatternentre guillemets doubles pour chaque point de terminaison externe légitime auquel le contexte se connecte ou accède (API, CDN d'images/de polices et cibles de redirection<a href>,<form action>, OAuth ou de paiement sortantes). Lorsque vous créez des modèles de manière dynamique à partir d'objets de configuration ouURL, normalisezws:/wss:enhttp:/https:et supprimez les barres obliques de fin sur les URL d'origine uniquement (carnew URL('https://api.example.com').hrefajoute/, ce qui limite la correspondanceURLPatternau chemin racine/uniquement). - Limiter le middleware par
Sec-Fetch-Destet couvrir les workers : les navigateurs ne respectentConnection-Allowlistque sur les réponses de document/navigation et les réponses de script de worker (valeursSec-Fetch-Destdocument,iframe,worker,sharedworkeretserviceworker). Lorsque vous variez les en-têtes parSec-Fetch-Dest, incluezVary: Sec-Fetch-Destafin que les caches partagés ne diffusent pas de réponses sans en-tête. Diffusez une liste d'autorisation correspondante sur les scripts DedicatedWorker et SharedWorker afin que les scripts de page compromis ne puissent pas exfiltrer de données viaworker.postMessage(). Étant donné qu'un service worker effectue ses propres récupérations réseau en cas d'échec de mise en cache et de préchargement de la navigation, configurez sonConnection-Allowlisten tant que sur-ensemble des listes d'autorisation de tous les documents qu'il contrôle. - Configurer le signalement des cas de non-respect et le déploiement progressif ou double : définissez un en-tête
Reporting-Endpointset ajoutez un paramètre; report-to=<endpoint-name>sans guillemets après le)de fermeture. Vous pouvez envoyerConnection-Allowlist-Report-Onlyseul lors du déploiement initial, ou envoyer à la foisConnection-Allowlist(en appliquant une liste d'autorisation de référence) etConnection-Allowlist-Report-Only(en auditant une liste d'autorisation candidate plus stricte) sur la même réponse. Les rapports d'infraction sont envoyés avec l'objet"type": "connection-allowlist"et le corps{ url, connection, allowlist, disposition }(oùconnectionest l'URL de destination bloquée ou"webrtc", etdispositionest"enforce"ou"report"). - Prenez en compte le blocage par défaut des redirections et de WebRTC, et gérez les erreurs client : par défaut,
Connection-Allowlistbloque toutes les redirections HTTP3xx(redirects=block, même lors de la redirection entre des URL ajoutées à la liste d'autorisation ou des URL d'origine) et tout le traficRTCPeerConnection(webrtc=block). Ajoutez; redirects=allowuniquement lorsque les points de terminaison de confiance nécessitent des redirections côté serveur (la requête initiale doit toujours correspondre à la liste d'autorisation) ou; webrtc=allowlorsque WebRTC est requis. Sur le client, encapsulez les appelsfetch()sortants danstry...catchet gérez les événementsWebSocketerrorafin que les requêtes rejetées avecTypeError(net::ERR_NETWORK_ACCESS_REVOKED) renvoient un état d'erreur géré et mettent à jour l'état de l'UI au lieu de provoquer des rejets non gérés.
Exemple de code
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 };
}
}
Bonnes pratiques
- À FAIRE : Formatez
Connection-Allowlistsous la forme d'une seule liste interne RFC 9651(...)d'éléments séparés par des espaces. Si un en-têteConnection-Allowlistnon vide échoue à l'analyse des champs structurés de premier niveau (par exemple, en omettant(...)ou en utilisant des guillemets simples'...'), le navigateur échoue avec une liste d'autorisation vide et bloque toutes les connexions réseau sortantes. UtilisezConnection-Allowlist: ()lorsque vous souhaitez intentionnellement révoquer tout accès au réseau après le chargement (les schémas locauxdata:,blob:,about:etfilesystem:restent autorisés). - À FAIRE : Omettez la barre oblique finale dans les formats d'URL à l'échelle de l'origine (
"https://api.example.com") ou utilisez un caractère générique explicite ("https://api.example.com/*") pour que les sous-chemins tels que/v1/userssoient autorisés. Supprimez les barres obliques de fin ajoutées parnew URL(origin).hrefavant de sérialiser les modèles. - AUTORISEZ les points de terminaison
WebSocket(ws://etwss://) ajoutés à la liste d'autorisation à l'aide des modèles"http://..."et"https://..."dansConnection-Allowlist, car le navigateur normalise les schémas WebSocket en HTTP/HTTPS avant la mise en correspondance. - À FAIRE : Incluez les cibles de navigation externes attendues (
<a href>,<form action>, fournisseurs OAuth/SSO, paiements) dans la liste d'autorisation d'un document restreint, et n'oubliez pas que la valeur par défautredirects=blockbloque même les redirections3xxde même origine, sauf si; redirects=allowest spécifié. - N'ajoutez PAS de guillemets autour des valeurs des paramètres
response-originou de la liste interne (; report-to=conn-reports,; redirects=allow,; webrtc=allow), et ne séparez pas les éléments de(...)par des virgules. - N'UTILISEZ PAS de groupes d'expressions régulières personnalisés tels que
(\d+)ni de chemins relatifs tels que"/api/*"dans les formats d'URL. L'analyseur de service réseau du navigateur rejette les groupes et les formats d'expressions régulières sans schéma ni nom d'hôte. - NE VOUS FIEZ PAS encore à l'attribut expérimental
<iframe connectionAllowlist="...">ni à l'en-tête d'application intégréAllow-Connection-Allowlist-Fromen production, car l'application intégrée n'est pas activée par défaut dans les navigateurs de livraison.
Stratégie de remplacement
Étant donné que Connection-Allowlist est appliqué au niveau de la couche réseau du navigateur via un en-tête de réponse HTTP, il ne peut pas être polyfillé en JavaScript côté client. Si votre cible Baseline inclut des navigateurs qui ne sont pas encore compatibles avec Connection-Allowlist, associez Connection-Allowlist à un en-tête Content-Security-Policy (CSP) (et à un élément <meta http-equiv="Content-Security-Policy"> pour les restrictions de récupération et de sous-ressources au niveau du document lorsque les en-têtes de réponse HTTP ne peuvent pas être configurés) :
- Défense en profondeur additive : les navigateurs compatibles appliquent les deux règles simultanément.
Connection-Allowlistbloque les canaux d'exfiltration non CSP (dns-prefetch,preconnect, WebRTC et redirections), tandis que CSP limite les navigationsfetch(), les sous-ressources et les navigations<form action>dans les navigateurs sansConnection-Allowlist. - Couvrez explicitement
form-actionetworker-src: dans la CSP,default-srcne sert pas de solution de secours pour les envois<form action>. Incluez toujours explicitementform-action. Incluezworker-src 'self'(à l'exclusion deblob:) afin que les pages restreintes ne puissent pas générer de SharedWorkersblob:pour contourner les restrictions au niveau du document. - Mappez les origines autorisées aux directives CSP par ressource : contrairement à
Connection-Allowlist(qui utilise une seule liste de points de terminaison et ne régit pas le balisage intégré),default-src 'self'dans CSP limite chaque type de ressource (connect-src,img-src,style-src,font-src,script-src) et bloque les blocs<style>et<script>intégrés, sauf s'ils sont autorisés via des hachages ou des nonces. - Inclure
wss://dans le CSPconnect-src: bien queConnection-Allowlistmette en correspondance les connexionswss://avec les modèles"https://...", le CSPconnect-srcdans les environnements multi-navigateurs doit lister explicitement les origineswss://lorsque des WebSockets sont utilisés.
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'"
/>