কানেকশন অনুমোদিত তালিকার মাধ্যমে আউটবাউন্ড নেটওয়ার্ক কানেকশন সীমাবদ্ধ করা

Connection-Allowlist HTTP রেসপন্স হেডার কোনও ডকুমেন্ট বা ওয়ার্কারকে নেটওয়ার্ক এন্ডপয়েন্টের একটি নির্দিষ্ট তালিকার সাথে যোগাযোগ করার ক্ষেত্রে সীমাবদ্ধ করে। কন্টেন্ট নিরাপত্তা নীতি (CSP)—যার জন্য প্রতিটি রিসোর্স অনুযায়ী আলাদা নির্দেশিকা (connect-src, img-src, script-src, font-src, form-action) বজায় রাখতে হয় এবং যা <link rel="dns-prefetch">, <link rel="preconnect"> বা WebRTC-এর মতো স্পেকুলেটিভ বা নন-ফেচ নেটওয়ার্ক চ্যানেল নিয়ন্ত্রণ করে না—তার থেকে আলাদাভাবে Connection-Allowlist, কনটেক্সট (fetch(), XMLHttpRequest, সাবরিসোর্স, navigator.sendBeacon(), <a ping>, টপ-লেভেল ও ফ্রেম নেভিগেশন, রিসোর্স হিন্ট, স্পেকুলেশন রুল, WebSocket, WebTransport ও WebRTC) দ্বারা শুরু করা সব আউটবাউন্ড কানেকশন জুড়ে একটি একক, নেটওয়ার্ক-লেয়ার সাদাতালিকা প্রয়োগ করে।

কীভাবে প্রয়োগ করতে হয়

  1. 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-কে শুধুমাত্র রুট পাথ /-এর সাথে ম্যাচ করার জন্য সীমাবদ্ধ করে)।
  2. Sec-Fetch-Dest-এর মাধ্যমে মিডলওয়্যার স্কোপ করুন এবং ওয়ার্কার কভার করুন: ব্রাউজার শুধুমাত্র ডকুমেন্ট/নেভিগেশন রেসপন্স এবং ওয়ার্কার স্ক্রিপ্ট রেসপন্সে (Sec-Fetch-Dest ভ্যালু document, iframe, worker, sharedworker এবং serviceworker) Connection-Allowlist-কে সম্মান করে। Sec-Fetch-Dest-এর মাধ্যমে হেডার পরিবর্তন করার সময়, Vary: Sec-Fetch-Dest অন্তর্ভুক্ত করুন যাতে শেয়ার করা ক্যাশে হেডারলেস রেসপন্স পরিবেশন না করে। ডেডিকেটেড ও শেয়ার করা ওয়ার্কার স্ক্রিপ্টে একটি ম্যাচিং সাদাতালিকা পরিবেশন করুন যাতে আপোস করা পৃষ্ঠা স্ক্রিপ্ট worker.postMessage()-এর মাধ্যমে ডেটা এক্সফিল্ট্রেট করতে না পারে। যেহেতু কোনও সার্ভিস ওয়ার্কার ক্যাশে মিস ও নেভিগেশন প্রিলোডের ক্ষেত্রে নিজস্ব নেটওয়ার্ক ফেচ পারফর্ম করে, তাই এটি নিয়ন্ত্রণ করে এমন সব ডকুমেন্টের অনুমোদিত তালিকার সুপারসেট হিসেবে এর Connection-Allowlist কনফিগার করুন।
  3. লঙ্ঘন সংক্রান্ত রিপোর্টিং এবং স্টেজড বা ডুয়াল রোলআউট কনফিগার করা: 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")।
  4. ডিফল্ট রিডাইরেক্ট ও WebRTC ব্লক করার জন্য অ্যাকাউন্ট এবং ক্লায়েন্ট সংক্রান্ত সমস্যার সমাধান করা: ডিফল্ট হিসেবে, Connection-Allowlist সব 3xx HTTP রিডাইরেক্ট (redirects=block, এমনকি সাদা তালিকায় থাকা বা একই-উৎসের URL-এর মধ্যে রিডাইরেক্ট করার সময়ও) এবং সব RTCPeerConnection ট্রাফিক (webrtc=block) ব্লক করে। শুধুমাত্র বিশ্বস্ত এন্ডপয়েন্টের জন্য সার্ভার-সাইড রিডাইরেক্টের প্রয়োজন হলে (প্রাথমিক অনুরোধটি এখনও সাদা তালিকার সাথে মিলতে হবে) ; redirects=allow যোগ করুন অথবা WebRTC-এর প্রয়োজন হলে ; webrtc=allow যোগ করুন। ক্লায়েন্টে, আউটবাউন্ড fetch() কল try...catch-এ র‍্যাপ করুন এবং WebSocket error ইভেন্ট ম্যানেজ করুন যাতে TypeError (net::ERR_NETWORK_ACCESS_REVOKED) সহ বাতিল করা অনুরোধ ম্যানেজ করা হয়েছে এমন সমস্যা সংক্রান্ত স্ট্যাটাস ফিরিয়ে দেয় এবং ম্যানেজ করা হয়নি এমন বাতিলকরণের পরিবর্তে UI স্ট্যাটাস আপডেট করে।

কোডের উদাহরণ

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

পেশাদার পদ্ধতি

  • DO format Connection-Allowlist as a single RFC 9651 Inner List (...) of space-separated items. খালি নয় এমন Connection-Allowlist হেডার টপ-লেভেল স্ট্রাকচার্ড ফিল্ড পার্সিং (যেমন, (...) বাদ দেওয়া বা সিঙ্গল কোট '...' ব্যবহার করা) করতে না পারলে, ব্রাউজার খালি অনুমোদিত তালিকা সহ বন্ধ হয়ে যায় এবং সব আউটবাউন্ড নেটওয়ার্ক কানেকশন ব্লক করে দেয়। লোড করার পরে আপনি ইচ্ছাকৃতভাবে সব নেটওয়ার্ক অ্যাক্সেস তুলে নিতে চাইলে Connection-Allowlist: () ব্যবহার করুন (লোকাল স্কিম data:, blob:, about: এবং filesystem: অনুমোদিত থাকবে)।
  • অরিজিন-ওয়াইড URL প্যাটার্নে ("https://api.example.com") ট্রেলিং স্ল্যাশ বাদ দেবেন অথবা একটি এক্সপ্লিসিট ওয়াইল্ডকার্ড ("https://api.example.com/*") ব্যবহার করবেন যাতে /v1/users-এর মতো সাবপাথ অনুমোদিত হয়। প্যাটার্ন সিরিয়ালাইজ করার আগে new URL(origin).href-এর যোগ করা ট্রেলিং স্ল্যাশ স্ট্রিপ করুন।
  • Connection-Allowlist-এ "http://..." এবং "https://..." প্যাটার্ন ব্যবহার করে WebSocket (ws:// এবং wss://) এন্ডপয়েন্টকে অনুমতি তালিকায় রাখুন, কারণ ব্রাউজার মেলানোর আগে 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/*"-এর মতো রিলেটিভ পাথ ব্যবহার করবেন না; ব্রাউজারের নেটওয়ার্ক-পরিষেবা পার্সার, স্কিম ও হোস্টনেম ছাড়া regex গ্রুপ ও প্যাটার্ন বাতিল করে দেয়।
  • প্রোডাকশনে এখনও পরীক্ষামূলক <iframe connectionAllowlist="..."> অ্যাট্রিবিউট বা Allow-Connection-Allowlist-From এম্বেড করা এনফোর্সমেন্ট হেডারের উপর নির্ভর করবেন না, কারণ শিপিং ব্রাউজারে ডিফল্ট হিসেবে এম্বেড করা এনফোর্সমেন্ট চালু করা থাকে না।

ফলব্যাক স্ট্র্যাটেজি

যেহেতু HTTP রেসপন্স হেডারের মাধ্যমে ব্রাউজার নেটওয়ার্ক লেয়ারে Connection-Allowlist এনফোর্স করা হয়, তাই ক্লায়েন্ট-সাইড জাভাস্ক্রিপ্টে এটি পলিফিল করা যায় না। আপনার বেসলাইন টার্গেটে যদি এমন ব্রাউজার অন্তর্ভুক্ত থাকে যা এখনও Connection-Allowlist, পেয়ার Connection-Allowlist-কে Content-Security-Policy (CSP) হেডারের সাথে (এবং HTTP রেসপন্স হেডার কনফিগার করা না গেলে ডকুমেন্ট-লেভেল ফেচ এবং সাবরিসোর্স সংক্রান্ত বিধিনিষেধের জন্য একটি <meta http-equiv="Content-Security-Policy"> এলিমেন্ট) সাপোর্ট করে না:

  • অ্যাডিটিভ বিবিধ প্রতিরোধ ব্যবস্থা: যেসব ব্রাউজার এই সুবিধা প্রদান করে সেগুলি দুটি নীতিই একসাথে প্রয়োগ করে—Connection-Allowlist নন-CSP এক্সফিলট্রেশন চ্যানেল (dns-prefetch, preconnect, WebRTC ও রিডাইরেক্ট) ব্লক করে, যেখানে CSP Connection-Allowlist ছাড়া ব্রাউজারে fetch(), সাবরিসোর্স ও <form action> নেভিগেশন সীমাবদ্ধ করে।
  • form-action এবং worker-src স্পষ্টভাবে কভার করুন: CSP-তে, <form action> জমা দেওয়ার জন্য default-src ফলব্যাক করে না; সবসময় স্পষ্টভাবে form-action অন্তর্ভুক্ত করুন। worker-src 'self' (blob: বাদ দিয়ে) অন্তর্ভুক্ত করুন যাতে বিধিনিষেধযুক্ত পৃষ্ঠা ডকুমেন্ট-লেভেল বিধিনিষেধ বাইপাস করার জন্য blob: SharedWorkers স্পন করতে না পারে।
  • প্রতি-রিসোর্স CSP ডিরেক্টিভে অনুমোদিত অরিজিন ম্যাপ করুন: Connection-Allowlist-এর (যা একটি সিঙ্গেল এন্ডপয়েন্ট তালিকা ব্যবহার করে এবং ইনলাইন মার্কআপ পরিচালনা করে না) মতো নয়, CSP-তে default-src 'self' প্রতিটি রিসোর্সের ধরন (connect-src, img-src, style-src, font-src, script-src) সীমাবদ্ধ করে এবং হ্যাশ বা ননসের মাধ্যমে অনুমোদিত না হলে ইনলাইন <style> এবং <script> ব্লক করে।
  • CSP connect-src-এ wss:// অন্তর্ভুক্ত করুন: Connection-Allowlist, "https://..." প্যাটার্নের সাথে wss:// কানেকশন ম্যাচ করলেও, ক্রস-ব্রাউজার এনভায়রনমেন্টে CSP connect-src-এর ক্ষেত্রে WebSocket ব্যবহার করা হলে, wss://-এর সোর্স স্পষ্টভাবে তালিকাভুক্ত করতে হবে।
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'"
/>