الأمن
الـ deserialization عند حدّ الـ framework
ثغرة RCE بدرجة 10.0، ثم 6 ترقيعات DoS في 8 أشهر. حين يكون العقد "اقبل كل ما يرمّزه الـ protocol"، فإغلاق ثغرة يترك البقية.
بين كانون الأول 2025 وحزيران 2026 أصدرت حزمة @nestjs/platform-fastify 4 تحديثات أمنية منفصلة، لما هو عند القراءة المتأنية الثغرة نفسها:
CVE-2025-69211 — تخطّي الـ middleware عبر مسار مُرمَّز بـ percent-encoding. عولجت في 11.1.11.CVE-2026-2293 — تخطّيه مجدداً حين تكون خيارات path normalization في Fastify مفعّلة. عولجت في 11.1.14.CVE-2026-33011 — تخطّيه بإرسال HEAD بدل GET، لأن Fastify يوجّه طلب HEAD إلى handler الـ GET المقابل. عولجت في 11.1.16.CVE-2026-54281 — تخطّيه بإضافة trailing slash في آخر العنوان. عولجت في 11.1.24.4 إصلاحات في 7 أشهر. كل واحد منها صحيح وكان يستحق أن يُنشر. لكن ثغرة تعود في كل مرة برقم جديد ليست ثغرة في الغالب، بل موقف تصميمي لا يصمد.
ربط الـ middleware بمسارات بعينها يعني أن نظامين يقرآن الطلب نفسه، وكلٌّ منهما يقرر ما هو. الـ middleware يطابق المسار مع نمط ليقرر هل يعمل أم لا، والـ router يطابق المسار نفسه مع route مسجَّل ليقرر ماذا ينفّذ. هذان تطبيقان منفصلان لسؤال واحد — "أي route هذا؟" — وكل ثغرة أعلاه حالة أجابا فيها إجابتين مختلفتين.
/admin و/admin/ عنوان واحد عند الـ router ونصّان مختلفان عند الـ matcher. والمسار المُرمَّز شيء قبل فكّ الترميز وشيء آخر بعده. وHEAD /admin لا يطابق أي middleware مسجَّل على GET، ثم يصل إلى handler الـ GET رغم ذلك. الـ handler عمل، والحارس لم يعمل.
هذا الصنف من الثغرات لا يُغلق بإحصاء الحالات. القائمة بطول مجموعة النصوص التي يمكن لـ parserين أن يختلفا عليها، وهي تطول كلما اكتسب أحدهما خياراً جديداً.
الثغرة CVE-2025-29927، بدرجة 9.1 على مقياس CVSS: طلب يحمل الـ header المسمّى x-middleware-subrequest كان يُعامَل كاستدعاء داخلي فيتخطّى الـ middleware كلياً. عولجت في 15.2.3 و14.2.25 و13.5.9 و12.3.5. ليست اختلافاً بين parserين هذه المرة، بل trust boundary قَبِل header يرسله العميل دليلاً على أن الطلب داخلي — لكن النتيجة واحدة وللسبب البنيوي نفسه: قرار الـ authorization كان في موضع يمكن للطلب أن يتجاوزه.
ثم تكرر الأمر. الثغرة CVE-2026-64642 في تموز 2026: تطبيقات App Router المبنية بـ Turbopack مع locale واحد في config.i18n.locales كان يمكن تخطّي الـ authentication فيها المعتمد على الـ middleware. عولجت في 16.2.11. والحل البديل في نص الـ advisory نفسه يستحق أن يُقرأ كما كُتب:
enforce authorization in the page's server-side data path
instead of relying solely on middlewareهذا الـ framework نفسه يقول لك أين ينتمي التحقق.
middleware.ts يعيد توجيه الزائر غير المسجَّل، وpages أو route handlers تفترض أنه عمل؟MiddlewareConsumer.forRoutes() في تطبيق Nest هو الـ authorization الوحيد الواقف أمام route محمي؟middleware كلياً، هل تصبح أي بيانات محمية قابلة للقراءة؟إن كان جواب السؤال الثالث نعم، فالـ middleware عندك هو نظام الصلاحيات، وهو على بُعد اختلاف واحد بين parserين من أن يكون غائباً.
عند النقطة التي تُقرأ فيها البيانات. الدالة التي تجلب السجل هي آخر موضع في الطلب لا يمكن الالتفاف حوله، لأنه لا يوجد تطبيق ثانٍ لها ينافسها على تحديد ما طابقه. هناك دالة واحدة تقرأ ذلك الصف، وإما أن يعمل التحقق بداخلها أو لا يُقرأ الصف.
عملياً: جلب الـ session والتحقق من الملكية يعيشان في طبقة البيانات، أو في دالة واحدة يستدعيها كل handler قبل أن يعيد شيئاً. هذا كود أكثر من ملف middleware واحد، ويتكرر تكراراً يبدو زائداً عن الحاجة — إلى أن يأتي اليوم الذي لا يكون فيه كذلك. وهو أيضاً النسخة الوحيدة التي تصمد حين يغيّر الـ framework رأيه في معنى الـ trailing slash.
كل ما ليس قراراً أمنياً: اختيار الـ locale، وإعادة التوجيه، وإضافة response headers، ورفض الطلبات المشوّهة مبكراً كي لا يعمل المسار المكلف أصلاً. هو مكان جيد لاتخاذ قرار رخيص، ومكان سيّئ لاتخاذ القرار الوحيد.
عامله على أنه تحسين للأداء. إن تُخطّي، فالمفترض أن يصبح الطلب أبطأ — لا أن يصبح أوسع صلاحية.
الأمن
ثغرة RCE بدرجة 10.0، ثم 6 ترقيعات DoS في 8 أشهر. حين يكون العقد "اقبل كل ما يرمّزه الـ protocol"، فإغلاق ثغرة يترك البقية.
الأمن
18 إصداراً خبيثاً من package واحدة خرجت في ساعتين ونصف، تحصد الـ tokens عند التنصيب. lockfiles، وتعطيل الـ scripts، واعتمادات قصيرة العمر.
الأمن
16 advisory في يوم واحد عبر 14 مشروعاً، ومعظمها access bypass. الحل ليس مزيداً من الاجتهاد، بل قائمة modules أقصر.