عند تحميل نصوص برمجية أو أوراق أنماط أو خطوط أو صور من شبكات توصيل محتوى (CDN) تابعة لجهات خارجية أو مصادر خارجية، يجب التحقّق من سلامة تشفيرها باستخدام ميزة "سلامة الموارد الفرعية" (integrity في HTML وintegrity() في CSS url())، وضبط معالجة بيانات الاعتماد المتعدّدة المصادر (crossorigin وcross-origin())، وحظر عناوين Referer الصادرة (referrerpolicy وreferrer-policy()). للاطّلاع على "سياسة أمان المحتوى" على مستوى المستند والقوائم المسموح بها للاتصالات الصادرة، يُرجى مراجعة أمان الويب وحظر الاتصالات الصادرة بالشبكة باستخدام القوائم المسموح بها للاتصالات.
1. إنشاء ملخّصات لسلامة التشفير لمواد العرض الثابتة
تضمن ميزة "سلامة الموارد الفرعية" (SRI) أن ينفّذ المتصفّح أو يعرض مادة عرض خارجية فقط إذا كانت وحدات البايت الخاصة بها تتطابق مع التجزئة التشفيرية التي يعلن عنها تطبيقك.
- احتساب التجزئات في وقت الإنشاء أو الإصدار، وليس من الردود المباشرة غير الموثوق بها: يمكنك إنشاء تجزئات السلامة أثناء خطوة الإنشاء أو الإصدار من وحدات بايت مواد العرض المحلية المعروفة بأنّها جيدة (أو نسخ التجزئة الرسمية التي نشرها مؤلف الحزمة) وترميزها بشكل ثابت في HTML أو CSS أو JavaScript. لا تجلب أبدًا عنوان URL تابعًا لجهة خارجية في المتصفّح في وقت التشغيل لحساب تجزئة
integrityالخاصة به، والتي تثق في أي وحدات بايت يعرضها الخادم البعيد وتتغلب على SRI. - استخدام
sha384-تلقائيًا: يمكنك تنسيق البيانات الوصفية الخاصة بالسلامة على أنّها<algorithm>-<base64-digest>باستخدامsha384-(الإعداد التلقائي المقترَح) أوsha512-أوsha256-. - تثبيت الموارد الفرعية الخارجية على عناوين URL غير قابلة للتغيير ومحدّدة الإصدار: يجب دائمًا الإشارة إلى مواد العرض التابعة لجهات خارجية باستخدام عناوين URL غير قابلة للتغيير أو محدّدة الإصدار أو موجَّهة حسب المحتوى (مثل
https://cdn.example.com/ui@2.4.0/app.min.js) بدلاً من عناوين URL العائمة القابلة للتغيير (latest/app.min.js)، والتي تعرّض موقعك الإلكتروني لتغييرات غير معلنة في سلسلة الإمداد وتؤدي إلى تعطُّل SRI فور تحديث الملف البعيد. إذا كان عليك تحميل نص برمجي تابع لجهة خارجية قابل للتغيير ويتم تحديثه في مكانه (مثل علامة إحصاءات المورّد)، يمكنك حذفintegrityفي عنوان URL هذا وتقييده باستخدام "سياسة أمان المحتوى" (strict-dynamic/ الأرقام المستخدمة لمرة واحدة) أو وضع الحماية في iframe بدلاً من ذلك. - الفصل بين الملخّصات المتعددة بمسافة عندما تعرض نقطة نهاية حمولات متعددة صالحة: جميع المتصفحات التي تتيح SRI تنفّذ
sha256-وsha384-وsha512-. إذا كان عنوان URL قد يعرض بشكل مشروع إحدى الحمولات المتعددة المعروفة بأنّها جيدة (مثل أثناء اختبار A/B أو طرح مرحلي لشبكة توصيل المحتوى أو إصدار تم التفاوض على محتواه)، يمكنك إدراج ملخّصات متعددة للخوارزمية نفسها مفصولة بمسافة بيضاء (على سبيل المثال،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)}`;
}
2. تأمين نصوص HTML البرمجية وأوراق الأنماط وعمليات التحميل المُسبق
اضبط عنوان استجابة HTTP Referrer-Policy: strict-origin-when-cross-origin على مستوى المستند (أو ضمِّن <meta name="referrer" content="strict-origin-when-cross-origin"> في <head>) كإعداد تلقائي أساسي، بحيث يستمر أي مورد فرعي أو url() CSS يحذف معدِّل المُحيل لكل طلب في إزالة المسار الحسّاس ومعلمات طلب البحث في الطلبات الواردة من عدّة مصادر.
بعد ذلك، في عناصر <script> و<link rel="stylesheet"> و<link rel="preload"> و<link rel="modulepreload"> الخارجية، اضبط دائمًا integrity وcrossorigin وreferrerpolicy معًا:
integrity="sha384-...": يحظر تنفيذ المورد أو تطبيقه إذا كانت وحدات البايت في الاستجابة لا تتطابق مع الملخّص المتوقّع (بالنسبة إلى<link rel="stylesheet">المحظور، يظلlinkElement.sheetnullويتم تنشيط حدثerror).-
crossorigin="anonymous"(الإعداد التلقائي المقترَح لمواد عرض شبكة توصيل المحتوى العامة) أوcrossorigin="use-credentials": مطلوبان عند ضبطintegrityعلى مورد متعدّد المصادر. يؤدي حذفcrossoriginإلى جلب المتصفّح للمورد في وضعno-cors(ما يؤدي إلى استجابة مبهمة لا يمكن التحقّق منها باستخدام التجزئة) وحظر المورد. -
referrerpolicy="no-referrer"(الإعداد التلقائي المقترَح لشبكات توصيل المحتوى التابعة لجهات خارجية) أو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',
});
3- تأمين صور CSS والخطوط وأوراق الأنماط المستورَدة باستخدام مُعدِّلات url()
تتحكّم معدِّلات طلبات CSS url() (cross-origin() وreferrer-policy() وintegrity()) في وضع الجلب وعنوان المُحيل والتحقّق من سلامة الموارد الفرعية للموارد التي تم تحميلها من خلال CSS، بما في ذلك قواعد background-image وcontent وmask-image وborder-image-source وfilter و@font-face وsrc و@import، مباشرةً من CSS:
| دالة التعديل | بنية الوسيطة والقيم المسموح بها | الغرض |
|---|---|---|
cross-origin(<ident>) |
كلمة رئيسية غير مقتبسة: anonymous (الإعداد التلقائي المقترَح) أو use-credentials |
يتم جلب مورد CSS باستخدام CORS والتحكّم في ما إذا كان يتم إرسال بيانات الاعتماد. يجب دائمًا تضمينها مع integrity() في موارد CSS المتعدّدة المصادر. |
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 الذي تم جلبه مقابل الملخّص المشفّر المتوقّع قبل عرضه أو تطبيقه. |
اتّبِع قواعد الأمان وبنية CSS التالية عند إنشاء معدِّلات url():
- اقتباس سلسلة عنوان URL داخل
url("..."): لا تكون معدِّلات الطلبات صالحة إلا عندما يكون عنوان URL داخلurl(...)عبارة عن<string>مقتبس (لا يمكن لعناوين URL غير المقتبسة استخدام المعدِّلات). - تحديد كل نوع معدِّل مرة واحدة على الأكثر لكل
url(): يمكنك دمجcross-origin()وreferrer-policy()وintegrity()بأي ترتيب داخلurl("..." ...)، ولكن تكرار نوع المعدِّل نفسه مرتين (مثل معدِّلَين من النوعintegrity()) غير صالح. لتقديم تجزئات متعددة، افصل بينها بمسافة داخل سلسلةintegrity("sha384-... sha384-...")واحدة. - يجب دائمًا إقران السمة
integrity()المتعددة المصادر بالسمةcross-origin(anonymous): على الرغم من أنّ السمتَين@font-facesrcوmask-imageتجلبان البيانات تلقائيًا في وضعcorsالمجهول، فإنّ السِماتbackground-imageوcontentوborder-image-sourceو@importتستخدم تلقائيًا وضعno-cors(ما يؤدي إلى استجابة مبهمة لا يمكن التحقّق من تجزئتها)، وتستخدم السمةfilterلملفات SVG الخارجية تلقائيًا وضعsame-origin(ما يؤدي إلى حظر عناوين URL المتعددة المصادر بشكل كامل). يضمن تضمينcross-origin(anonymous)(أوcross-origin(use-credentials)) التحقّق من CORS بشكل متّسق في كل سمة CSS. - وضع المُعدِّلات داخل
url("...")في قواعد@importو@font-face: يجب إبقاءcross-origin()وreferrer-policy()وintegrity()داخلurl("..." ...)، مع إبقاء واصفات الخطوط مثل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 المتتالية لكي تظل الموارد الفرعية تعمل ومحمية:
- يُفضَّل استخدام HTML
<link rel="stylesheet">بدلاً من CSS@importعند الحاجة إلى استخدام SRI على مستوى المتصفّحات: نظرًا لأنّsubresource-integrityوreferrer-policyعلى<link rel="stylesheet">متاحان على نطاق واسع في جميع المتصفّحات، يمكنك تحميل أوراق الأنماط الخارجية باستخدام<link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer">بدلاً من@importعندما يكون من الضروري فرض التحقّق من السلامة في كل متصفّح. - الاعتماد على عنوان
Referrer-Policyعلى مستوى المستند أو<meta name="referrer">من الخطوة 2: في المتصفحات التي لا تتيح استخدامreferrer-policy()داخل CSSurl()، يزيل تلقائيًا عنوانReferrer-Policy: strict-origin-when-cross-originعلى مستوى المستند أو عنصر<meta name="referrer" content="strict-origin-when-cross-origin">المسار الحسّاس ومعلمات طلب البحث في طلبات CSS المتعددة المصادر. استخدام إعلانات CSS منفصلة ومتتالية لتحقيق تحسين تدريجي: المتصفحات التي لا تتعرّف على المُعدِّل داخل
url("..." ...)تتجاهل الإعلان بالكامل في وقت تحليل CSS. أعلِن عنurl("...")عادي أولاً، ثم عنcross-origin()وreferrer-policy()(للمتصفّحات التي تتيح هذين المُعدِّلَين ولكن ليسintegrity())، ثم عن البيان الكامل بما في ذلكintegrity("sha384-..."):- لا تضع أبدًا
url()غير متحقق منه داخل قائمة@font-facesrcنفسها المفصولة بفواصل: إنّ كتابةsrc: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2");في واصفsrcواحد يتجاوز "سلامة الموارد الفرعية" في وقت التشغيل. إذا كانت شبكة توصيل المحتوى (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") ); }- لا تضع أبدًا
Feature-detect CSS
url()modifier support in JavaScript withCSS.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 };
}