تخطَّ إلى المحتوى

الأمان والعمليات

يرسل هذا الجسر لغة ترميز النص الفائق (⁨HTML⁩) لديك عبر حدود الشبكة إلى محرك متصفح. توثّق هذه الصفحة عناصر التحكم التي تحمي تلك الحدود، مع اعتماد الشيفرة المصدرية مرجعًا للحقيقة. عندما يستند عنصر تحكم إلى معيار، يكون الاستشهاد هو نفسه المعلن في كتلة توثيق الشيفرة. تعيد هذه الصفحة صياغة ما تؤكده الشيفرة؛ ولا تعيد بناء الصياغة المعيارية.

تسمّي كتل توثيق الحزمة التهديدات التي تدافع ضدها:

  • ⁨XSS-to-PDF⁩ — البرمجة عبر المواقع (⁨XSS⁩) عبر ترميز عدائي يُنفَّذ أثناء تصيير تنسيق المستندات المحمولة (⁨PDF⁩).
  • ⁨SSRF⁩ — تزوير الطلب من جانب الخادم (⁨SSRF⁩) الناتج عن ترميز أو محدِّد موقع موارد موحَّد (⁨URL⁩) لوجهة ترسل طلبًا إلى عنوان داخلي.
  • استنزاف الموارد — مُدخَل مفرط الحجم أو قنبلة فكّ ضغط.
  • إعادة ربط ⁨DNS⁩ — إعادة الربط في نظام أسماء النطاقات (⁨DNS⁩)، حيث يجتاز اسم المضيف عملية التحقق ثم يُحلَّل إلى عنوان خاص عند وقت الاتصال.
  • اعتراض ⁨TLS⁩ على المسار — اعتراض أمان طبقة النقل (⁨TLS⁩) على المسار عبر شهادة مستبدَلة على المسار إلى الـ ⁨Worker.⁩

لكل تهديد عنصر تحكم محدد وقابل للاختبار أدناه.

ضوابط المُدخَلات (قبل أن يغادر الطلب ⁨PHP⁩)

قسم بعنوان «ضوابط المُدخَلات (قبل أن يغادر الطلب ⁨PHP⁩)»

تُنفَّذ CloudflareSecurityPolicy::validate() قبل بناء أي طلب:

الضابطالسلوكمصدر الحدّ
حدّ الحجميرفض ⁨HTML⁩ الأكبر من maxHtmlSizeCloudflareRendererConfig، الافتراضي 5000000 بايت
حارس قنبلة فكّ ضغط ⁨Base64⁩يقدّر الحجم بعد فكّ التشفير لكل ⁨URI⁩ من نوع data:…;base64,…؛ ويرفض القيم التي تبلغ الحدّ الأقصى أو تتجاوزهMAX_DATA_URI_BYTES = 13631488
حظر ⁨meta-refresh⁩يرفض أي <meta http-equiv="refresh">، دون حساسية لحالة الأحرفتعبير نمطي في CloudflareSecurityPolicy

تثير أي مخالفة RuntimeException برسالة تذكر القيمة المخالِفة والحدّ. يوجد حظر ⁨meta-refresh⁩ لأن توجيه التحديث يمكن أن يبدأ تنقّلًا داخل الصفحة التي يصيّرها الـ ⁨Worker⁩ — وهو ناقل ⁨SSRF⁩ يكمن في المحتوى لا في الـ ⁨URL.⁩

تعمل سياسة أمان ⁨HTML⁩ من nextpdf/core (HtmlSecurityPolicyInterface، الافتراضي DefaultHtmlSecurityPolicy) عند طبقة التحليل وتكمّل فحوص طبقة النقل المذكورة أعلاه. استرجعها باستخدام getHtmlSecurityPolicy(). احقن سياسة مخصصة عبر الباني.

ضوابط الوجهة (⁨SSRF⁩ وإعادة ربط ⁨DNS⁩)

قسم بعنوان «ضوابط الوجهة (⁨SSRF⁩ وإعادة ربط ⁨DNS⁩)»

CloudflareSecurityPolicy::validateWorkerUrl():

  1. يرفض أي ⁨URL⁩ يتعذّر تحليله أو يفتقر إلى ⁨scheme/host⁩ (Invalid Worker URL).
  2. يرفض أي مخطط غير ⁨HTTPS⁩ (Worker URL must use HTTPS).
  3. بالنسبة لمضيف بعنوان ⁨IP⁩ حرفي، يرفض النطاقات الخاصة أو المحجوزة باستخدام FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE في ⁨PHP.⁩ من الناحية العملية، يرفض هذا المساحة الخاصة في ⁨RFC 1918⁩، وحلقة الاسترجاع (⁨loopback⁩)، وعناوين الربط المحلي في ⁨RFC 3927.⁩ تغطّي الاختبارات صراحةً رفض 192.168.x و127.0.0.1 و169.254.x. امتداد المرشّحات (⁨filter⁩) في ⁨PHP⁩ هو ما يقرّر الانتماء إلى النطاق؛ ولا تربط هذه الحزمة ذلك القرار ببند معيّن. يُذكر ⁨RFC 1918⁩ و⁨RFC 3927⁩ هنا وصفيًا بوصفهما التعريفين المعروفين لتلك النطاقات.
  4. بالنسبة لاسم المضيف، يُحلَّل كل سجلات ⁨A⁩ و ⁨AAAA⁩ باستخدام dns_get_record() (وليس gethostbyname()، الذي يعيد الجواب الأول فقط)، ويُرفض المضيف إذا كان أي عنوان مُحلَّل خاصًا أو محجوزًا.

استخدام تحليل جميع السجلات مقصود. توثّقه كتلة توثيق الصنف بوصفه دفاعًا ضد مضيف يعيد عدة سجلات؛ إذ قد يختار البحث بسجل واحد العنوان العام، بينما يختار الاتصال اللاحق عنوانًا خاصًا. يطابق هذا ورقة ⁨OWASP SSRF Prevention Cheat Sheet⁩: حلّ جوابَي ⁨A⁩ و ⁨AAAA⁩ كليهما للنطاق، وتطبيق فحص العنوان غير العام على مجموعة النتائج الكاملة.

تعيد validateWorkerUrl() مجموعة عناوين ⁨IP⁩ التي تم التحقق منها. قبل الإرسال مباشرةً، يستدعي المُصيِّر assertPinsStillValid(). يعيد هذا الاستدعاء تحليل المضيف ويرفض أي عنوان ⁨IP⁩ يظهر حديثًا (Worker URL DNS answer changed since validation — possible DNS rebinding attack). يغلق ذلك نافذة وقت-الفحص / وقت-الاستخدام بين التحقق والاتصال.

عند وجود مجموعة عناوين ⁨IP⁩ تم التحقق منها أو مجموعة تثبيت لمعلومات المفتاح العام للموضوع (⁨SPKI⁩) و توفير ResponseFactory متوافق مع توصية معايير ⁨PHP⁩ رقم 17 (⁨PSR-17⁩)، يستخدم المُصيِّر Transport\PinnedCurlTransport بدلًا من عميل توصية معايير ⁨PHP⁩ رقم 18 (⁨PSR-18⁩) المحقون. يفرض النقل عناصر التحكم هذه عند طبقة مقبض ⁨cURL⁩:

  • ⁨DNS⁩ مثبَّت — يربط CURLOPT_RESOLVE المضيف:المنفذ بمجموعة عناوين ⁨IP⁩ التي تم التحقق منها، بحيث لا تجري ⁨libcurl⁩ بحثها الخاص عند وقت الاتصال. يجعل هذا الربط فحص ⁨DNS⁩ على مستوى المستخدم منطبقًا على الاتصال الفعلي؛ وبدونه قد تحلّ ⁨libcurl⁩ عنوانًا مختلفًا.
  • تثبيت المفتاح العام لـ ⁨TLS⁩ — يُضبط CURLOPT_PINNEDPUBLICKEY من مجموعة التثبيت المجمَّعة. يتّبع هذا ⁨RFC 7469⁩ §2.6: يُقبل الاتصال المثبَّت عندما تتقاطع مجموعة بصمات ⁨SPKI⁩ التي يقدّمها الخادم مع مجموعة التثبيت المُهيَّأة، ويكون فشل التحقق من التثبيت غير قابل للاسترداد. تُطبَّع سلاسل التثبيت من sha256/<base64> إلى صيغة sha256//<base64> الخاصة بـ ⁨cURL⁩؛ ويثير أي تثبيت مشوَّه InvalidSpkiPinException.
  • التحقق من ⁨TLS⁩ مُفعَّلCURLOPT_SSL_VERIFYPEER => true، CURLOPT_SSL_VERIFYHOST => 2.
  • لا إعادة توجيه تلقائيةCURLOPT_FOLLOWLOCATION => false، CURLOPT_MAXREDIRS => 0. تُعرَض استجابة 3⁨xx⁩ على طبقة السياسة بدلًا من أن تتبعها ⁨libcurl⁩ إلى مضيف لم يتم التحقق منه. تنصّ كتلة توثيق الصنف على أن هذا مقصود، حتى يُعاد التحقق من إعادات التوجيه بدلًا من اتّباعها بصمت.
  • مهلة صارمة — يُضبط CURLOPT_TIMEOUT من renderTimeout (الافتراضي 30 ثانية).

يثير أي خطأ ⁨cURL⁩ أو متن غير نصي CloudflareRenderException مع رقم خطأ ⁨cURL⁩ ورسالته.

يحمل التكوين pinnedPublicKeys وbackupPublicKeys بصورة منفصلة. يصف ⁨RFC 7469⁩ §2.5 تثبيت النسخة الاحتياطية بوصفه بصمة لزوج مفاتيح ثانوي لم يُنشر بعد ويُحتفَظ به دون اتصال، ويعامله بوصفه مسار الاسترداد الأساسي لفشل التحقق من التثبيت غير المقصود. احتفظ بتثبيت نسخة احتياطية واحد على الأقل حتى لا يؤدي تدوير الشهادة إلى تعطيل نقطة النهاية. يتيح لك الحقل المنفصل التحقق من التدوير بشكل مستقل. من الناحية التشغيلية:

  • ثبّت ⁨SPKI⁩ الخاص بالشهادة الطرفية أو بشهادة وسيطة تتحكم في تدويرها.
  • هيّئ دائمًا تثبيت نسخة احتياطية للشهادة التالية قبل التدوير.
  • تعطّل مجموعة تثبيت فارغة التثبيت؛ لا تستخدم ذلك إلا مع سلسلة شهادات مستقرة ومعروفة. التثبيت اختياري وفقًا للتكوين.
  • يحمل طلب الـ ⁨Worker⁩ Authorization: Bearer <apiToken>. apiToken هو #[SensitiveParameter]، لذلك تحجبه آثار التتبّع. يرسل مسبار الوصول ترويسة الحامل نفسها عبر HEAD لبروتوكول نقل النص الفائق (⁨HTTP⁩).
  • مفاتيح وصول ⁨Cloudflare R2⁩ (accessKeyId، secretAccessKey) هي #[SensitiveParameter] وتُستخدم فقط لاشتقاق مفتاح توقيع ⁨Amazon Web Services⁩ (⁨AWS⁩) ⁨Signature V4.⁩
  • يقارن ApiKeyValidator المفاتيح باستخدام hash_equals() (آمن من حيث التوقيت) ويدعم تخزين المفاتيح المُجزّأة بخوارزمية التجزئة الآمنة 256 (⁨SHA-256⁩) عبر validateHashed().
  • كائنات التكوين هي final readonly — لا يمكن تعديل سرّ ضُبط مرة واحدة.
  • استمدّ الأسرار من متغيرات البيئة أو من مدير أسرار. لا تودِعها في الشيفرة أبدًا. تتّبع الحزمة خط الأساس الأمني الأوسع لـ ⁨NextPDF⁩: ⁨PHPStan Level 10⁩، وdeclare(strict_types=1) في كل ملف، ودون eval()/exec()، وإجراءات ⁨GitHub Actions⁩ مثبَّتة على ⁨SHA.⁩
  • لا تنصّ على أي حدّ لمنصة ⁨Cloudflare⁩ (وقت معالج الـ ⁨Worker⁩، أو الذاكرة، أو الحدّ الأقصى لمتن الطلب، أو عدد الطلبات الفرعية). حدود الحجم والوقت الوحيدة التي ينصّ عليها هذا التوثيق هي تلك التي تفرضها الحزمة بنفسها، والمذكورة أعلاه وفي /⁨integrations/cloudflare/configuration/.⁩ للاطلاع على حدود المنصة، راجع التوثيق الرسمي لـ ⁨Cloudflare⁩ وتنفيذ الـ ⁨Worker⁩ لديك.
  • لا توقّع ملفات ⁨PDF⁩ ولا تقدّم أي ادّعاء بمطابقة التوقيع. عندما تُطلب التواقيع، صيّر هنا، ثم وقّع بالمحرك. يوفّر ⁨NextPDF Pro⁩ توقيع التواقيع الإلكترونية المتقدمة لـ ⁨PDF⁩ (⁨PAdES⁩) ⁨B-B⁩ فقط؛ أما ملفات التحقق طويل الأمد فهي قدرة على مستوى ⁨Enterprise⁩ وتقع خارج نطاق هذا الجسر.
  • لا تشهد بأن خط الأنابيب “⁨tamper-proof⁩” ولا تضمن ذلك ولا تجعله كذلك. تنفّذ فقط الضوابط المحددة القابلة للتحقق من المصدر الموصوفة في هذه الصفحة.
العَرَضالفحص الأول
Worker URL must use HTTPSتحقّق من مخطط workerUrl المُهيَّأ.
private or reserved IPسجلات ⁨DNS⁩ لاسم مضيف الـ ⁨Worker⁩؛ ابحث عن سجل يُحلَّل إلى مساحة ⁨RFC 1918⁩ / ⁨loopback⁩ / ⁨RFC 3927.⁩
DNS answer changed since validationعدم استقرار ⁨DNS⁩ أو محاولة إعادة ربط؛ أعد التحليل وافحص مجموعة السجلات الكاملة.
cURL transport errorمسار الشبكة، وسلسلة ⁨TLS⁩، و — إذا كانت التثبيتات مضبوطة — تحقق مما إذا كان ⁨SPKI⁩ للشهادة المقدَّمة لا يزال ضمن مجموعة التثبيت.
تفشل عمليات التصيير فور تدوير الشهادةمجموعة تثبيت بدون تثبيت نسخة احتياطية مطابق. أضف ⁨SPKI⁩ الجديد كنسخة احتياطية قبل التدوير.
is not installed / no LocalRendererFactoryInterfaceالاحتياط مُفعَّل لكن لا يوجد مصنع موصول، أو nextpdf/artisan غائب.
رفض حدّ المعدّل غير متّسق عبر العُقَدالمُحدِّد في الذاكرة لكل عملية على حدة؛ ضع أمامه مخزنًا مشتركًا.

أبلِغ عن الثغرات عبر ⁨GitHub Security Advisories⁩ أو جهة الاتصال الأمنية في SECURITY.md الخاص بالمستودع. لا تُسجِّل المشكلات الأمنية بوصفها مشكلات ⁨GitHub⁩ عامة.

  • /⁨integrations/cloudflare/overview/⁩ — لماذا صُممت هذه الحزمة حول الحدود.
  • /⁨integrations/cloudflare/configuration/⁩ — حقول مجموعة التثبيت والحدود.
  • /⁨integrations/cloudflare/troubleshooting/⁩ — التعيين الكامل من الإخفاق إلى الاستثناء.