استبدال القوالب من جهة الخادم
أدرج قيمًا يولّدها الخادم (مثل العناصر النائبة `{{ .Field }}` على طريقة قوالب Go) داخل JavaScript المشوَّش باستخدام VM دون كسر خط التشويش.
المشكلة
يقدّم خادمك الخلفي (Go أو Rails أو Django أو PHP أو غيرها) ملف JavaScript يجب توليد جزء من محتواه لكل طلب على حدة:
نقطة نهاية API، أو قائمة بأعلام الميزات، أو كتلة الحالة الأولية، أو معرِّف البناء، أو قيمة nonce. أنت تشوّش الكود
باستخدام vmObfuscation: true، لكنك تحتاج أيضًا إلى أن يستبدل محرك القوالب العناصر النائبة في الناتج المشوَّش
بعد انتهاء التشويش. يُبقي reservedNames وreservedStrings العنصر النائب ظاهرًا في الناتج كي يمكن استبداله.
ومن دونهما، يُبتلَع العنصر النائب النصي داخل بايت كود الآلة الافتراضية، ويُخرَج العنصر النائب على شكل معرِّف في عدة
مواضع مُعاد كتابتها، فإما ألا يجد محرك القوالب ما يستبدله، وإما أن يُفسد الناتج.
فكّر في تحميل القيم كبيانات بدلًا من ذلك
إن أمكنك، فتجنّب تعديل الناتج المحمي تمامًا: حمّل إعدادات وقت التشغيل كبيانات قبل تشغيل الحزمة المحمية. ضعها
في سكربت منفصل أو اجلبها من نقطة نهاية تتطلب مصادقة. يحافظ ذلك على الناتج المحمي كما هو، فيمكن إبقاء
vmSelfDefending مفعَّلًا. وفي كل الأحوال، تبقى البيانات المرسلة إلى المتصفح مرئية لذلك المتصفح.
استخدم الأنماط أدناه عندما يتحتم فعلًا استبدال القيم داخل الملف المشوَّش.
ملاحظة حول أمثلة Go: تُجري هذه الأمثلة استبدالًا نصيًا بسيطًا (strings.ReplaceAll / strings.NewReplacer) على
الناتج المشوَّش وتتولى التهريب بنفسها، كما هو موضح في كل نمط. وهي لا تمر عبر الحزمة html/template في Go، إذ إن ذلك
سيطبّق تهريب JavaScript السياقي الخاص بها فوق ذلك، فتُهرَّب الحمولة مرتين. وإذا كنت تعرض القالب عبر html/template فعلًا،
فاحذف التهريب اليدوي ودع المحرك يهرّب مرة واحدة.
أي خيار أختار؟
| قيمة الخادم هي… | استخدم |
|---|---|
| قيمة JS خام (مصفوفة، أو كائن، أو رقم، أو قيمة منطقية - أي قيمة JSON) | النمط 1 - reservedNames + عنصر نائب على شكل معرِّف |
| نص (الحالة الشائعة - JSON مولَّد، أو معرِّف بناء، أو قيمة nonce) | النمط 2 - reservedStrings + عنصر نائب على شكل قالب حرفي |
| نص، حيث يجب أن يبقى الكود بمعيار ES5 أو تحتاج واجهة API المحيطة إلى نص حرفي بين علامتي اقتباس | النمط 3 - reservedStrings + عنصر نائب على شكل نص بين علامتي اقتباس |
الاستبدال بعد التشويش غير متوافق مع vmSelfDefending: true. راجع
ملاحظات التوافق في نهاية هذه الوصفة.
النمط 1 - reservedNames مع عنصر نائب على شكل معرِّف
الاستخدام الأنسب: إدراج تعبيرات JS خام (مصفوفات، كائنات، أرقام، وغيرها) دون أي قلق بشأن تهريب علامات الاقتباس.
اختر معرِّفًا مميزًا لن يتعارض أبدًا مع الكود الحقيقي، وأشِر إليه مباشرة، وأضف تعبيرًا نمطيًا يطابقه إلى
reservedNames. في ظل تشويش VM، يُمرَّر المعرِّف عبر مصفوفة التعبيرات المحجوزة ويظهر حرفيًا في الناتج، مثلًا:
استبدل المعرِّف بتعبير كامل متسلسل بصيغة JSON. لا تضع القيمة داخل علامات اقتباس أو علامات backtick، فالمعرِّف
ليس داخل نص حرفي، ولذلك يحلّل محرك JS القيمة المُدرجة كتعبير عادي. استخدم أداة تسلسل موثوقة، وهرِّب < عندما
يكون السكربت مضمَّنًا في HTML، واختبر قيمًا تحتوي على علامات اقتباس وشرطات مائلة عكسية وفواصل أسطر وعلامات
backtick و${...}.
النمط 2 - reservedStrings مع عنصر نائب على شكل قالب حرفي
الاستخدام الأنسب: محركات القوالب على طريقة Go أو Jinja التي تتطلب محددات مثل {{ .Field }}، والتي تصادف أنها نص JS
صالح عند إحاطتها بعلامات backtick.
يوجد العنصر النائب داخل قالب حرفي بجزء نصي واحد ودون استيفاء. في ظل تشويش VM يُمرَّر عبر مصفوفة التعبيرات المحجوزة، فيُحفظ شكل backtick الخام في الناتج.
المصدر
خيارات المشوِّش
واجهة المستخدم
لحجز العنصر النائب {{.Config.FeatureFlags}} من المصدر أعلاه، أضف هذا التعبير النمطي إلى حقل Reserved Strings
بشرطات مائلة عكسية مفردة. تخزّن الواجهة القيمة حرفيًا، لذا لا تُضاعَف الشرطات المائلة العكسية بخلاف ما يحدث في
كود JS:

واجهة API
بعد التشويش
يُحفظ العنصر النائب بايتًا ببايت، بما في ذلك علامات backtick المحيطة به.
الاستبدال من جهة الخادم
استبدل {{.Config.FeatureFlags}} بنص JSON مهرَّب ليناسب قالبًا حرفيًا. لا تتطلب علامات backtick تهريب "
الداخلية، لكن الشرطة المائلة العكسية أو علامة backtick أو ${ داخل الحمولة ستظل تغيّر القالب الحرفي أو تُنهيه،
لذا هرِّب هذه الثلاثة:
أثناء التشغيل: JSON.parse(`{"newCheckout":true,"darkMode":false}`) يعمل.
لماذا تُفضَّل القوالب الحرفية على علامات الاقتباس المفردة أو المزدوجة لقيم النصوص؟ لا تتطلب علامات backtick
تهريب " داخل حمولة JSON. وهذا مهم لأن معظم القيم التي يولّدها الخادم هي JSON، وJSON مليء بعلامات الاقتباس
المزدوجة. مع عنصر نائب بين علامتي اقتباس مزدوجتين ستحتاج إلى تهريب كل " داخلية (انظر النمط 3)؛ أما مع علامات
backtick فلا تحتاج الحمولة إلا إلى تهريب ما ندر فيها من شرطات مائلة عكسية وعلامات backtick و${.
النمط 3 - reservedStrings مع عنصر نائب على شكل نص بين علامتي اقتباس
الاستخدام الأنسب: الكود المصدري الذي يجب أن يبقى بمعيار ES5 (بلا قوالب حرفية)، أو الحالات التي تتوقع فيها واجهة API المحيطة نصًا حرفيًا عاديًا.
العنصر النائب نص حرفي بين علامتي اقتباس مفردتين أو مزدوجتين. في ظل تشويش VM يُمرَّر عبر مصفوفة النصوص المحجوزة،
التي تُخرَج كمصفوفة JS متسلسلة عبر JSON.stringify، وتكون دائمًا بعلامات اقتباس مزدوجة أيًّا كان نمط الاقتباس
في المدخلات.
المصدر
خيارات المشوِّش
واجهة المستخدم
لحجز العنصر النائب {{.Page.Tags}} من المصدر أعلاه، أضف هذا التعبير النمطي إلى حقل Reserved Strings بشرطات
مائلة عكسية مفردة. تخزّن الواجهة القيمة حرفيًا، لذا لا تُضاعَف الشرطات المائلة العكسية بخلاف ما يحدث في كود JS:

واجهة API
بعد التشويش
الاستبدال من جهة الخادم
لأن العنصر النائب يقع داخل نص JS بين علامتي اقتباس مزدوجتين، يجب تهريب الشرطات المائلة العكسية وأحرف " الداخلية
في حمولة JSON المُدرجة:
الناتج:
أثناء التشغيل: JSON.parse("[\"news\",\"tech\",\"release\"]") ← ["news","tech","release"].
إذا نسيت التهريب، فسيرى المتصفح علامات اقتباس غير متوازنة ويرمي SyntaxError. أما النمط 2 فيتجنب علامات الاقتباس
المزدوجة كليًا باستخدام علامات backtick.
عدة عناصر نائبة في برنامج واحد
يمكن الجمع بين الأنماط الثلاثة. فتعبير نمطي واحد في reservedStrings يستخدم البدائل يستطيع مطابقة كل أشكال العناصر
النائبة التي يُخرجها محرك القوالب لديك - وهنا كلا النمطين {{ .Field }} و%{ .Field }:
يمكنك المزج بين العناصر النائبة على شكل معرِّفات (لقيم JS الخام) والعناصر النائبة على شكل نصوص (لبيانات JSON المولَّدة) في المصدر نفسه، واختيار النوع لكل عنصر نائب بحسب ما سيُدرجه الخادم فعلًا.
ملاحظات التوافق
يكسر vmSelfDefending الاستبدال بعد التشويش
يكتشف خيار VM Self Defending أي تغيير على الناتج المشوَّش بعد بنائه، بما في ذلك استبدال القوالب المشروع، فيرفض الكود المحمي حينها العمل.
إذا كنت تعتمد على استبدال القوالب من جهة الخادم، فاضبط vmSelfDefending: false. واترك selfDefending معطَّلًا
كذلك: فهو بلا أثر في ظل تشويش VM، ومن دون VM يمنع أيضًا أي تغيير على الناتج.
نصائح حول العناصر النائبة
- لا تُعِد استخدام العناصر النائبة نفسها للمعرِّفات والنصوص. الاسم المحجوز مثل
__TOKEN__والنص المحجوز الذي يطابق__TOKEN__يصفان مسارين مختلفين في الكود (مصفوفة التعبيرات المحجوزة مقابل مصفوفة النصوص المحجوزة). استخدم أشكالًا نصية مختلفة لكل منهما، مثل اصطلاحUPPER_SNAKEللعناصر النائبة على شكل معرِّفات، وشكل محاط بمحددات ({{ ... }}أو%{...}أو<<<...>>>) للعناصر النائبة على شكل نصوص. وبهذا لا يمكن لخطأ في أحد التعبيرين النمطيين أن يطابق الآخر بصمت. - تُطبَّق تعبيرات
reservedStringsالنمطية على قيم النصوص الخام. يُطابَق التعبير النمطي مع قيمة النص أثناء التشغيل، لا مع النص المصدري. فالتعبير\{\{[^}]+\}\}يطابق النصوص التي تحتوي على{{.something}}(أو أي شكل آخر من{{...}}). وإذا كان العنصر النائب قد يُحاط بمحتوى إضافي ("prefix-{{.Field}}-suffix")، فسيظل التعبير النمطي يطابقه لكن النص بأكمله هو ما يُحفظ، فخطّط للاستبدال على هذا الأساس. - يعمل مع أي محرك قوالب. رغم أن الأمثلة تستخدم صياغة Go، فلا شيء في التكامل مع javascript-obfuscator خاص بلغة Go. أي أداة قادرة على إجراء استبدال على مستوى النص في ناتج المشوِّش ستعمل: Rails ERB أو Django أو وسوم PHP المختصرة أو sed في خط CI، وغيرها. اختر محددات يُخرجها محركك بشكل طبيعي ولا تتعارض مع صياغة JS الحقيقية.
