Bağlantı izin verilenler listeleriyle giden ağ bağlantılarını kısıtlama

Connection-Allowlist HTTP yanıt başlığı, bir dokümanın veya çalışanın yalnızca açık bir ağ uç noktası listesiyle iletişim kurmasını kısıtlar. Ayrı kaynak başına yönergelerin (connect-src, img-src, script-src, font-src, form-action) sürdürülmesini gerektiren ve <link rel="dns-prefetch">, <link rel="preconnect"> veya WebRTC gibi spekülatif ya da Fetch olmayan ağ kanallarını yönetmeyen İçerik Güvenliği Politikası'nın (CSP) aksine Connection-Allowlist, bağlam tarafından başlatılan tüm giden bağlantılarda (fetch(), XMLHttpRequest, alt kaynaklar, navigator.sendBeacon(), <a ping>, üst düzey ve çerçeve gezinmeleri, kaynak ipuçları, spekülasyon kuralları, WebSocket, WebTransport ve WebRTC) tek bir ağ katmanı izin verilenler listesi uygular.

Nasıl uygulanır?

  1. İzin verilenler listesini RFC 9651 İç Listesi olarak serileştirin (...): Connection-Allowlist (veya Connection-Allowlist-Report-Only) öğesini, boşlukla ayrılmış öğelerden oluşan tek bir parantez içine alınmış liste olarak biçimlendirin. Dokümanın veya çalışanın kendi kaynağına izin vermek için tırnak içine alınmamış response-origin jetonunu ekleyin. Ardından, bağlamın bağlandığı veya gittiği her meşru harici uç nokta (API'ler, resim/yazı tipi CDN'leri ve giden <a href>, <form action>, OAuth veya ödeme yönlendirme hedefleri) için çift tırnak içine alınmış URLPattern dizelerini ekleyin. Yapılandırma veya URL nesnelerinden dinamik olarak kalıp oluştururken ws:/wss: değerini http:/https: olarak normalleştirin ve yalnızca kaynak URL'lerdeki sondaki eğik çizgileri kaldırın (çünkü new URL('https://api.example.com').href, / ekler ve bu da URLPattern eşleşmesini yalnızca kök yol / ile kısıtlar).
  2. Ara katman yazılımlarını Sec-Fetch-Dest ile kapsamlandırın ve çalışanları kapsayın: Tarayıcılar, yalnızca belge/gezinme yanıtlarında ve çalışan komut dosyası yanıtlarında (Sec-Fetch-Dest değerleri document, iframe, worker, sharedworker ve serviceworker) Connection-Allowlist değerini dikkate alır. Üstbilgileri Sec-Fetch-Dest ile değiştirirken Vary: Sec-Fetch-Dest değerini ekleyin. Böylece paylaşılan önbellekler üstbilgisiz yanıtlar sunmaz. Özel ve paylaşılan çalışan komut dosyalarında eşleşen bir izin verilenler listesi sunun. Böylece güvenliği ihlal edilmiş sayfa komut dosyaları worker.postMessage() üzerinden veri sızdıramaz. Hizmet Çalışanı, önbellekte yok ve gezinme önceden yüklemelerinde kendi ağ getirme işlemlerini gerçekleştirdiğinden Connection-Allowlist değerini, kontrol ettiği tüm dokümanların izin verilenler listelerinin süper seti olarak yapılandırın.
  3. İhlal raporlamasını ve aşamalı veya ikili sunumu yapılandırın: Bir Reporting-Endpoints üstbilgisi tanımlayın ve kapanış ) işaretinden sonra tırnak içine alınmamış bir ; report-to=<endpoint-name> parametresi ekleyin. İlk kullanıma sunma sırasında yalnızca Connection-Allowlist-Report-Only gönderebilir veya aynı yanıtta hem Connection-Allowlist (temel izin verilenler listesini zorunlu kılar) hem de Connection-Allowlist-Report-Only (daha katı bir aday izin verilenler listesini denetler) gönderebilirsiniz. İhlal raporları "type": "connection-allowlist" ve { url, connection, allowlist, disposition } gövdesiyle gönderilir (burada connection, engellenen hedef URL veya "webrtc", disposition ise "enforce" ya da "report" şeklindedir).
  4. Varsayılan yönlendirme ve WebRTC engellemesini hesaba katın ve istemci hatalarını işleyin: Varsayılan olarak Connection-Allowlist, tüm 3xx HTTP yönlendirmelerini (redirects=block, izin verilenler listesindeki veya aynı kaynak URL'ler arasında yönlendirme yaparken bile) ve tüm RTCPeerConnection trafiğini (webrtc=block) engeller. ; redirects=allow parametresini yalnızca güvenilir uç noktalar sunucu tarafı yönlendirmeleri gerektirdiğinde (ilk istek yine de izin verilenler listesiyle eşleşmelidir) veya WebRTC gerektiğinde ; webrtc=allow parametresini ekleyin. İstemcide, giden fetch() çağrılarını try...catch ile sarmalayın ve WebSocket error etkinliklerini işleyin. Böylece TypeError (net::ERR_NETWORK_ACCESS_REVOKED) ile reddedilen istekler, işlenmeyen retlere neden olmak yerine işlenen bir hata durumu döndürür ve kullanıcı arayüzü durumunu günceller.

Örnek kod

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 };
  }
}

En iyi uygulamalar

  • Connection-Allowlist değerini, boşlukla ayrılmış öğelerden oluşan tek bir RFC 9651 iç listesi (...) olarak biçimlendirin (DO). Boş olmayan bir Connection-Allowlist üstbilgisi, üst düzey Yapılandırılmış Alan ayrıştırmasında başarısız olursa (örneğin, (...) atlanırsa veya tek tırnak '...' kullanılırsa) tarayıcı, boş bir izin verilenler listesiyle başarısız olur ve tüm giden ağ bağlantılarını engeller. Yüklemeden sonra tüm ağ erişimini kasıtlı olarak iptal etmek istediğinizde Connection-Allowlist: () kullanın (yerel şemalar data:, blob:, about: ve filesystem: izinli kalır).
  • Kaynak genelindeki URL kalıplarında ("https://api.example.com") sondaki eğik çizgiyi ATLAYIN veya açık bir joker karakter ("https://api.example.com/*") kullanın. Böylece /v1/users gibi alt yollara izin verilir. Kalıpları serileştirmeden önce new URL(origin).href tarafından eklenen sondaki eğik çizgileri kaldırın.
  • Tarayıcı, eşleştirmeden önce WebSocket şemalarını HTTP/HTTPS olarak normalleştirdiğinden Connection-Allowlist içinde "http://..." ve "https://..." kalıplarını kullanarak WebSocket (ws:// ve wss://) uç noktalarına izin verilenler listesinde YER VERİN.
  • Kısıtlanmış bir dokümanın izin verilenler listesine beklenen harici gezinme hedeflerini (<a href>, <form action>, OAuth/SSO sağlayıcıları, ödeme kasaları) EKLEYİN ve varsayılan redirects=block değerinin, ; redirects=allow belirtilmediği sürece aynı kaynaklı 3xx yönlendirmelerini bile engellediğini unutmayın.
  • response-origin veya iç liste parametre değerlerini (; report-to=conn-reports, ; redirects=allow, ; webrtc=allow) tırnak içine ALMAYIN ya da (...) içindeki öğeleri virgülle ayırmayın.
  • URL kalıplarında (\d+) gibi özel normal ifade grupları veya "/api/*" gibi göreli yollar KULLANMAYIN. Tarayıcının ağ hizmeti ayrıştırıcısı, şema ve ana makine adı içermeyen normal ifade gruplarını ve kalıplarını reddeder.
  • Yerleşik yaptırım, tarayıcılarda varsayılan olarak etkinleştirilmediğinden henüz üretimde deneysel <iframe connectionAllowlist="..."> özelliğine veya Allow-Connection-Allowlist-From yerleşik yaptırım başlığına GÜVENMEYİN.

Yedek strateji

Connection-Allowlist, bir HTTP yanıt başlığı aracılığıyla tarayıcı ağ katmanında uygulandığından istemci tarafı JavaScript'te polyfill uygulanamaz. Temel hedefiniz henüz Connection-Allowlist özelliğini desteklemeyen tarayıcılar içeriyorsa Connection-Allowlist özelliğini Content-Security-Policy (CSP) başlığıyla eşleyin (ve HTTP yanıt başlıkları yapılandırılamadığında belge düzeyinde getirme ve alt kaynak kısıtlamaları için <meta http-equiv="Content-Security-Policy"> öğesi kullanın):

  • Eklemeli derinlemesine savunma: Destekleyen tarayıcılar her iki politikayı da aynı anda uygular. Connection-Allowlist, İGP dışı veri sızdırma kanallarını (dns-prefetch, preconnect, WebRTC ve yönlendirmeler) engellerken İGP, Connection-Allowlist olmayan tarayıcılarda fetch(), alt kaynaklar ve <form action> gezinmelerini kısıtlar.
  • form-action ve worker-src öğelerini açıkça kapsayın: CSP'de default-src, <form action> gönderimleri için yedek olarak kullanılmaz; form-action öğesini her zaman açıkça ekleyin. Kısıtlanmış sayfaların, belge düzeyindeki kısıtlamaları atlamak için blob: SharedWorker'lar oluşturamaması amacıyla worker-src 'self' (blob: hariç) ekleyin.
  • İzin verilen kaynakları kaynak başına İGP yönergeleriyle eşleyin: Tek bir uç nokta listesi kullanan ve satır içi işaretlemeyi yönetmeyen Connection-Allowlist'nin aksine, İGP'deki default-src 'self' her kaynak türünü (connect-src, img-src, style-src, font-src, script-src) kısıtlar ve karma veya tek seferlik rastgele sayılar aracılığıyla yetkilendirilmediği sürece satır içi <style> ve <script> bloklarını engeller.
  • İGP connect-src'ye wss:// ekleyin: Connection-Allowlist, wss:// bağlantılarını "https://..." kalıplarıyla eşleştirirken tarayıcılar arası ortamlardaki İGP connect-src, WebSockets kullanıldığında wss:// kaynaklarını açıkça listelemelidir.
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'"
/>