আপনার অ্যানালিটিক্স স্ক্রিপ্টই আপনার Content-Security-Policy-র ফাঁক
একটি কঠোর CSP XSS বন্ধ করে। একটি থার্ড-পার্টি অ্যানালিটিক্স ট্যাগ তা আবার খুলে দেয়। কেন host allowlist এবং অনুপস্থিত SRI আপনার নীতি দুর্বল করে — এবং একটি ফার্স্ট-পার্টি ট্র্যাকার কী ঠিক করে তা এখানে রইল।
ক্রস-সাইট স্ক্রিপ্টিং-এর বিরুদ্ধে একক সবচেয়ে কার্যকর প্রতিরক্ষা হলো একটি Content-Security-Policy। যে মুহূর্তে আপনি একটি থার্ড-পার্টি অ্যানালিটিক্স ট্যাগ যোগ করেন, সাধারণত তখনই আপনাকে এতে একটি ফাঁক তৈরি করতে হয়। বাস্তব জগতের অধিকাংশ CSP বাইপাস ঠিক সেই ফাঁকেই বাস করে।
এর কারণটি কাঠামোগত, আকস্মিক নয়। নজরদারি-ভারী অ্যানালিটিক্স স্ক্রিপ্টগুলো এমনভাবে ডিজাইন করা হয় যাতে সেগুলো রানটাইমে আপনার নিয়ন্ত্রণে নেই এমন ডোমেইন থেকে আরও কোড লোড করতে পারে। ঠিক সেই কাজটিই নিষিদ্ধ করার জন্যই একটি কঠোর CSP-র অস্তিত্ব।
একটি স্ক্রিপ্টকে বিশ্বাস করার দুর্বল উপায় হলো host allowlist
একটি অ্যানালিটিক্স ভেন্ডরকে অনুমতি দেওয়ার পুরোনো উপায় ছিল একটি host allowlist:
Content-Security-Policy: script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com;
OWASP আর এই প্যাটার্নের সুপারিশ করে না। Allowlist সহজেই বাইপাস করা যায়: যেকোনো অনুমোদিত origin যা একটি JSONP endpoint, একটি open redirect, বা ব্যবহারকারীর আপলোড করা স্ক্রিপ্ট হোস্ট করে, তা একটি XSS ভেক্টরে পরিণত হয়। ট্যাগ ম্যানেজার বিষয়টিকে আরও খারাপ করে, কারণ সেগুলোর অস্তিত্বই হলো নির্বিচার origin থেকে আরও স্ক্রিপ্ট ইনজেক্ট করার জন্য।
বর্তমানের শীর্ষস্থানীয় চর্চা হলো একটি strict CSP, যা একটি per-response nonce-এর সাথে strict-dynamic-এর উপর নির্মিত:
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none';
strict-dynamic-এর সাথে, ব্রাউজার ইতিমধ্যে বিশ্বস্ত একটি স্ক্রিপ্ট দ্বারা লোড করা স্ক্রিপ্টগুলোকে বিশ্বাস করে — এটিই ঠিক সেই আচরণ যার উপর একটি ট্যাগ ম্যানেজার নির্ভর করে। এটি ভেন্ডরের জন্য সুবিধাজনক এবং আপনার জন্য বিপজ্জনক: একটিমাত্র আপোসকৃত ট্যাগ এখন যেকোনো কিছু লোড করার অন্তর্নিহিত অনুমতি পেয়ে যায়।
SRI ঠিক সেই স্ক্রিপ্টগুলোতেই কাজ করে না যেগুলোর সবচেয়ে বেশি প্রয়োজন
Subresource Integrity ব্রাউজারকে এক্সিকিউট করার আগে একটি ফেচ করা ফাইলকে একটি ক্রিপ্টোগ্রাফিক hash-এর বিপরীতে যাচাই করতে দেয়:
<script
src="https://cdn.example.com/tracker.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
SRI sha256, sha384, এবং sha512 সমর্থন করে, এবং কোনো মিল না হলে ব্রাউজার রিসোর্সটি প্রত্যাখ্যান করে। স্ট্যাটিক, version-pinned ফাইলের ক্ষেত্রে এটি একটি আপোসকৃত CDN-এর বিরুদ্ধে একটি পরিচ্ছন্ন প্রতিরক্ষা।
অ্যানালিটিক্সের ক্ষেত্রে এটি ভেঙে পড়ে। ভেন্ডর ট্যাগগুলো ইচ্ছাকৃতভাবে পরিবর্তনশীল: ভেন্ডর যখনই ফিচার শিপ করে তখনই একই URL-এর ফাইল পরিবর্তিত হয়, তাই একটি pinned hash পরবর্তী ডিপ্লয়েই কালেকশন ভেঙে দেবে। বাস্তবে দলগুলো ঠিক সেই স্ক্রিপ্টগুলো থেকেই SRI বাদ দেয় যেগুলোর পেজে সবচেয়ে বিস্তৃত নাগাল থাকে। ২০২৫-এর অক্টোবরে সাপ্লাই-চেইন ঝুঁকির পুনর্বিন্যাস ঠিক এই কারণেই অনিরাপদ SRI বাস্তবায়নকে high severity হিসেবে রেট করেছিল।
Trusted Types ২০২৬-এর ফেব্রুয়ারিতে মানদণ্ড উঁচু করে
সমস্যার DOM-XSS অর্ধেকের এখন একটি ব্রাউজার-স্তরের উত্তর রয়েছে। Baseline February 2026 অনুযায়ী, Trusted Types বর্তমান ব্রাউজারগুলোতে উপলব্ধ। আপনি এনফোর্সমেন্ট সক্রিয় করেন এভাবে:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default;
এর পরে, Element.innerHTML-এর মতো DOM sink গুলো কাঁচা স্ট্রিং প্রত্যাখ্যান করে এবং কেবল একটি রেজিস্টার্ড policy দ্বারা তৈরি করা মান গ্রহণ করে:
const policy = trustedTypes.createPolicy("default", {
createHTML: (input) => DOMPurify.sanitize(input),
});
el.innerHTML = policy.createHTML(userInput); // ok
el.innerHTML = userInput; // throws TypeError
এটি সত্যিকারের শক্তিশালী। এটি আবার এমন ধরনের নিয়ম যা লিগ্যাসি অ্যানালিটিক্স এবং ট্যাগ-ম্যানেজার কোড নিয়মিত লঙ্ঘন করে, কারণ সেই স্ক্রিপ্টগুলো innerHTML-এ লেখে এবং <script> নোডকে সাধারণ স্ট্রিং হিসেবে ইনজেক্ট করে। Trusted Types চালু করা মানে প্রায়ই আগে আপনার অ্যানালিটিক্স ভেন্ডর বন্ধ করা।
একটি ফার্স্ট-পার্টি ট্র্যাকার একটি কঠোর নীতির বিরুদ্ধে না লড়ে বরং তার সাথে মানিয়ে যায়
দ্বন্দ্বটি অদৃশ্য হয়ে যায় যখন অ্যানালিটিক্স স্ক্রিপ্টটি ছোট, স্বয়ংসম্পূর্ণ, এবং আপনার নিজের origin থেকে সার্ভ করা হয়। যে ট্র্যাকার আর কোনো কোড লোড করে না তার strict-dynamic-ও লাগে না, আপনার allowlist-এ একটি ভেন্ডর হোস্টও লাগে না:
Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'none'; require-trusted-types-for 'script';
এটুকুই। কোনো থার্ড-পার্টি origin নেই, কোনো nonce এসকেলেশন নেই, কোনো ট্যাগ ম্যানেজারের জন্য খোদাই করা ব্যতিক্রম নেই।
Monoid-এর ট্র্যাকার মোটামুটি ২ KB, এর কোনো নির্ভরতা নেই, এবং এটি কখনো একটি DOM-XSS sink কল করে না। এটি innerHTML-এ লেখে না, স্ক্রিপ্ট ইনজেক্ট করে না, এবং কুকি বা স্টোরেজ পড়ে বা সেট করে না। এটি প্রতি পেজভিউ-তে /collect-এ একটি keepalive রিকোয়েস্ট পাঠায় এবং অন্যথায় নিষ্ক্রিয় থাকে:
fetch('/collect', {
method: 'POST',
body: JSON.stringify({ site_id, path, referrer, screen_w }),
keepalive: true,
});
যেহেতু ফাইলটি স্ট্যাটিক, আপনি এটিতে কখনো ভেঙে না পড়ে SRI প্রয়োগ করতে পারেন — আপনি স্ক্রিপ্টটি আপডেট করতে না চাওয়া পর্যন্ত একটি pinned hash বৈধ থাকে। আর যেহেতু পরিচয় হলো একটি একমুখী দৈনিক hash, SHA-256(IP | UA | SALT_SECRET | YYYY-MM-DD), এখানে এমন কোনো ক্রস-সাইট প্রোফাইলিং নেই যা একটি শিথিল CSP ফাঁসও করতে পারত।
প্যাটার্নটি অ্যানালিটিক্সের বাইরেও সাধারণীকৃত হয়: আপনার allowlist থেকে আপনি যে প্রতিটি স্ক্রিপ্ট সরিয়ে দেন, তা একসাথে আপনি যে এক শ্রেণীর XSS বাইপাস সরিয়ে দেন। সবচেয়ে নিরাপদ থার্ড-পার্টি স্ক্রিপ্ট হলো সেটি যা আপনি লোড করেন না।
Comments
Loading comments…