Subresource Integrity, CORS এবং Referrer Policy-এর মাধ্যমে এক্সটার্নাল সাবরিসোর্স সুরক্ষিত করা

থার্ড-পার্টি CDN বা এক্সটার্নাল অরিজিন থেকে স্ক্রিপ্ট, স্টাইলশিট, ফন্ট বা ছবি লোড করার সময়, সাবরিসোর্স ইন্টিগ্রিটি (HTML-এ integrity এবং CSS-এ integrity() url()) ব্যবহার করে সেগুলির ক্রিপ্টোগ্রাফিক ইন্টিগ্রিটি যাচাই করুন, ক্রস-অরিজিন ক্রেডেনশিয়াল ম্যানেজমেন্ট (crossorigin এবং cross-origin()) কনফিগার করুন এবং আউটগোয়িং Referer হেডার (referrerpolicy এবং referrer-policy()) সীমিত করুন। ডকুমেন্ট-লেভেল কন্টেন্ট সুরক্ষা সংক্রান্ত নীতি এবং আউটবাউন্ড কানেকশন অনুমোদিত তালিকার জন্য, ওয়েব সুরক্ষা এবং কানেকশন অনুমোদিত তালিকার মাধ্যমে আউটবাউন্ড নেটওয়ার্ক কানেকশন সীমিত করা দেখুন।

১. অপরিবর্তনীয় অ্যাসেটের জন্য ক্রিপ্টোগ্রাফিক ইন্টিগ্রিটি ডাইজেস্ট জেনারেট করা

সাবরিসোর্স ইন্টিগ্রিটি (SRI) নিশ্চিত করে যে ব্রাউজার শুধুমাত্র তখনই কোনও এক্সটার্নাল অ্যাসেট এক্সিকিউট বা রেন্ডার করে যখন এর বাইট আপনার অ্যাপ্লিকেশনের মাধ্যমে ঘোষিত ক্রিপ্টোগ্রাফিক হ্যাশের সাথে মিলে যায়।

  • বিল্ড বা রিলিজের সময় ডাইজেস্ট গণনা করুন, লাইভ বিশ্বস্ত নয় এমন উত্তর থেকে কখনও নয়: আপনার বিল্ড বা রিলিজের সময় পরিচিত-ভাল লোকাল অ্যাসেট বাইট থেকে ইন্টিগ্রিটি হ্যাশ জেনারেট করুন (অথবা প্যাকেজ লেখকের প্রকাশিত অফিসিয়াল ডাইজেস্ট কপি করুন) এবং আপনার HTML, CSS বা জাভাস্ক্রিপ্টে সেগুলি হার্ডকোড করুন। রানটাইমে ব্রাউজারে কখনও থার্ড-পার্টি URL ফেচ করবেন না যাতে নিজস্ব integrity হ্যাশ গণনা করা যায়, যা রিমোট সার্ভার থেকে পাওয়া যেকোনও বাইটকে বিশ্বাস করে এবং SRI-কে পরাস্ত করে।
  • ডিফল্ট হিসেবে sha384- ব্যবহার করুন: sha384- (সাজেস্ট করা ডিফল্ট), sha512- অথবা sha256- ব্যবহার করে <algorithm>-<base64-digest> হিসেবে ইন্টিগ্রিটি মেটাডেটা ফর্ম্যাট করুন।
  • এক্সটার্নাল সাবরিসোর্সকে অপরিবর্তনীয়, ভার্সন করা URL-এ পিন করুন: পরিবর্তনশীল ফ্লোটিং URL (latest/app.min.js)-এর পরিবর্তে সবসময় অপরিবর্তনীয়, ভার্সন-পিন করা বা কন্টেন্ট-অ্যাড্রেস করা URL (যেমন https://cdn.example.com/ui@2.4.0/app.min.js) ব্যবহার করে থার্ড-পার্টি অ্যাসেট রেফারেন্স করুন, যা আপনার সাইটকে সাইলেন্ট সাপ্লাই-চেন পরিবর্তনের জন্য এক্সপোজ করে এবং রিমোট ফাইল আপডেট হওয়ার সাথে সাথেই SRI ব্রেক করে। আপনাকে যদি এমন কোনও পরিবর্তনশীল থার্ড-পার্টি স্ক্রিপ্ট লোড করতে হয় যা নিজের জায়গায় আপডেট হয় (যেমন, কোনও ভেন্ডর অ্যানালিটিক্স ট্যাগ), তাহলে সেই URL-এ integrity বাদ দিন এবং পরিবর্তে কন্টেন্ট নিরাপত্তা নীতি (strict-dynamic / ননসেস) বা iframe স্যান্ডবক্সিংয়ের মাধ্যমে এটি সীমাবদ্ধ করুন।
  • এন্ডপয়েন্ট একাধিক বৈধ পেলোড পরিবেশন করলে, স্পেস দিয়ে একাধিক ডাইজেস্ট আলাদা করুন: SRI সমর্থন করে এমন সব ব্রাউজার sha256-, sha384- এবং sha512- প্রয়োগ করে। কোনও URL যদি বৈধভাবে একাধিক পরিচিত-ভাল পেলোডের মধ্যে একটি ফেরত দেয় (যেমন, A/B টেস্ট, স্টেজড CDN রোলআউট বা কন্টেন্ট-নেগোশিয়েটেড বিল্ডের সময়), তাহলে একই অ্যালগরিদমের একাধিক ডাইজেস্টের তালিকা তৈরি করুন এবং সেগুলিকে হোয়াইটস্পেস দিয়ে আলাদা করুন (যেমন, integrity="sha384-variantA... sha384-variantB...")। ব্রাউজার উত্তরটি গ্রহণ করে যদি এটি উপস্থিত সবচেয়ে শক্তিশালী অ্যালগরিদমের তালিকাভুক্ত যেকোনও ডাইজেস্টের সাথে মেলে।
  • CORS হেডার সহ ক্রস-অরিজিন সাবরিসোর্স পরিবেশন করা: ব্রাউজার শুধুমাত্র তখনই ক্রস-অরিজিন রেসপন্সের ইন্টিগ্রিটি যাচাই করতে পারে যখন এটি CORS-রিডেবল হয় (বেনামী অনুরোধের জন্য Access-Control-Allow-Origin: *, অথবা ক্রেডেনশিয়াল করা অনুরোধের জন্য Access-Control-Allow-Credentials: true সহ সঠিক অনুরোধকারী অরিজিন)।
# Generate a sha384- SRI hash at build time from a known-good local file
printf "sha384-%s\n" "$(openssl dgst -sha384 -binary app.min.js | openssl base64 -A)"
// Build-time / tooling helper (Node.js 19+, Deno, Bun) to compute an SRI string from known-good local bytes
export async function computeIntegrityMetadata(bytes) {
  const rawBytes =
    typeof bytes === 'string' ? new TextEncoder().encode(bytes) : bytes;
  const digestBuffer = await crypto.subtle.digest('SHA-384', rawBytes);
  const binary = Array.from(new Uint8Array(digestBuffer), (b) =>
    String.fromCharCode(b),
  ).join('');
  return `sha384-${btoa(binary)}`;
}

২. HTML স্ক্রিপ্ট, স্টাইলশিট এবং প্রিলোড সুরক্ষিত করা

বেসলাইন ডিফল্ট হিসেবে ডকুমেন্ট-লেভেল Referrer-Policy: strict-origin-when-cross-origin HTTP রেসপন্স হেডার সেট করুন (অথবা <head>-এ <meta name="referrer" content="strict-origin-when-cross-origin"> অন্তর্ভুক্ত করুন) যাতে কোনও সাবরিসোর্স বা CSS url() যা প্রতি-অনুরোধ রেফারার মডিফায়ার বাদ দেয়, তা এখনও ক্রস-অরিজিন অনুরোধে সংবেদনশীল পাথ এবং কোয়েরি প্যারামিটার বাদ দেয়।

তারপর, এক্সটার্নাল <script>, <link rel="stylesheet">, <link rel="preload"> ও <link rel="modulepreload"> এলিমেন্টে, সবসময় integrity, crossorigin ও referrerpolicy একসাথে সেট করুন:

  • integrity="sha384-...": রেসপন্স বাইট প্রত্যাশিত ডাইজেস্টের সাথে না মিললে রিসোর্সের এক্সিকিউশন বা অ্যাপ্লিকেশন ব্লক করে (ব্লক করা <link rel="stylesheet">-এর জন্য, linkElement.sheet null থাকে এবং একটি error ইভেন্ট ফায়ার করে)।
  • crossorigin="anonymous" (পাবলিক CDN অ্যাসেটের জন্য সাজেস্ট করা ডিফল্ট) অথবা crossorigin="use-credentials": ক্রস-অরিজিন রিসোর্সে integrity সেট করা থাকলে এটি প্রয়োজন। crossorigin বাদ দিলে ব্রাউজার no-cors মোডে রিসোর্স নিয়ে আসে (একটি অস্বচ্ছ উত্তর তৈরি করে যা হ্যাশ-যাচাই করা যায় না) এবং রিসোর্স ব্লক করে।
  • referrerpolicy="no-referrer" (থার্ড-পার্টি CDN-এর জন্য সাজেস্ট করা ডিফল্ট) অথবা referrerpolicy="strict-origin-when-cross-origin": থার্ড-পার্টি হোস্টের কাছে Referer অনুরোধের হেডারে কোনও উৎস বা পাথ সংক্রান্ত তথ্য ফাঁস হওয়া আটকাতে ডকুমেন্ট রেফারার নীতি ওভাররাইড করে।
<!-- Document-level baseline referrer policy in <head> (or via Referrer-Policy HTTP header) -->
<meta name="referrer" content="strict-origin-when-cross-origin" />

<!-- External stylesheet with SRI, anonymous CORS, and no referrer -->
<link
  rel="stylesheet"
  href="https://cdn.example.com/ui@2.4.0/styles.css"
  integrity="sha384-bs0EksOYBbU/g0IAHMXbSyH1Mgw83GKn+EZlSI+VE+zVchXmSbLwhVLrPzsOWnc5"
  crossorigin="anonymous"
  referrerpolicy="no-referrer"
/>

<!-- External script with SRI, anonymous CORS, and no referrer -->
<script
  src="https://cdn.example.com/ui@2.4.0/bundle.min.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"
  referrerpolicy="no-referrer"
></script>

fetch()-এর মাধ্যমে প্রোগ্রাম্যাটিক্যালি এক্সটার্নাল সাবরিসোর্স ফেচ করার সময়, ম্যাচিং integrity, mode, credentials ও referrerPolicy বিকল্প পাস করুন (ডাইজেস্ট মিসম্যাচ হলে TypeError-এর মাধ্যমে প্রমিস বাতিল করা হয়):

const response = await fetch('https://cdn.example.com/ui@2.4.0/manifest.json', {
  integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC',
  mode: 'cors',
  credentials: 'omit',
  referrerPolicy: 'no-referrer',
});

৩. url() মডিফায়ার ব্যবহার করে CSS ছবি, ফন্ট ও ইমপোর্ট করা স্টাইলশিট সুরক্ষিত করুন

CSS url() অনুরোধ মডিফায়ার (cross-origin(), referrer-policy() এবং integrity()) সরাসরি CSS থেকে CSS-লোড করা রিসোর্সের ফেচ মোড, রেফারার হেডার এবং সাবরিসোর্স ইন্টিগ্রিটি যাচাইকরণ নিয়ন্ত্রণ করে—যার মধ্যে background-image, content, mask-image, border-image-source, filter, @font-face src এবং @import নিয়ম অন্তর্ভুক্ত:

মডিফায়ার ফাংশন আর্গুমেন্ট সিনট্যাক্স ও অনুমোদিত ভ্যালু উদ্দেশ্য
cross-origin(<ident>) কোটেশন চিহ্ন ছাড়া কীওয়ার্ড: anonymous (সাজেস্ট করা ডিফল্ট) অথবা use-credentials CORS ব্যবহার করে CSS রিসোর্স ফেচ করে এবং ক্রেডেনশিয়াল পাঠানো হয়েছে কিনা তা নিয়ন্ত্রণ করে। ক্রস-অরিজিন CSS রিসোর্সে সবসময় integrity()-এর সাথে অন্তর্ভুক্ত করুন।
referrer-policy(<ident>) কোটেশন ছাড়া কীওয়ার্ড: no-referrer (থার্ড-পার্টি অ্যাসেটের জন্য সাজেস্ট করা ডিফল্ট), strict-origin-when-cross-origin, origin, same-origin, strict-origin, origin-when-cross-origin, no-referrer-when-downgrade, অথবা unsafe-url এই নির্দিষ্ট CSS অনুরোধের জন্য ডকুমেন্ট রেফারার নীতি ওভাররাইড করে।
integrity("<string>") কোট করা স্ট্রিং: "sha384-<base64>" (অথবা স্পেস দিয়ে আলাদা করা ডাইজেস্ট "sha384-... sha384-...") রেন্ডার বা প্রয়োগ করার আগে প্রত্যাশিত ক্রিপ্টোগ্রাফিক ডাইজেস্টের সাথে ফেচ করা CSS রিসোর্স যাচাই করে।

url() মডিফায়ার তৈরি করার সময় এই CSS সিনট্যাক্স ও নিরাপত্তা সংক্রান্ত নিয়ম মেনে চলুন:

  • url("...")-এর মধ্যে URL স্ট্রিং কোট করুন: url(...)-এর মধ্যে URL কোট করা <string> হলে তবেই অনুরোধ মডিফায়ার বৈধ হয় (কোট না করা URL মডিফায়ার ব্যবহার করতে পারে না)।
  • প্রতি url()-এ সর্বাধিক একবার প্রতিটি মডিফায়ার টাইপ উল্লেখ করুন: আপনি url("..." ...)-এর মধ্যে যেকোনও ক্রমে cross-origin(), referrer-policy() এবং integrity() একত্রিত করতে পারেন, তবে একই মডিফায়ার টাইপ দুবার পুনরাবৃত্তি করা (যেমন দুটি integrity() মডিফায়ার) অবৈধ। একাধিক হ্যাশ দিতে, একটি integrity("sha384-... sha384-...") স্ট্রিংয়ের মধ্যে স্পেস দিয়ে সেগুলি আলাদা করুন।
  • ক্রস-অরিজিন integrity()-এর সাথে সবসময় cross-origin(anonymous) পেয়ার করুন: @font-face src এবং mask-image আগে থেকেই ডিফল্ট হিসেবে পরিচয় গোপন করা cors মোডে ফেচ করলেও, background-image, content, border-image-source এবং @import ডিফল্ট হিসেবে no-cors মোডে (অস্বচ্ছ উত্তর তৈরি করে যা হ্যাশ-যাচাই করা যায় না) এবং এক্সটার্নাল SVG filter ডিফল্ট হিসেবে same-origin মোডে (ক্রস-অরিজিন URL সরাসরি ব্লক করে) ফেচ করে। cross-origin(anonymous) (বা cross-origin(use-credentials)) অন্তর্ভুক্ত করলে, প্রতিটি CSS প্রপার্টি জুড়ে CORS যাচাইকরণ নিশ্চিত করা হয়।
  • @import এবং @font-face নিয়মে url("...")-এর মধ্যে মডিফায়ার রাখুন: url("..." ...)-এর মধ্যে cross-origin(), referrer-policy() এবং integrity() রাখুন, তবে format("woff2") এবং tech(...)-এর মতো ফন্ট ডেসক্রিপ্টর url(...)-এর বাইরে রাখুন।
@import url(
  "https://cdn.example.com/theme@1.2.0/tokens.css"
  cross-origin(anonymous)
  referrer-policy(no-referrer)
  integrity("sha384-bs0EksOYBbU/g0IAHMXbSyH1Mgw83GKn+EZlSI+VE+zVchXmSbLwhVLrPzsOWnc5")
);

@font-face {
  font-family: "BrandSans";
  src: url(
    "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2"
    cross-origin(anonymous)
    referrer-policy(no-referrer)
    integrity("sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC")
  ) format("woff2");
  font-display: swap;
}

.verified-graphic {
  background-image: url(
    "https://cdn.example.com/media@1.0.0/badge.svg"
    cross-origin(anonymous)
    referrer-policy(no-referrer)
    integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91")
  );
}

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

আপনার বেসলাইন টার্গেট এখনও CSS url() অনুরোধ মডিফায়ার (cross-origin(), referrer-policy() বা integrity()) সাপোর্ট না করলে, ক্যাসকেডিং CSS ডিক্লারেশনের সাথে ডকুমেন্ট-লেভেল নীতি একত্রিত করুন যাতে সাবরিসোর্স কার্যকরী ও সুরক্ষিত থাকে:

  1. ক্রস-ব্রাউজার SRI-এর প্রয়োজন হলে CSS @import-এর পরিবর্তে HTML <link rel="stylesheet"> ব্যবহার করুন: কারণ <link rel="stylesheet">-এ subresource-integrity ও referrer-policy সব ব্রাউজারে ব্যাপকভাবে উপলভ্য, তাই প্রতিটি ব্রাউজারে ইন্টিগ্রিটি যাচাইকরণ এনফোর্স করতে হলে, @import-এর পরিবর্তে <link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer"> ব্যবহার করে এক্সটার্নাল স্টাইলশিট লোড করুন।
  2. ডকুমেন্ট-লেভেল Referrer-Policy হেডার বা ২ নম্বর ধাপ থেকে <meta name="referrer">-এর উপর নির্ভর করুন: যেসব ব্রাউজার CSS url()-এর মধ্যে referrer-policy() সাপোর্ট করে না, সেগুলিতে ডকুমেন্ট-লেভেল Referrer-Policy: strict-origin-when-cross-origin হেডার বা <meta name="referrer" content="strict-origin-when-cross-origin"> এলিমেন্ট ক্রস-অরিজিন CSS অনুরোধে সংবেদনশীল পাথ ও কোয়েরি প্যারামিটার অটোমেটিক বাদ দিয়ে দেয়।
  3. প্রোগ্রেসিভ এনহ্যান্সমেন্টের জন্য আলাদা CSS ডিক্লারেশন ক্যাসকেড করুন: যেসব ব্রাউজার url("..." ...)-এর মধ্যে মডিফায়ার শনাক্ত করতে পারে না, সেগুলি CSS পার্স করার সময় সম্পূর্ণ ডিক্লারেশন বাতিল করে দেয়। প্রথমে একটি সাধারণ url("...") ঘোষণা করুন, তারপরে একটি cross-origin() এবং referrer-policy() ঘোষণা (যেসব ব্রাউজার ওই দুটি মডিফায়ার সাপোর্ট করে কিন্তু integrity() করে না), তারপরে integrity("sha384-...") সহ সম্পূর্ণ ঘোষণা:

    • কখনও একই কমা দ্বারা আলাদা করা @font-face src তালিকায় যাচাই না করা url() রাখবেন না: একটি src ডেসক্রিপ্টরে src: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2"); লিখলে রানটাইমে সাবরিসোর্স ইন্টিগ্রিটি বাইপাস হয়ে যায়—যদি কোনও CDN এমন কোনও ফন্ট দেখায় যা integrity() চেক করতে ব্যর্থ হয়, তাহলে ব্রাউজার এটিকে লোড করতে ব্যর্থ হয়েছে বলে মনে করে এবং অটোমেটিক দ্বিতীয় কমা দ্বারা আলাদা করা url("font.woff2")-এ ফিরে যায়। আলাদা src: ডিক্লারেশন ব্যবহার করলে, এটি নিশ্চিত করা যায় যে কাজ করে এমন ব্রাউজার CSS পার্স করার সময় যাচাই না করা ডিক্লারেশন ওভাররাইট করে এবং রানটাইমে ট্যাম্পার করা ফন্ট ব্লক করে।
    @font-face {
      font-family: "BrandSans";
      /* 1. Parse-time fallback for browsers without url() modifier support */
      src: url("https://cdn.example.com/fonts@1.0.0/brand-sans.woff2") format("woff2");
      /* 2. Parse-time fallback for browsers supporting cross-origin() and referrer-policy() */
      src: url(
        "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2"
        cross-origin(anonymous)
        referrer-policy(no-referrer)
      ) format("woff2");
      /* 3. Full protection in browsers supporting cross-origin(), referrer-policy(), and integrity() */
      src: url(
        "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2"
        cross-origin(anonymous)
        referrer-policy(no-referrer)
        integrity("sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC")
      ) format("woff2");
      font-display: swap;
    }
    
    .verified-graphic {
      background-image: url("https://cdn.example.com/media@1.0.0/badge.svg");
      background-image: url(
        "https://cdn.example.com/media@1.0.0/badge.svg"
        cross-origin(anonymous)
        referrer-policy(no-referrer)
      );
      background-image: url(
        "https://cdn.example.com/media@1.0.0/badge.svg"
        cross-origin(anonymous)
        referrer-policy(no-referrer)
        integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91")
      );
    }
    
  4. জাভাস্ক্রিপ্টে CSS url() মডিফায়ার সাপোর্ট ফিচার-ডিটেক্ট করুন CSS.supports()-এর মাধ্যমে:

export function getCssUrlModifierSupport() {
  const supportsCrossOriginAndReferrer =
    typeof CSS !== 'undefined' &&
    typeof CSS.supports === 'function' &&
    CSS.supports(
      'background-image',
      'url("https://example.com/a.svg" cross-origin(anonymous) referrer-policy(no-referrer))',
    );

  const supportsUrlIntegrity =
    typeof CSS !== 'undefined' &&
    typeof CSS.supports === 'function' &&
    CSS.supports(
      'background-image',
      'url("https://example.com/a.svg" cross-origin(anonymous) referrer-policy(no-referrer) integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91"))',
    );

  return { supportsCrossOriginAndReferrer, supportsUrlIntegrity };
}