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

أمان Artisan وعملياته التشغيلية

يَعرِض الجسر ⁨HTML⁩ قد يكون غير موثوق داخل ⁨Chrome⁩، خلف حاجزَي شبكة مستقلَّين وسياسة محتوى صارمة. ويُعَدّ صندوق العزل على مستوى نظام التشغيل في ⁨Chrome⁩ ضابطًا منفصلًا واختياريًا له حدود صريحة. توثّق هذه الصفحة تلك الحدود، ولا تدّعي أنها مطلقة.

العرض هو تنفيذ طلب من جانب الخادم: يمرّر تطبيقك ⁨HTML⁩ إلى محرك متصفح يمكنه جلب الموارد افتراضيًا. وعندما تؤدي مُدخلات غير موثوقة إلى عملية جلب صادرة، يكون الخطر هو تزوير الطلب من جانب الخادم (⁨SSRF⁩): يعرّفه مدخل ⁨CWE-918⁩ في تعداد نقاط الضعف الشائعة (⁨CWE⁩) بأنه استرجاع الخادم لمحتوى عنوان ⁨URL⁩ مُقدَّم دون ضمان كافٍ بأن الطلب يصل إلى الوجهة المتوقعة. و⁨SSRF⁩ (⁨CWE-918⁩) هو نقطة ضعف ضمن ⁨CWE Top 25.⁩ ويتطلب معيار التحقق من أمان التطبيقات (⁨ASVS⁩) الصادر عن مشروع أمان تطبيقات الويب العالمي المفتوح (⁨OWASP⁩) التحكم في الطلبات الصادرة من مكوّنات الخادم بدلًا من تركها ضمنية. وتتعامل ورقة ⁨OWASP⁩ المرجعية للوقاية من ⁨SSRF⁩ مع رفض الاستدعاءات إلى وجهات اعتباطية على مستوى الشبكة بوصفه الضابط الأقوى. والوضع الشبكي القائم على الرفض افتراضيًا أدناه هو استجابة الجسر لذلك المتطلب. ويصف المنشور الخاص (⁨SP⁩) 800-53 ⁨SC-7⁩ الصادر عن المعهد الوطني للمعايير والتقنية (⁨NIST⁩) المبدأ نفسه لحدّ «الرفض الكامل والسماح بالاستثناء» الذي يطبّقه الجسر على طبقة النقل.

إقامة البيانات وتدابير تخفيف معلومات التعريف الشخصية

قسم بعنوان «إقامة البيانات وتدابير تخفيف معلومات التعريف الشخصية»

يُعالَج ⁨HTML⁩ المُمرَّر إلى الجسر بالكامل داخل العملية وداخل نسخة ⁨Chrome⁩ المحلية. ولا يُجري الجسر أي استدعاء شبكي صادر خاص به، ويمنع ⁨Chrome⁩ من إجراء أي استدعاء (انظر نموذج الشبكة أدناه)، بحيث لا يغادر محتوى المُدخلات المضيف عبر محرك العرض. وتظهر معلومات التعريف الشخصية (⁨PII⁩) الموجودة في المُدخلات ضمن مخرجات تنسيق المستندات المحمولة (⁨PDF⁩) التي تنتجها، لذا عامِل المخرجات بضوابط الإقامة نفسها المطبَّقة على المُدخلات. ولا يحفظ الجسر المُدخلات أو المخرجات على القرص؛ فالحفظ مسؤولية المُستدعِي.

القياس عن بُعد الآمن وتنقية السجلات

قسم بعنوان «القياس عن بُعد الآمن وتنقية السجلات»

يقبل ChromeHtmlRenderer وBrowserPool واجهة LoggerInterface اختيارية من توصية ⁨PHP⁩ القياسية (⁨PSR⁩)-3. ولا يسجّل الجسر سوى البيانات الوصفية التشغيلية: طول المُدخلات بالبايت، والعرض والارتفاع المستهدفين، وطول المخرجات بالبايت، وارتفاع المحتوى المقيس، وإطلاق المتصفح بمسار الملف الثنائي المُهيّأ، وإشعارات إعادة التشغيل مع عدد عمليات العرض، وأحداث الإغلاق. وهو لا يسجّل محتوى ⁨HTML⁩ أو البايتات المعروضة أو النص المُستخرَج. ويتوافق ذلك مع إرشاد ⁨NIST SP 800-92⁩ بشأن تسجيل الأحداث التشغيلية مع إبقاء الحمولات الحساسة خارج السجلات. ويُسجَّل مسار الملف الثنائي؛ عامِله بوصفه بيانات وصفية للنشر غير حساسة. وتتحقق الاختبارات من أشكال استدعاءات السجل في tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSize وtests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath.

نموذج عزل الشبكة (الدفاع المتعمّق)

قسم بعنوان «نموذج عزل الشبكة (الدفاع المتعمّق)»

يطبّق الجسر حاجزَين مستقلَّين حتى لا يعرّض تجاوز أحدهما المضيف للخطر:

  1. سياسة أمان المحتوى. تُغلَّف كل عملية عرض بواسطة ChromeSecurityPolicy::wrapHtml() في مستند يحمل:

    default-src 'none'; style-src 'unsafe-inline'; img-src data:;
    base-uri 'none'; form-action 'none'; frame-ancestors 'none';
    navigate-to 'none';

    يرفض توجيه سياسة أمان المحتوى (⁨CSP⁩) default-src 'none' جميع مصادر الموارد. ويسمح img-src data: بالصور المضمَّنة فقط. ويحجب navigate-to 'none' التنقل من جانب العميل. أما style-src 'unsafe-inline' فهو التخفيف الوحيد المطلوب كي يطبّق printToPDF في ⁨Chrome⁩ الأنماط المضمَّنة. تم التحقق منه في src/Artisan/ChromeSecurityPolicy.php وتؤكّده ChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives.

  2. حجب النقل في بروتوكول أدوات مطوّري ⁨Chrome⁩ (⁨CDP⁩). قبل تحميل المحتوى، يُصدِر ChromeHtmlRenderer الأمر Network.enable ثم Network.setBlockedURLs بالنمط ['*']. ويحجب ذلك كل عنوان ⁨URL⁩ لمورد فرعي على طبقة النقل في بروتوكول أدوات مطوّري ⁨Chrome⁩، بشكل مستقل عن ⁨CSP.⁩ تم التحقق منه في src/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests() وتؤكّده ChromeHtmlRendererTest::renderAutoFitsHeightAndBlocksNetworkRequests (التي تتحقق من الترتيب الدقيق لطرق ⁨CDP⁩ ومن المعامل ['urls' => ['*']]). وهذا هو الحجب على مستوى الشبكة الذي يوصي به إرشاد ⁨OWASP⁩ بشأن ⁨SSRF⁩ بوصفه الضابط الأقوى، وهو رفض كامل على مستوى النقل يتسق مع ⁨NIST SP 800-53 SC-7.⁩

والنتيجة: لا يُحمَّل أي عنوان ⁨URL⁩ بعيد في المُدخلات لعنصر <img>، أو ورقة أنماط، أو خط، أو سكربت، أو إطار مضمَّن. ولا يطبّق الجسر قائمة سماح للنطاقات أو مرشّحًا لعناوين ⁨IP⁩ الخاصة لأنه لا يحتاج إلى ذلك: فهو لا يسمح بأي جلب صادر لمورد فرعي على الإطلاق.

ملاحظة انحراف: يقول التعليق البرمجي في nextpdf/core على writeHtmlChrome() إن ⁨Chrome⁩ “سيجلب الموارد الخارجية” وينصح بتهيئة سياسة لـ “حجب نطاقات ⁨IP⁩ الخاصة وتقييد النطاقات المسموح بها.” وهذا يصف نموذج قائمة سماح قابلة للتهيئة. أما ChromeSecurityPolicy المُورَّد في ⁨Artisan⁩ فلا يكشف قائمة سماح؛ بل يحجب جميع طلبات الموارد الفرعية دون قيد أو شرط. والكود، لا التعليق البرمجي في النواة، هو المرجع الموثوق. وقد سُجِّل هذا الانحراف لدى فريق توثيق النواة.

التحقق من المُدخلات (قبل ⁨Chrome⁩)

قسم بعنوان «التحقق من المُدخلات (قبل ⁨Chrome⁩)»

ChromeSecurityPolicy::validate() يُنفَّذ قبل أن يتصل الجسر بـ ⁨Chrome⁩ ويرفض:

الفحصالحدّالمبرّر
حجم ⁨HTML⁩> maxHtmlSize (الافتراضي 5 ⁨MB⁩)حدّ لاستنزاف الموارد (الاستهلاك غير المُتحكَّم به للموارد ضمن ⁨CWE Top 25⁩)
معرّف موارد موحَّد ببيانات ⁨base64⁩مجموعة التقاط >= 13_000_000 بايتحدّ لقنبلة فكّ الضغط
<meta http-equiv="refresh">أيّ منها (غير حساس لحالة الأحرف، ⁨single/double quote⁩)يحجب عمليات إعادة التوجيه من جانب العميل إلى النقاط الطرفية الداخلية، وهي ناقل تنقّل لـ ⁨SSRF⁩

حجب ⁨meta-refresh⁩ تصليب صريح ضد ⁨SSRF.⁩ فبدونه، يمكن لـ ⁨HTML⁩ يتحكم به المهاجم أن يعيد توجيه ⁨Chrome⁩ إلى نقطة طرفية لبيانات السحابة الوصفية قبل printToPDF. ويُؤكَّد سلوك الحدّ عبر ChromeSecurityPolicyTest (validateThrowsOnOversizedHtml، validateRejectsMetaRefreshRedirect، validateRejectsMetaRefreshCaseInsensitive، validateRejectsMetaRefreshWithSingleQuotes، validateRejectsOversizedBase64DataUri، validateRejectsBase64DataUriAtExactThreshold).

وبالإضافة إلى ذلك، يجرّد ChromeSecurityPolicy::wrapHtml() الوسمَ </style> من defaultCss قبل الحقن لمنع الخروج من كتلة الأنماط إلى سياق السكربت (تؤكّده ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss).

حدّ صندوق العزل في ⁨Chrome⁩ — مذكور صراحةً

قسم بعنوان «حدّ صندوق العزل في ⁨Chrome⁩ — مذكور صراحةً»

صندوق العزل على مستوى نظام التشغيل في ⁨Chrome⁩ ضابط منفصل عن حواجز الشبكة أعلاه، ولا يضمنه الجسر.

  • افتراضيًا، تكون قيمة noSandbox هي false، لذا يُطلَق ⁨Chrome⁩ مع تفعيل صندوق العزل الخاص به. ولا يطبّق الجسر ذلك الصندوق؛ بل يعتمد على صندوق العزل في الملف الثنائي لـ ⁨Chrome⁩، وهو بدوره يعتمد على دعم نواة المضيف.
  • يؤدي ضبط noSandbox: true إلى إطلاق ⁨Chrome⁩ مع --no-sandbox. وهذا يزيل صندوق عزل العمليات في ⁨Chrome.⁩ وهو متاح للحاويات التي يتعذّر فيها تهيئة صندوق العزل. ويمثّل ذلك خفضًا حقيقيًا للعزل: فاختراق محرك العرض لن يبقى محتوًى ضمن صندوق عزل ⁨Chrome.⁩
  • تبقى حواجز الشبكة في الجسر (⁨CSP⁩ + حجب ⁨CDP⁩) سارية المفعول سواء أكان صندوق العزل مفعَّلًا أم لا، لكنها ليست بديلًا عن عزل العمليات. وينطبق إرشاد ⁨OWASP ASVS⁩ بشأن أقل الامتيازات: شغّل ⁨Chrome⁩ بمستخدم غير الجذر، وداخل حاوية مقيَّدة، واستخدم noSandbox فقط عندما يتعذّر تجنّبه، وعامِل أي نشر بـ --no-sandbox بوصفه متطلبًا أعلى للثقة في المُدخلات.

لا يدّعي هذا التوثيق أن الجسر “آمن افتراضيًا” أو “مقاوم للعبث”. كما لا يدّعي أن تعطيل صندوق العزل آمن. بل يوضح الضوابط الموجودة وأين تتوقف. وتوفير حاوية قادرة على صندوق العزل مشروح في صفحة /⁨integrations/artisan/chrome-renderer-setup/.⁩

أنماط الفشل هذه مأخوذة من src/Artisan/Exception/ وكود ⁨render/transport⁩:

الحالةتظهر بوصفهاالمصدر
مكتبة chrome-php/chrome غائبةChromeNotAvailableException (مع أمر التثبيت)BrowserPool::getBrowser()
يتجاوز ⁨HTML⁩ maxHtmlSizeRuntimeException (“⁨exceeds maximum allowed size⁩“)ChromeSecurityPolicy::validate()
معرّف موارد موحَّد ببيانات ⁨base64⁩ مفرط الحجمRuntimeException (“⁨oversized base64 data URI⁩“)ChromeSecurityPolicy::validate()
⁨meta-refresh⁩ ممنوعRuntimeException (“⁨forbidden meta refresh redirect⁩“)ChromeSecurityPolicy::validate()
إطلاق ⁨Chrome⁩ / انتهاء المهلة / تعطّلChromeRenderException (يغلّف السبب)ChromeHtmlRenderer::render()
أعاد ⁨Chrome⁩ ملف ⁨PDF⁩ فارغًاChromeRenderException (“⁨returned empty data⁩“)ChromeHtmlRenderer::render()
الصفحة لا تحتوي على دفق محتوىPdfParseExceptionPageImporter::import()

إذا أُثير ChromeRenderException داخل العرض، فيُعاد رميُه دون تغيير. ويُغلَّف أي Throwable آخر بوصفه ChromeRenderException، مع الحفاظ على الاستثناء السابق (تؤكّده ChromeHtmlRendererTest::renderRethrowsChromeRenderExceptionWithoutWrapping و::renderWrapsUnexpectedThrowablesWithChromeRenderException). وتُغلَق صفحة ⁨Chrome⁩ دائمًا في كتلة finally، حتى عند الفشل.

  • حجم المُدخلات: maxHtmlSize (الافتراضي 5 ⁨MB⁩) وسقف معرّف الموارد الموحَّد لبيانات ⁨base64⁩ البالغ 13 ⁨MB.⁩
  • الوقت: يحدّ renderTimeout بالثواني كلًّا من تحميل المحتوى واستدعاءات المزامنة في ⁨CDP.⁩ وتستخدم أوامر التحكم في ⁨CDP⁩ مهلة ثابتة قدرها 5 ثوانٍ.
  • العملية: يعيد BrowserPool تشغيل ⁨Chrome⁩ كل 100 عملية عرض للحد من نمو الذاكرة، ويغلق العملية عند close() / الإتلاف.

هذه حدود، لا حصص. ولأي مسار معرَّض لمُدخلات غير موثوقة، استخدم كذلك حدًّا للموارد على مستوى المضيف (⁨cgroup⁩ أو ⁨ulimit⁩ أو ميزانية طلبات)، بما يتسق مع إرشاد ⁨CWE Top 25⁩ بشأن استهلاك الموارد.

احقن مُسجِّل ⁨PSR-3⁩ لالتقاط بدء العرض (الحجم والعرض والارتفاع)، واكتمال العرض (حجم المخرجات وارتفاع المحتوى)، وإطلاق المتصفح (مسار الملف الثنائي)، وإعادة تشغيل المتصفح (عدد عمليات العرض)، وإغلاق المتصفح (عدد عمليات العرض). هذه هي الأحداث الوحيدة المُصدَرة، ولا تحمل أي محتوى للحمولة. استخدمها لأهداف مستوى الخدمة (⁨SLOs⁩) الخاصة بالكمون ولتنبيهات معدل إعادة التشغيل.

الادعاءالمرجع⁨clause_id⁩⁨reference_id⁩
يجب التحكم بالطلبات الصادرة من مكوّنات الخادم⁨OWASP ASVS 5.0⁩§ (⁨SSRF/outbound control⁩)
⁨SSRF⁩ = استرجاع الخادم عنوان ⁨URL⁩ مُقدَّمًا دون التحقق من الوجهة⁨CWE Top 25 2025⁩ (⁨CWE-918⁩)cwe_top25_2025#x28.x2.p2
⁨SSRF⁩ (⁨CWE-918⁩) هو نقطة ضعف ضمن ⁨CWE Top 25⁩⁨CWE Top 25 2025⁩cwe_top25_2025#x1.p73
الاستهلاك غير المُتحكَّم به للموارد هو نقطة ضعف ضمن ⁨CWE Top 25⁩⁨CWE Top 25 2025⁩ (⁨CWE-400⁩)cwe_top25_2025#x19.x2.p2
الحماية الحدّية بالرفض افتراضيًا (السماح بالاستثناء)⁨NIST SP 800-53 Rev 5 SC-7⁩⁨SC-7⁩
الرفض على مستوى الشبكة للاستدعاءات إلى وجهات اعتباطية هو الضابط القوي ضد ⁨SSRF⁩⁨OWASP Cheat Sheet Series⁩ (⁨SSRF Prevention⁩ §⁨Network layer⁩)owasp_cheatsheet_series#x132.x2
حماية مكوّنات جلب عناوين ⁨URL⁩ من ⁨SSRF⁩⁨OWASP Cheat Sheet Series⁩§ (⁨SSRF prevention⁩, ⁨URL-fetch tools⁩)
عزل عرض المحتوى غير الموثوق، وأقل الامتيازات⁨OWASP ASVS 5.0⁩§ (⁨sandbox⁩ / ⁨least privilege⁩)
سجّل الأحداث التشغيلية؛ وأبقِ الحمولات خارج السجلات⁨NIST SP 800-92⁩§ (⁨log content guidance⁩)

استُرجِعت الاستشهادات عبر محرك المطابقة في ⁨NextPDF⁩ (بيان فهرس المجموعة 1d05b7c4…d790b6)؛ ونص البند مُعاد صياغته، وليس مقتبسًا حرفيًا أبدًا.

التهديدالضابطالخطر المتبقي
⁨SSRF⁩ عبر مورد فرعي بعيد⁨CSP⁩ default-src 'none' + ⁨CDP⁩ setBlockedURLs('*')خلل في محرك ⁨Chrome⁩ يتجاوز الحاجزين معًا (الدفاع المتعمّق يقلل الخطر، لكنه لا يزيله)
⁨SSRF⁩ عبر تنقّل ⁨meta-refresh⁩يرفض التحقق قبل ⁨Chrome⁩ الوسمَناقل تنقّل جديد لا يطابق النمط
استنزاف المواردحجم المُدخلات + سقوف ⁨base64⁩ + المهلة + إعادة التشغيل كل 100 عملية عرضلا حصة لكل مضيف؛ أرفِقها بـ ⁨cgroup/ulimit⁩
اختراق عملية محرك العرضصندوق عزل ⁨Chrome⁩ عند تفعيلهnoSandbox: true يزيل هذا الضابط بالكامل
كسر الأنماط / الحقنتجريد </style> في defaultCss؛ و⁨CSP⁩ يحجب السكربتحقن عبر ناقل مستقبلي لا يُجرَّد

لا يُجري الجسر أي عمليات تشفيرية. بل ينتج بايتات ⁨PDF⁩ عبر ⁨Chrome⁩ ويضمّنها. والتوقيع والتشفير وسلوك وضع المعايير الفيدرالية لمعالجة المعلومات (⁨FIPS⁩) شؤون تخص النواة/الإصدار المميز، ولا تتأثر بـ ⁨Artisan.⁩

  • /integrations/artisan/configuration/
  • /integrations/artisan/chrome-renderer-setup/
  • /integrations/artisan/troubleshooting/
  • /integrations/artisan/production-usage/
  • /integrations/artisan/overview/