ওয়েব অরিজিন ও সাইট তুলনা ও যাচাই করা

ওয়েব ফিচার: অরিজিন

সহজ অনুমোদিত তালিকার জন্য একটি নির্দিষ্ট, পোর্ট-নরমালাইজড স্ট্রিং লিটারালের (যেমন event.origin === 'https://app.example.com') সাথে event.origin তুলনা করা কাজ করলেও, ইচ্ছামতো URL, DOM লিঙ্ক এলিমেন্ট, ক্রস-সাবডোমেন রিডাইরেক্ট বা স্যান্ডবক্স করা মেসেজ যাচাই করার সময় ম্যানুয়াল স্ট্রিং তুলনা এবং রেগুলার এক্সপ্রেশন কাজ করে না:

  • প্রিফিক্স ও সাবস্ট্রিং স্পুফিং: url.startsWith('https://app.example.com') চেক করা 'https://app.example.com.attacker.example'-এর মাধ্যমে বাইপাস করা হয়।
  • আননরমালাইজড URL: পাথ, কোয়েরি স্ট্রিং বা এক্সপ্লিসিট ডিফল্ট পোর্ট ('https://app.example.com:443/path') সহ সম্পূর্ণ URL প্রথমে পার্স ও নরমালাইজ না করে স্ট্রিং ইকুয়ালিটির অধীনে 'https://app.example.com'-এর সাথে মেলে না।
  • পাবলিক সাফিক্স তালিকার ভুল: একই সাইটের সম্পর্ক চেক করার জন্য .-এ হোস্টনেম স্প্লিট করা মাল্টি-লেবেল পাবলিক সাফিক্সে (যেমন .co.uk বা .github.io-এর মতো শেয়ার করা হোস্টিং ডোমেন) কাজ করে না এবং স্কিম অমিল (http: বনাম https:) উপেক্ষা করে।
  • অস্বচ্ছ "null" অরিজিন কলিশন: স্যান্ডবক্স করা iframe (<iframe sandbox="allow-scripts">) এবং data: URL, event.origin-কে আক্ষরিক স্ট্রিং "null"-এ সিরিয়ালাইজ করে। originA === originB-এর সাথে দুটি সিরিয়ালাইজড রানটাইম অরিজিন তুলনা করলে, দুটি সম্পর্কহীন অস্বচ্ছ প্রসঙ্গ ("null" === "null") ভুলভাবে সমান হয়ে যায়।

Origin API (Origin.from(), isSameOrigin(), isSameSite() এবং opaque) ব্রাউজারের URL পার্সার এবং পাবলিক সাফিক্স তালিকার মাধ্যমে স্ট্রাকচার্ড, স্পেক-কমপ্লায়েন্ট অরিজিন এবং স্কিমফুল একই-সাইটের তুলনা প্রদান করে।

আপনার পরিস্থিতির জন্য সঠিক তুলনা বেছে নেওয়া

  • কঠোরভাবে প্রিভিলেজ বাউন্ডারি প্রয়োগ করতে isSameOrigin() ব্যবহার করুন: ক্রস-উইন্ডো postMessage কমান্ড (যেমন, এম্বেড করা চেকআউট বা প্রমাণীকরণ পপ-আপ) ম্যানেজ করার সময় বা ক্রেডেনশিয়াল অ্যাটাচ করা হবে কিনা তা নির্ধারণ করার সময় (scheme, host, port)-এর সাথে সম্পূর্ণ মিল থাকা প্রয়োজন। এমনকি, আলাদা সাবডোমেনও (https://blog.example.com বনাম https://app.example.com) আলাদা উৎস হিসেবে বিবেচিত হয় এবং তা অবশ্যই বাতিল করতে হবে যাতে একটি সাবডোমেনের দুর্বলতা অন্যটিকে নিয়ন্ত্রণ করতে না পারে।
  • ক্রস-সাবডোমেন নেভিগেশন ও লিঙ্ক ক্লাসিফিকেশনের জন্য isSameSite() ব্যবহার করুন: লগ-ইন করার পরে রিডাইরেক্ট URL (?returnTo=https://billing.example.co.uk/orders) যাচাই করার সময় অথবা ইন্টার্নাল বনাম এক্সটার্নাল হিসেবে লিঙ্ক ক্লাসিফাই করার সময় একই স্কিম ও রেজিস্টার করা যায় এমন ডোমেন শেয়ার করে এমন যেকোনও সাবডোমেন বা পোর্টকে অনুমতি দিন। isSameSite(), http: স্কিম ডাউনগ্রেড এবং শেয়ার করা পাবলিক সাফিক্সে (.co.uk, .github.io) সিবলিং টেন্যান্ট, দুটিই বাতিল করে দেয়।
  • অস্বচ্ছ বা স্যান্ডবক্স করা প্রসঙ্গের জন্য origin.opaque এবং Origin.from(event) ব্যবহার করুন: রিডাইরেক্টের (origin.opaque === true) জন্য data: URL বাতিল করুন অথবা নির্দিষ্ট <iframe sandbox="allow-scripts"> উইজেটের সাথে যোগাযোগ করার সময় Origin.from(event)-এর মাধ্যমে এক্সট্র্যাক্ট করা অস্বচ্ছ Origin পিন করুন, যার সিরিয়ালাইজ করা event.origin হল "null"।

১. প্রিভিলেজড অ্যাকশনের জন্য সঠিক অরিজিন যাচাই করুন (Origin.from() এবং isSameOrigin())

Origin অবজেক্ট এক্সট্র্যাক্ট করতে Origin.from() ব্যবহার করুন এবং isSameOrigin() ব্যবহার করে বিশ্বস্ত Origin-এর সাথে তুলনা করুন:

  • উভয় পক্ষকে অবশ্যই Origin ইনস্ট্যান্স হতে হবে: isSameOrigin(other) এবং isSameSite(other) শুধুমাত্র একটি Origin অবজেক্ট গ্রহণ করে—একটি র স্ট্রিং পাস করলে একটি TypeError থ্রো করে।
  • কাজ করে এমন Origin.from() ইনপুট:
    • সিরিয়ালাইজ করা URL স্ট্রিং (যেমন, 'https://app.example.com:443/path')। মনে রাখবেন যে, যেকোনও ভুল URL স্ট্রিং—যেমন 'example', অথবা event.origin-এর মাধ্যমে সিরিয়ালাইজ করা 'null' স্ট্রিং, যা অস্বচ্ছ অরিজিনের জন্য ব্যবহার করা হয়—এর ফলে Origin.from()-এ TypeError দেখা যায়।
    • প্ল্যাটফর্ম অবজেক্ট যা HTML স্পেসিফিকেশনে অরিজিন-এক্সট্র্যাকশন ধাপগুলি সংজ্ঞায়িত করে: Origin, URL, HTMLAnchorElement (<a>), HTMLAreaElement (<area>), একই-অরিজিন Window বা WorkerGlobalScope (Origin.from(self)), এবং ব্রাউজার-ডিসপ্যাচ করা MessageEvent ইনস্ট্যান্স (Origin.from(event))।
    • Location, WorkerLocation, Document, Request এবং Response-এর মতো অবজেক্ট অরিজিন-এক্সট্র্যাকশন ধাপকে সংজ্ঞায়িত করে না এবং সরাসরি Origin.from()-এ পাস করা হলে TypeError থ্রো করে—পরিবর্তে self (বর্তমান প্রসঙ্গের জন্য) অথবা request.url / response.url পাস করুন।
  • ব্রাউজার-ডিসপ্যাচ করা এবং সিন্থেটিক MessageEvent ইনস্ট্যান্স, দুটিই ম্যানেজ করুন: আসল ব্রাউজার postMessage ইভেন্টে Origin.from(event)-এর মাধ্যমে এক্সট্র্যাক্ট করা ইন্টার্নাল অরিজিন থাকে, যেখানে ইউনিট টেস্টে তৈরি করা সিন্থেটিক new MessageEvent('message', { origin }) ইভেন্ট শুধুমাত্র event.origin স্ট্রিং পপুলেট করে এবং Origin.from(event)-এ থ্রো করে। try...catch-এর মধ্যে Origin.from(event.origin)-এ ফিরে যাওয়া, ভুল বা "null" স্ট্রিং বাতিল করার সময় দুটিই পরিষ্কারভাবে ম্যানেজ করে।
// Replace with your application's trusted origin(s)
const TRUSTED_ORIGIN = Origin.from('https://app.example.com');

function extractOrigin(candidate) {
  try {
    return Origin.from(candidate);
  } catch (err) {
    // Synthetic new MessageEvent('message', { origin }) objects in tests lack an
    // internal browser origin; fall back to parsing candidate.origin if it is a URL.
    if (typeof MessageEvent !== 'undefined' && candidate instanceof MessageEvent) {
      return Origin.from(candidate.origin);
    }
    throw err;
  }
}

export function isTrustedSameOrigin(candidate) {
  try {
    return extractOrigin(candidate).isSameOrigin(TRUSTED_ORIGIN);
  } catch {
    // Rejects invalid URLs, "null" strings, or unsupported objects
    return false;
  }
}

window.addEventListener('message', (event) => {
  if (!isTrustedSameOrigin(event)) return;

  // Safe to process privileged commands from the verified origin
});

রিডাইরেক্ট টার্গেট (যেমন ?returnTo= প্যারামিটার) বা একই রেজিস্টার করা যায় এমন ডোমেনের সাবডোমেন জুড়ে ইন্টার্নাল লিঙ্ক (যেমন, https://app.example.co.uk এবং https://billing.example.co.uk:8443) যাচাই করার সময় isSameSite() ব্যবহার করুন:

  • isSameSite() স্কিমফুল সেম-সাইট তুলনা করে: https://sub.example.com এবং http://sub.example.com সেম-সাইট নয় কারণ তাদের স্কিম আলাদা।
  • isSameSite() ব্রাউজারের বিল্ট-ইন সর্বজনীন সাফিক্স তালিকা ব্যবহার করে, তাই শেয়ার করা সর্বজনীন সাফিক্সে (যেমন https://tenant-a.github.io এবং https://tenant-b.github.io অথবা https://a.co.uk এবং https://b.co.uk) আলাদা আলাদা টেন্যান্ট false হিসেবে মূল্যায়ন করে।
// Replace with your application's primary origin
const APP_ORIGIN = Origin.from('https://app.example.co.uk');

export function isAllowedSameSiteUrl(candidateUrlOrElement) {
  try {
    return extractOrigin(candidateUrlOrElement).isSameSite(APP_ORIGIN);
  } catch {
    return false;
  }
}

// true: same scheme (https) and same registrable domain (example.co.uk)
isAllowedSameSiteUrl('https://billing.example.co.uk:8443/checkout');

// false: scheme downgrade (http vs. https) is rejected by schemeful same-site
isAllowedSameSiteUrl('http://billing.example.co.uk/checkout');

৩. অস্বচ্ছ উৎস (origin.opaque এবং Origin.from(event)) আলাদা করা এবং পিন করা

স্যান্ডবক্স করা iframe (<iframe sandbox="allow-scripts">), data: URL এবং new Origin() অস্বচ্ছ উৎস (origin.opaque === true) তৈরি করে। তাদের স্ট্রিং সিরিয়ালাইজেশন সবসময় "null" হলেও, Origin.from(event) ব্রাউজার-ডিসপ্যাচ করা MessageEvent থেকে প্রেরকের আসল অন্তর্নিহিত অস্বচ্ছ উৎস এক্সট্র্যাক্ট করে:

  • একই স্যান্ডবক্সড iframe থেকে পাঠানো দুটি মেসেজ Origin অবজেক্ট তৈরি করে যা একে অপরের সাথে isSameOrigin() (true)।
  • বিভিন্ন স্যান্ডবক্সড iframe—বা আলাদা Origin.from('data:...') / new Origin() কল—থেকে পাঠানো মেসেজ স্বতন্ত্র অস্বচ্ছ অরিজিন তৈরি করে যা isSameOrigin() থেকে false পর্যন্ত মূল্যায়ন করে, যা সম্পর্কহীন অস্বচ্ছ প্রসঙ্গ জুড়ে "null" === "null" স্পুফিং প্রতিরোধ করে।
  • new Origin() একটি নতুন, অনন্য অস্বচ্ছ অরিজিন তৈরি করে যা নিজের ছাড়া অন্য কিছুর সাথে মেলে না, যা প্রত্যাশিত পিয়ার অরিজিন একবার ইনিশিয়ালাইজেশনে পিন করার আগে ডিফল্ট সব-অস্বীকার সেন্টিনেল হিসেবে উপযোগী।
// Initialize with a unique opaque sentinel that matches no other origin
let pinnedSandboxOrigin = new Origin();
let isSandboxPinned = false;

export function handleSandboxedWidgetMessage(event, expectedWidgetWindow) {
  try {
    const senderOrigin = Origin.from(event);

    // Pin the opaque origin only once on initialization from the expected sandboxed iframe
    if (!isSandboxPinned && event.source === expectedWidgetWindow && event.data?.type === 'init') {
      pinnedSandboxOrigin = senderOrigin;
      isSandboxPinned = true;
    }

    // Subsequent messages from the SAME sandboxed iframe match pinnedSandboxOrigin;
    // messages from any other sandboxed iframe (also event.origin === "null") return false.
    return senderOrigin.isSameOrigin(pinnedSandboxOrigin);
  } catch {
    return false;
  }
}

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

  • isSameOrigin(other) বা isSameSite(other) কল করার আগে Origin.from()-এর মাধ্যমে তুলনার উভয় দিক Origin ইনস্ট্যান্সে কনভার্ট করুন।
  • অস্পষ্ট সোর্স আইডেন্টিটি সংরক্ষণ করার প্রয়োজন হলে, ব্রাউজার-ডিসপ্যাচ করা MessageEvent সরাসরি Origin.from(event)-এ পাস করুন এবং বর্তমান এক্সিকিউশন কন্টেক্সটের Origin পেতে Origin.from(self) (Location বা Document-এর পরিবর্তে, যা TypeError থ্রো করে) ব্যবহার করুন।
  • অবশ্যই Origin.from() কলকে try...catch ব্লকে অবিশ্বস্ত বা রানটাইম ইনপুটে র‍্যাপ করুন যাতে ভুল URL, "null" স্ট্রিং বা কাজ করে না এমন অবজেক্ট আনক্যাচড TypeError থ্রো করার পরিবর্তে নিরাপদে false রিটার্ন করে।
  • "null" (origin !== 'null') প্রথমে বাতিল না করে সরাসরি দুটি রানটাইম event.origin বা url.origin স্ট্রিংয়ের তুলনা করবেন না, কারণ দুটি সম্পর্কহীন অস্বচ্ছ অরিজিনই "null"-এ সিরিয়ালাইজ করে।
  • কখনও startsWith(), includes(), endsWith() বা সাধারণ .split('.') হোস্টনেম স্লাইসিংয়ের মাধ্যমে উৎস বা সাইট যাচাই করবেন না।

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

আপনার বেসলাইন টার্গেটে যদি এমন ব্রাউজার অন্তর্ভুক্ত থাকে যা এখনও Origin ইন্টারফেস সাপোর্ট করে না, তাহলে ফিচার-ডিটেক্ট 'Origin' in globalThis এবং new URL()-এর মাধ্যমে URL পার্স করার পদ্ধতিতে ফিরে যান:

  • একই-অরিজিন ফলব্যাক (টুপল অরিজিন): URL স্ট্রিং এক্সট্র্যাক্ট করুন (MessageEvent অবজেক্টের জন্য event.origin বা লিঙ্ক এলিমেন্টের জন্য candidate.href ব্যবহার করে), new URL()-এর মাধ্যমে ক্যান্ডিডেট ও বিশ্বস্ত URL পার্স করুন, "null" অস্বচ্ছ অরিজিন (candidateUrl.origin !== 'null') বাতিল করুন এবং candidateUrl.origin === trustedUrl.origin তুলনা করুন। যেহেতু একটি অস্বচ্ছ অরিজিন কখনই 'https://app.example.com'-এর মতো টিউপল অরিজিনের সাথে একই-অরিজিন হয় না, তাই অবিলম্বে 'null'-এর সাথে Origin.isSameOrigin()-এর মিল বাতিল করা হচ্ছে।
  • সেম-সাইট ফলব্যাক (সতর্কতা): URL ইন্টারফেস ব্রাউজারের পাবলিক সাফিক্স তালিকা প্রকাশ করে না। Origin ছাড়া ব্রাউজারে, শুধুমাত্র আপনার কন্ট্রোল করা একটি স্পষ্ট, পরিচিত ব্যক্তিগত রেজিস্টারযোগ্য ডোমেনের সাথে সাফিক্স ম্যাচিং করুন (protocol এবং একটি সঠিক বা ডট-প্রিফিক্সড hostname ম্যাচ উভয়ই যাচাই করা), অথবা একটি রক্ষণাবেক্ষণ করা পাবলিক সাফিক্স লিস্ট লাইব্রেরি ব্যবহার করুন।
  • অস্বচ্ছ / স্যান্ডবক্স করা iframe ফলব্যাক: Origin ছাড়া ব্রাউজারগুলি MessageEvent-এ স্বতন্ত্র অস্বচ্ছ অরিজিন পরিচয় প্রকাশ করে না (event.origin সবসময় "null" হয়)। আপনি যদি ইচ্ছাকৃতভাবে কোনও পরিচিত স্যান্ডবক্স করা <iframe sandbox="allow-scripts"> থেকে মেসেজ গ্রহণ করেন, তাহলে event.origin তুলনা করার পরিবর্তে event.source === expectedWidgetWindow যাচাই করুন (অথবা ইনিশিয়ালাইজ করার সময় iframe-এ ট্রান্সফার করা একটি নির্দিষ্ট MessageChannel MessagePort-এর মাধ্যমে যোগাযোগ করুন)।
// Replace with your trusted reference URL and known registrable domain
const TRUSTED_URL = 'https://app.example.com';
const TRUSTED_SITE_DOMAIN = 'example.com';

function extractUrlString(candidate) {
  if (typeof candidate === 'string') return candidate;
  if (typeof MessageEvent !== 'undefined' && candidate instanceof MessageEvent) {
    return candidate.origin;
  }
  if (candidate === globalThis) {
    return globalThis.location?.href ?? '';
  }
  return candidate?.href ?? '';
}

function extractOrigin(candidate) {
  try {
    return Origin.from(candidate);
  } catch (err) {
    if (typeof MessageEvent !== 'undefined' && candidate instanceof MessageEvent) {
      return Origin.from(candidate.origin);
    }
    throw err;
  }
}

export function checkSameOriginWithFallback(candidate) {
  try {
    if ('Origin' in globalThis) {
      return extractOrigin(candidate).isSameOrigin(Origin.from(TRUSTED_URL));
    }
    const candidateUrl = new URL(extractUrlString(candidate));
    const trustedUrl = new URL(TRUSTED_URL);
    return candidateUrl.origin !== 'null' && candidateUrl.origin === trustedUrl.origin;
  } catch {
    return false;
  }
}

export function checkSameSiteWithFallback(candidate) {
  try {
    if ('Origin' in globalThis) {
      return extractOrigin(candidate).isSameSite(Origin.from(TRUSTED_URL));
    }
    const candidateUrl = new URL(extractUrlString(candidate));
    const trustedUrl = new URL(TRUSTED_URL);
    return (
      candidateUrl.origin !== 'null' &&
      candidateUrl.protocol === trustedUrl.protocol &&
      (candidateUrl.hostname === TRUSTED_SITE_DOMAIN ||
        candidateUrl.hostname.endsWith(`.${TRUSTED_SITE_DOMAIN}`))
    );
  } catch {
    return false;
  }
}