Cursor كتب الـ API الخاص بك — لكن من راجع صلاحيات الوصول؟ إصلاح ثغرة BOLA
إذا بنى Cursor الواجهة الخلفية لتطبيقك، فمن المرجّح أن أي مستخدم مسجّل الدخول يستطيع قراءة بيانات مستخدم آخر بمجرد تغيير رقم المعرّف (ID) في الرابط — إليك لماذا تُشحن ثغرة BOLA هذه افتراضيًا، وكيف تختبرها، وكيف تُغلقها.
طلبت من Cursor نقطة نهاية مثل GET /api/orders/123، فأنشأ كودًا نظيفًا يعمل، وأعاد الطلب الطلبية الصحيحة، ثم انتقلت إلى مهمة أخرى. هنا يكمن الفخّ. عبارة «إنه يعمل» تُثبت فقط المسار السعيد — ولا تقول شيئًا عمّا إذا كان بإمكان المستخدم A قراءة طلبية المستخدم B عبر طلب GET /api/orders/124. تُسمّى هذه الثغرة «كسر التفويض على مستوى الكائن» (Broken Object-Level Authorization أو BOLA)، وهي أكثر العيوب الخطيرة شيوعًا في واجهات API التي يبنيها Cursor. المشكلة ليست أن Cursor سيّئ؛ بل أن النموذج يُحسّن مساره لإرجاع البيانات، وأمّا التفويض الصحيح فيبقى غير مرئي ما لم تطلبه صراحةً. والأرقام في المجال تؤكد ذلك: نحو 91.5% من التطبيقات المُولَّدة بالذكاء الاصطناعي تحمل عيبًا واحدًا على الأقل، ووجدت جامعة CMU أنه بينما 61% من كود الذكاء الاصطناعي صحيح وظيفيًا، فإن نحو 10.5% فقط يجتاز مراجعة أمنية.
ما هي ثغرة BOLA فعليًا (ولماذا هي ليست «مجرّد خطأ برمجي»)
المصادقة (Authentication) تُجيب عن سؤال «من أنت؟»، بينما التفويض (Authorization) يُجيب عن سؤال «هل يُسمح لك بالوصول إلى هذا السجل تحديدًا؟». وثغرة BOLA هي فشل في السؤال الثاني. فحين تقبل نقطة النهاية معرّف كائن (ID) من العميل — رقم طلبية، أو رقم فاتورة، أو معرّف ملف مستخدم — وتجلبه دون التحقق من أن السجل يعود لصاحب الطلب، يستطيع أي شخص لديه تسجيل دخول صالح أن يتصفّح الجدول بأكمله بمجرّد زيادة الرقم. ولأن المهاجم مُصادَق عليه، لا يبدو أي شيء غير طبيعي: لا محاولات دخول فاشلة، ولا ارتفاع في الأخطاء، بل مجرّد مستخدم مسجّل الدخول يقرأ سجلات ليست له. لهذا تتسرّب بيانات العملاء الحقيقية بشكل متكرّر، ولهذا صنّفتها OWASP الخطر الأول في واجهات API. وهي تندرج مباشرةً في قمة فئة «كسر التحكم بالوصول» التي تُهيمن على النتائج الأمنية الحديثة.
لماذا يشحن Cursor هذه الثغرة افتراضيًا
يكتب مساعد البرمجة بالذكاء الاصطناعي الكود الأكثر احتمالًا لمطالبتك. فإذا قلت «ابنِ نقطة نهاية لجلب طلبية»، تكون الإجابة الأكثر ترجيحًا إحصائيًا، وذات الشكل المدرسي، هي: اقرأ المعرّف من الطلب، واستعلم عن قاعدة البيانات لذلك المعرّف، وأعِد السجل. أما التحقق من الملكية فهو شرط إضافي لم تذكره المطالبة، ولذلك يُحذف عادةً. وكل عيب افتراضي خاص بأداة يتّبع المنطق نفسه في موضع مختلف: Lovable يميل إلى شحن سياسات Supabase Row-Level Security (RLS) مفقودة أو معطوبة؛ وBolt.new يُضمّن الأسرار في حزمة العميل التي تُشحن إلى المتصفح؛ وReplit قد يكشف ملف .env عامًا في النشر الافتراضي؛ وv0 يترك مسارات API دون مصادقة. أما بصمة Cursor فهي BOLA — المسار مُصادَق عليه، لكنه لا يتأكد أبدًا من أن الكائن يعود لصاحب الطلب. والإصلاح رخيص متى عرفت أين تبحث؛ والخطر أن كل شيء يبدو صالحًا حتى يقرأ غريبٌ قائمة عملائك.
«إنه يعمل» تعني أن الطلب أعاد بيانات. أما «إنه آمن» فتعني أن الطلب أعاد فقط البيانات المسموح لصاحب الطلب برؤيتها. Cursor يضمن الأولى؛ وأنت وحدك من يضمن الثانية.
كيف تختبرها في عشر دقائق
لا تحتاج إلى اختبار اختراق كامل لاكتشاف BOLA — تحتاج فقط إلى حسابين. أنشئ المستخدم A والمستخدم B، ولكل منهما سجله الخاص (طلبية، أو مستند، أو ملف تعريف). سجّل الدخول بصفتك A ودوّن معرّف سجل A. والآن، وأنت لا تزال مُصادَقًا بصفتك A، اطلب سجل B باستخدام معرّفه — عبر رابط المتصفح، أو بأداة مثل Postman أو curl باستخدام رمز جلسة A. فإذا حصلت على بيانات B بدلًا من رمز 403 Forbidden أو 404 Not Found، فلديك ثغرة BOLA مؤكدة. كرّر ذلك لكل نقطة نهاية تأخذ معرّفًا في المسار أو الاستعلام، ولا تنسَ عمليات الكتابة: حاول تحديث سجل B أو حذفه وأنت مسجّل الدخول بصفتك A. تسريبات القراءة تتصدّر العناوين، لكن عملية DELETE غير المُتحقَّق منها أخطر.
كيف تُصلحها — وتُبقيها مُصلَحة
الإصلاح الجوهري هو مبدأ واحد يُطبَّق في كل مكان: لا تثق أبدًا بمعرّف قادم من العميل كدليل على الملكية. في كل طلب، استخلص هوية المستخدم من جلسته أو رمزه المُتحقَّق منه — لا من معامل يستطيع العميل تغييره — ثم قيّد كل استعلام لقاعدة البيانات بهذه الهوية. فبدلًا من «اعثر على الطلبية بهذا المعرّف»، اسأل «اعثر على الطلبية بهذا المعرّف والتي تعود أيضًا لهذا المستخدم»، وأعِد رمز 404 عند عدم التطابق حتى لا تكشف حتى أي المعرّفات موجودة. وادفع هذا المنطق إلى طبقة البيانات حيثما أمكن: مع Supabase يكون ذلك عبر سياسات Row-Level Security بحيث ترفض قاعدة البيانات نفسها القراءة عبر المستأجرين؛ ومع ORM تقليدي يكون عبر دالة تفويض مشتركة ومُختبَرة يجب أن يستدعيها كل معالِج. الهدف أن يكون المسار الآمن هو المسار الوحيد، لا قاعدة يجب أن تتذكرها كل نقطة نهاية جديدة. اتبع القائمة أدناه.
- أحصِ كل نقطة نهاية تقبل معرّفًا (في المسار أو الاستعلام) وكل عملية كتابة (POST/PUT/PATCH/DELETE) — فهذه هي مساحة هجوم BOLA لديك.
- لكل واحدة، أضِف تحققًا من الملكية: يجب أن يُرشّح الاستعلام حسب معرّف المستخدم المُصادَق عليه، المأخوذ من الجلسة/الرمز، لا من جسم الطلب أو الرابط.
- أعِد رمز 404 (لا 403) للسجلات التي لا يملكها صاحب الطلب، حتى لا تؤكّد أي المعرّفات موجودة.
- إذا كنت تستخدم Supabase، فعّل واكتب سياسات Row-Level Security (RLS) لتفرض قاعدة البيانات العزل حتى لو نسي أحد المسارات ذلك.
- فضّل المعرّفات غير القابلة للتخمين (UUIDs) على الأعداد المتسلسلة حتى لا يُمكن زيادتها ببساطة — دفاعٌ في العمق، لا بديل عن التحقق من الملكية.
- شغّل اختبار الحسابين (A يحاول قراءة/تحديث/حذف سجلات B) على كل نقطة نهاية، وأضِفه كاختبار آلي حتى لا يُعيد تعديلٌ مستقبلي من Cursor إدخال الثغرة بصمت.
- افحص الموارد المتداخلة والمرتبطة أيضًا (مثل /orders/123/items) — فقد يُتحقَّق من الأصل بينما يُهمَل الفرع.
لا شيء من هذا غريب أو معقّد — إنه سباكة منضبطة يتخطّاها مساعدو الذكاء الاصطناعي لأنك لم تطلبها. وإذا كنت تفضّل ألّا تُدقّق كل مسار يدويًا، فإن مهندسي البرمجيات والأمن الكبار في Comestare يتولّون عنك عمل إصلاح BOLA وما سواها، ثم تأمين تطبيقك المبني بالذكاء الاصطناعي وشحنه وتسويقه ليبقى حيًّا بعد الإطلاق.