event.origin की तुलना, पोर्ट-नॉर्मलाइज़ की गई स्ट्रिंग लिटरल (जैसे, event.origin === 'https://app.example.com') से करने पर, अनुमति वाली सामान्य सूचियों के लिए काम करती है. हालांकि, मैन्युअल तरीके से स्ट्रिंग की तुलना करने और रेगुलर एक्सप्रेशन का इस्तेमाल करने पर, आर्बिट्रेरी यूआरएल, डीओएम लिंक एलिमेंट, क्रॉस-सबडोमेन रीडायरेक्ट या सैंडबॉक्स किए गए मैसेज की पुष्टि नहीं हो पाती:
- प्रीफ़िक्स और सबस्ट्रिंग स्पूफ़िंग:
url.startsWith('https://app.example.com')की जांच को'https://app.example.com.attacker.example'से बायपास किया जाता है. - अननॉर्मलाइज़्ड यूआरएल: पाथ, क्वेरी स्ट्रिंग या साफ़ तौर पर बताए गए डिफ़ॉल्ट पोर्ट (
'https://app.example.com:443/path') वाले फ़ुल यूआरएल, स्ट्रिंग की समानता के तहत'https://app.example.com'से मैच नहीं होते. इसके लिए, पहले उन्हें पार्स और नॉर्मलाइज़ करना ज़रूरी है. - पब्लिक सफ़िक्स लिस्ट से जुड़ी गलतियां: सेम-साइट रिलेशनशिप की जांच करने के लिए,
.पर होस्टनेम को अलग-अलग करने की प्रोसेस, कई लेबल वाले पब्लिक सफ़िक्स (जैसे,.co.ukया.github.ioजैसे शेयर किए गए होस्टिंग डोमेन) पर काम नहीं करती. साथ ही, यह स्कीम के मेल न खाने (http:बनामhttps:) को अनदेखा करती है. - ओपेक
"null"ऑरिजिन कोलिज़न: सैंडबॉक्स किए गए iframe (<iframe sandbox="allow-scripts">) औरdata:यूआरएल,event.originको लिटरल स्ट्रिंग"null"में सीरियलाइज़ करते हैं.originA === originBका इस्तेमाल करके, सीरियलाइज़ किए गए दो रनटाइम ऑरिजिन की तुलना करने पर, दो अलग-अलग ओपेक कॉन्टेक्स्ट ("null" === "null") को गलत तरीके से बराबर माना जाता है.
Origin एपीआई (Origin.from(), isSameOrigin(), isSameSite(), और opaque) स्ट्रक्चर्ड और स्पेसिफ़िकेशन के मुताबिक ऑरिजिन और स्कीम के हिसाब से सेम-साइट की तुलना करने की सुविधा देता है. इसके लिए, ब्राउज़र के यूआरएल पार्सर और पब्लिक सफ़िक्स लिस्ट का इस्तेमाल किया जाता है.
अपने सिनेरियो के लिए सही तुलना चुनना
- अनुमतियों की सख्त सीमाओं के लिए,
isSameOrigin()का इस्तेमाल करें: क्रॉस-विंडोpostMessageकमांड (जैसे, एम्बेड किया गया चेकआउट या पुष्टि करने वाला पॉप-अप) को मैनेज करते समय या क्रेडेंशियल अटैच करने का फ़ैसला लेते समय, एग्ज़ैक्ट(scheme, host, port)मैच की ज़रूरत होती है. यहां तक कि अलग सबडोमेन (https://blog.example.comबनामhttps://app.example.com) भी एक अलग ऑरिजिन है और इसे अस्वीकार किया जाना चाहिए, ताकि एक सबडोमेन पर मौजूद जोखिम दूसरे को कंट्रोल न कर सके. - एक से दूसरे सबडोमेन पर जाने और लिंक को अलग-अलग कैटगरी में बांटने के लिए,
isSameSite()का इस्तेमाल करें: लॉगिन करने के बाद रीडायरेक्ट करने वाले यूआरएल (?returnTo=https://billing.example.co.uk/orders) की पुष्टि करते समय या लिंक को इंटरनल बनाम एक्सटर्नल के तौर पर अलग-अलग कैटगरी में बांटते समय, एक ही स्कीम और रजिस्टर किए जा सकने वाले डोमेन को शेयर करने वाले किसी भी सबडोमेन या पोर्ट को अनुमति दें.isSameSite(), शेयर किए गए सार्वजनिक सफ़िक्स (.co.uk,.github.io) पर,http:स्कीम के डाउनग्रेड और सिबलिंग टेनेंट, दोनों को अस्वीकार करता है. - ओपेक या सैंडबॉक्स किए गए कॉन्टेक्स्ट के लिए,
origin.opaqueऔरOrigin.from(event)का इस्तेमाल करें: रीडायरेक्ट (origin.opaque === true) के लिए,data:यूआरएल अस्वीकार करें या किसी खास<iframe sandbox="allow-scripts">विजेट के साथ कम्यूनिकेट करते समय,Origin.from(event)के ज़रिए निकाले गए ओपेकOriginको पिन करें. इस विजेट का सीरियलाइज़ किया गयाevent.origin,"null"है.
1. खास कार्रवाइयों (Origin.from() और isSameOrigin()) के लिए, सटीक ऑरिजिन की पुष्टि करना
Origin.from() का इस्तेमाल करके, Origin ऑब्जेक्ट को निकालें और isSameOrigin() का इस्तेमाल करके, भरोसेमंद Origin से उसकी तुलना करें:
- दोनों साइड
Originके इंस्टेंस होने चाहिए:isSameOrigin(other)औरisSameSite(other)सिर्फ़Originऑब्जेक्ट को स्वीकार करते हैं—रॉ स्ट्रिंग पास करने परTypeErrorदिखता है. - इस्तेमाल किए जा सकने वाले
Origin.from()इनपुट:- सीरियलाइज़ की गई यूआरएल स्ट्रिंग (उदाहरण के लिए,
'https://app.example.com:443/path'). ध्यान दें कि कोई भी अमान्य यूआरएल स्ट्रिंग—उदाहरण के लिए,'example'या ओपेक ऑरिजिन के लिएevent.originसे सीरियलाइज़ की गई'null'स्ट्रिंग—Origin.from()कोTypeErrorथ्रो करने का कारण बनती है. - एचटीएमएल स्पेसिफ़िकेशन में ऑरिजिन-एक्सट्रैक्शन के चरणों को तय करने वाले प्लैटफ़ॉर्म ऑब्जेक्ट:
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
});
2. क्रॉस-सबडोमेन रीडायरेक्ट और लिंक की पुष्टि करना (isSameSite())
रजिस्टर किए जा सकने वाले एक ही डोमेन (उदाहरण के लिए, https://app.example.co.uk और https://billing.example.co.uk:8443) के सबडोमेन में, रीडायरेक्ट टारगेट (जैसे कि ?returnTo= पैरामीटर) या इंटरनल लिंक की पुष्टि करते समय, 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');
3. ओपेक ऑरिजिन (origin.opaque और Origin.from(event)) को अलग-अलग करें और पिन करें
सैंडबॉक्स किए गए iframe (<iframe sandbox="allow-scripts">), data: यूआरएल, और 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ब्लॉक में रैप करें, ताकि अमान्य यूआरएल,"null"स्ट्रिंग या काम न करने वाले ऑब्जेक्ट, अनकैच किए गएTypeErrorको थ्रो करने के बजाय, सुरक्षित तरीके सेfalseको रिटर्न करें. "null"(origin !== 'null') को अस्वीकार किए बिना, दो रनटाइमevent.originयाurl.originस्ट्रिंग की तुलना सीधे तौर पर न करें. ऐसा इसलिए, क्योंकि दो अलग-अलग ओपेक ऑरिजिन, दोनों"null"में सीरियलाइज़ होते हैं.startsWith(),includes(),endsWith()या होस्टनेम को स्लाइस करने वाले सामान्य.split('.')का इस्तेमाल करके, ऑरिजिन या साइटों की पुष्टि न करें.
फ़ॉलबैक रणनीति
अगर आपके बेसलाइन टारगेट में ऐसे ब्राउज़र शामिल हैं जो अब तक Origin इंटरफ़ेस के साथ काम नहीं करते हैं, तो 'Origin' in globalThis की सुविधा का पता लगाएं. इसके बाद, new URL() की मदद से यूआरएल को पार्स करें:
- एक ही ऑरिजिन वाला फ़ॉलबैक (टपल ऑरिजिन): यूआरएल स्ट्रिंग को एक्सट्रैक्ट करें. इसके लिए,
MessageEventऑब्जेक्ट के लिएevent.originया लिंक एलिमेंट के लिएcandidate.hrefका इस्तेमाल करें. इसके बाद,new URL()का इस्तेमाल करके, कैंडिडेट और भरोसेमंद यूआरएल, दोनों को पार्स करें. साथ ही,"null"ओपेक ऑरिजिन (candidateUrl.origin !== 'null') को अस्वीकार करें औरcandidateUrl.origin === trustedUrl.originकी तुलना करें. ओपेक ऑरिजिन,'https://app.example.com'जैसे टपल ऑरिजिन के साथ कभी भी सेम-ऑरिजिन नहीं होता. इसलिए,'null'को तुरंत अस्वीकार करने सेOrigin.isSameOrigin()मैच होता है. - Same-site फ़ॉलबैक (चेतावनी):
URLइंटरफ़ेस, ब्राउज़र की सार्वजनिक सफ़िक्स सूची को ज़ाहिर नहीं करता.Originके बिना ब्राउज़र में, सिर्फ़ ऐसे साफ़ तौर पर जाने-पहचाने निजी रजिस्टर किए जा सकने वाले डोमेन के लिए सफ़िक्स मैचिंग करें जिसे आप कंट्रोल करते हैं. इसके लिए,protocolऔर सटीक या डॉट-प्रीफ़िक्स वालेhostnameमैच, दोनों की पुष्टि करें. इसके अलावा, मैनेज की जा रही सार्वजनिक सफ़िक्स सूची की लाइब्रेरी का इस्तेमाल करें. - ओपेक / सैंडबॉक्स किए गए iframe फ़ॉलबैक: जिन ब्राउज़र में
Originनहीं होता वेMessageEventपर अलग-अलग ओपेक ऑरिजिन आइडेंटिटी को ज़ाहिर नहीं करते (event.originहमेशा"null"होता है). अगर आपको किसी जाने-पहचाने सैंडबॉक्स किए गए<iframe sandbox="allow-scripts">से मैसेज स्वीकार करने हैं, तोevent.originकी तुलना करने के बजाय,event.source === expectedWidgetWindowकी पुष्टि करें या शुरू करने पर iframe में ट्रांसफ़र किए गए खासMessageChannelMessagePortपर कम्यूनिकेट करें.
// 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;
}
}