يمكن لنظام تحدي البوتات (bot-challenge system) الخاص بـ Cloudflare أن يعطل بصمت عمليات إرسال نماذج HTML العادية، مما يحول نقرة دفع بسيطة إلى طريق مسدود للمستخدمين الحقيقيين. إن الانتقال من طلب POST الخاص بالتنقل (navigation POST) إلى تدفق يعتمد على fetch أولاً (fetch-first flow) يعيد التجربة السلسة دون المساس بالأمان.
لماذا تهم هذه المشكلة
أطلق أحد المطورين نموذج دفع كان يعمل في كل مجموعة اختبارات، ومع curl، وعلى الخادم المحلي. ولكن نفس النموذج، عندما استخدمه عميل عبر متصفح Chrome، أظهر خطأ أمني بعد النقرة الأولى ورسالة "timeout-or-duplicate" في النقرة الثانية. أجبر هذا الفشل المطور على إصدار ثلاثة تحديثات إصلاح عاجلة (hot-fix) وقضاء يوم كامل في تصحيح الأخطاء (debugging).
الجانب الخفي للمشكلة
يوجد النموذج ضمن حزمة Astro مفتوحة المصدر تعتمد على عنصر <form> HTML بسيط. عندما ينقر المستخدم على Pay، يرد الخادم بإعادة توجيه 303 إلى Stripe، ويتبع المتصفح إعادة التوجيه دون أي JavaScript. تستخدم المواقع هذا النمط كخيار احتياطي عندما تكون البرامج النصية (scripts) معطلة.
تجلس Cloudflare أمام الموقع وتشغل محركاً للكشف عن البوتات. بالنسبة لطلبات GET العادية، يمكنها إظهار تحدٍ بيني (interstitial challenge) مثل CAPTCHA أو فحص JavaScript. بعد أن يجتاز المتصفح التحدي، يستمر الطلب.
ومع ذلك، لا يمكن إيقاف طلب navigation POST مؤقتاً من أجل التحدي ثم استئنافه مع بقاء محتواه (body) سليماً. تقوم "الحافة" (the edge) بالتخلص من الطلب وإرجاع حالة 503، مما يترك المتصفح مع صفحة فارغة أو خطأ عام. أما متصفحات الاختبار المؤتمتة، التي تحمل نفس البصمة (fingerprint) التي تثق بها Cloudflare، فلا تطلق التحدي أبداً، لذا تظل المشكلة غير مرئية حتى يزور مستخدم حقيقي الموقع.
ما كشفته السجلات
أظهر تتبع مباشر للشبكة من جلسة Chrome لأحد المستخدمين طلبين متناقضين لنفس نقطة النهاية (endpoint):
- Navigation POST ← استجابة 503، تعطلت علامة التبويب.
- fetch() POST ← اكتمل الطلب.
كلا الطلبين نشآ من نفس المصدر، وحملا نفس بيانات الاعتماد، وحدثا في نفس اللحظة. الفرق الوحيد كان في طريقة النقل. لقد تجاوز طلب fetch التدفق البيني الذي يحظر طلبات navigation POSTs.
مسارات لم تؤدِ إلى شيء
حاول المطور سلسلة من الإصلاحات التي لم تعالج السبب الجذري:
- تجديد رموز Turnstile، بافتراض أنها انتهت صلاحيتها.
- إضافة نطاقات IP إلى القائمة البيضاء (Whitelisted)، ظناً منه أن الحظر يعتمد على الموقع.
- تعطيل الإضافات، ومسح الـ service workers، وحذف ملفات تعريف الارتباط (cookies).
لم يغير أي من هذه التغييرات الخطأ لأن الفشل نشأ في مرحلة سابقة عند "الحافة" (upstream at the edge)، وليس في كود العميل أو الخادم.
الإصلاح العملي
بدلاً من إيقاف حماية Cloudflare، تمت إعادة هندسة النموذج لاستخدام نمط "fetch-first":
- جمع بيانات النموذج وإرسالها باستخدام
fetch()كحمولة JSON. - التعامل مع استجابة الخادم. إذا أرجع الخادم رابطاً لبوابة الدفع، يتم استدعاء
location.assign()للانتقال إلى هناك باستخدام طلب GET بسيط.
لا تطلق طلبات fetch التحدي البيني، لذا يصل طلب POST إلى خادم الأصل. يمكن لإعادة توجيه GET اللاحقة المرور بأمان عبر أي تحدٍ، لأن أجسام (bodies) طلبات GET تكون فارغة ويمكن إعادة إرسالها بعد أن يجتاز المستخدم التحدي.
المخاطر التي تواجه المطورين
- ثقة المستخدم: نموذج الدفع الذي يفشل بصمت يؤدي إلى تآكل الثقة ويمكن أن يؤدي إلى خسارة الإيرادات.
- أعباء الصيانة: تطلبت الحادثة ثلاثة إصدارات تصحيحية ويوم كامل من التحقيق.
- نقاط الضعف في الاختبار: الاعتماد فقط على بيئات الاختبار الداخلية قد يؤدي إلى تفويت حالات الفشل النادرة التي لا تظهر إلا في الواقع.
دروس للمجتمع التقني
- استخدم متصفحات حقيقية (Instrument real browsers). عندما تظهر المشكلة للمستخدمين الفعليين فقط، قم بالتقاط سجلات الشبكة من تلك الجلسات بدلاً من الثقة في عمليات الاختبار المؤتمتة.
- تعامل مع "الحافة" (the edge) كجزء من البنية التحتية (stack). تقع Cloudflare بين العميل والخادم؛ ويؤثر سلوكها على كيفية هيكلة الطلبات.
- اختر وسيلة النقل الصحيحة. تمر طلبات navigation POSTs و fetch POSTs عبر مسارات مختلفة عند "الحافة". صمم واجهات برمجة التطبيقات (APIs) مع وضع هذا التمييز في الاعتبار.
- أظهر تفاصيل الخطأ. قم بإظهار رسائل 503 أو "timeout-or-duplicate" في واجهة المستخدم حتى يتمكن المطورون من رؤية نمط الفشل الدقيق دون الحاجة للبحث في السجلات.
ما يجب مراقبته مستقبلاً
يجب على المطورين مراجعة أي سير عمل يعتمد على النماذج (form-based workflows) والتي تعتمد على تنقل POST الأصلي، خاصة عندما تكون Cloudflare أو خدمات CDN الأمنية المماثلة موجودة أمام الموقع. يمكن لإضافة غلاف fetch خفيف الوزن أن يمنع حالات فشل مماثلة. ستعمل أدوات المراقبة التي تلتقط رموز الحالة الناتجة عن "الحافة" (edge-generated status codes) على تحديد المشكلة قبل أن تصل إلى العملاء.
الخلاصة: عندما تكون تحديات البوت الخاصة بـ Cloudflare نشطة، يكون إرسال نماذج HTML العادية عرضة للفشل الصامت. إن إعادة توجيه طلب الـ POST عبر fetch() وإكمال التدفق باستخدام إعادة توجيه GET يتجاوز قيود الـ edge مع الحفاظ على الأمان. تعامل مع الـ edge ككود برمجي، وليس مجرد قفزة شبكية، وصمم وسائل النقل الخاصة بك بناءً على ذلك.
