صفر أخطاء حتى آخر مرحلة
كل مستخدم بحسابه وجلسته، على شاشات عمل حقيقية.
تقرير فني | الأمان والجاهزية للتشغيل
لم نكتب في هذا التقرير رقمًا لم نقِسه. شغّلنا على المنظومة اختبارات ضغط بمستخدمين حقيقيين مسجَّلي الدخول، وفحوص أمان آلية على الواجهة وداخلها، وراجعنا الكود سطرًا سطرًا في المواضع التي تلمس بيانات شخصية أو مالية. وما وجدناه ناقصًا أصلحناه قبل أن نكتب عنه.
من 5 إلى 50 مستخدمًا متزامنًا: صفر أخطاء.نتيجة اختبار الضغط
الخلاصة
المنظومة تحمّلت ضعف الذروة الواقعية للنادي دون خطأ واحد، واجتازت كل الفحوص الأمنية الآلية. وأقوى ما في حماية بيانات الدفع أنها غير موجودة أصلًا: المنظومة لا تخزّن أرقام بطاقات.
صفر أخطاء حتى آخر مرحلة
كل مستخدم بحسابه وجلسته، على شاشات عمل حقيقية.
لا تُخزَّن إطلاقًا
لا رقم بطاقة ولا CVV ولا تاريخ انتهاء — ولا بوابة دفع.
كلها تمر
أمان الواجهة، وجلسة مسجَّلة، ووحدات، وتوجيه، ونسخ احتياطي حقيقي.
لكل مسار صلاحيته
لا يوجد باب خلفي: الصلاحية تُفحص في الوسيط قبل الكود.
التحمّل: رفعنا الضغط تدريجيًا من 5 إلى 50 موظفًا متزامنًا، كلٌّ بحسابه وجلسته المستقلة، على شاشات ثقيلة فعلًا (67–76 كيلوبايت لكل صفحة: قوائم الأعضاء، ولوحة المؤشرات، والبحث، والاشتراكات). المنظومة أنهت 6,598 طلبًا بصفر أخطاء، وبلغت ذروة 106.6 طلبًا في الثانية. وتحت الإشباع لم تنكسر: طالت مدة الانتظار وحدها ثم عادت.
الأمان: اجتازت المنظومة فحصًا خارجيًا (33 فحصًا) وفحصًا داخليًا بجلسة موظف مسجَّل (6 فحوص) بنتيجة كاملة وصفر ملاحظات عالية الخطورة، إضافة إلى 68 اختبار وحدة تغطي الترميز والتوجيه والتشفير.
وما أصلحناه قبل الكتابة: وجدنا صور إثبات المخالفات تُحفظ في مجلد يفتحه أي شخص بالرابط دون تسجيل دخول — نقلناها خارج المجلد العام وجعلناها تمر بالصلاحية. ووجدنا الرقم القومي يظهر كاملًا لكل من يفتح صفحة العضو — صار مقنَّعًا إلا لمن يملك صلاحيته. ووجدنا المنظومة بلا أي نسخ احتياطي — فبنيناه مشفَّرًا ويوميًا.
لا نكتب في التقرير ما لم نجعله صحيحًا أولًا.قاعدة العمل في هذا التقرير
كيف تقرأ الأرقام: كل رقم هنا ناتج سكربت محفوظ مع المنظومة، ويمكن إعادة تشغيله في أي وقت ليعطي النتيجة نفسها. ملفات النتائج الخام محفوظة بجانب التقرير، وفصل «الاختبارات» يشرح كيف تُعاد.
التحمّل
رفعنا عدد الموظفين المتزامنين تدريجيًا وقِسنا زمن الاستجابة ونسبة الأخطاء في كل مرحلة. المهم ليس الرقم الأقصى، بل ما يحدث عند تجاوزه.
| موظف متزامن | طلبات | طلب/ثانية | أخطاء | وسيط (مللي) | p95 (مللي) | p99 (مللي) |
|---|---|---|---|---|---|---|
| 5 | 764 | 50.5 | 0.0% | 89.5 | 149.1 | 185.8 |
| 10 | 1,397 | 92.5 | 0.0% | 92.5 | 181.1 | 237.1 |
| 20 | 1,620 | 106.6 | 0.0% | 156.1 | 271.2 | 355.7 |
| 30 | 1,414 | 91.9 | 0.0% | 247.3 | 467.6 | 583.4 |
| 50 | 1,403 | 91.0 | 0.0% | 419.6 | 760.9 | 1026.4 |
اقرأ العمود الرابع أولًا: صفر أخطاء في كل مرحلة، حتى 50 مستخدمًا متزامنًا. المنظومة لم تُسقط طلبًا واحدًا ولم تُرجع خطأ خادم واحدًا طوال الاختبار.
عند 20 موظفًا متزامنًا — وهو أعلى من الذروة الواقعية لمكتب النادي — تأتي الصفحة الكاملة في أقل من ثلث ثانية لـ95٪ من الطلبات. وعند مضاعفة ذلك إلى 50، لم تظهر أخطاء: طال الانتظار إلى أقل من ثانية، ثم عادت المنظومة لوضعها فور انتهاء الضغط. هذا هو السلوك المطلوب تحت الضغط: طابور منظَّم، لا انهيار.
وبصراحة عن ظروف القياس: أُجري الاختبار على خادم مشترك يشغّل نحو سبعين تطبيقًا أخرى في الوقت نفسه، وكان محمّلًا قبل أن نبدأ. الأرقام أعلاه هي ما حققته المنظومة وهي تزاحم غيرها — وعلى خادم مخصَّص بالمواصفات الواردة في دليل التشغيل، المتوقع أعلى من ذلك. فضّلنا أن ننشر رقمًا متحفظًا قِسناه على رقم متفائل نقدّره.
ما الذي جعلها أسرع: راجعنا مسار الطلب ووجدنا أعمالًا تتكرر بلا داعٍ مع كل فتحة صفحة: كتابة في قاعدة البيانات لتحديث وقت آخر نشاط، وقفل عام على مستوى الخادم كله مع كل عملية حفظ، وأربعة استعلامات تمهيدية، ومطابقة كل مسار من 1864 مسارًا ببناء تعبير نمطي. أزلنا هذه كلها وأضفنا فهارس للفلاتر الفعلية.
والنمو بعد ذلك: المنظومة تُشغَّل كحاويات خلف موزِّع أحمال، فالتوسّع يكون بإضافة نسخ متوازية لا بتغيير الكود. دليل التشغيل يشرح الخطوة ومتطلبها (تخزين الجلسات المشترك) بالتفصيل.
بيانات الدفع
أكثر سؤال يُطرح على أي منظومة تتعامل مع مال: أين تُحفظ أرقام البطاقات، وكيف تُحمى؟ إجابتنا هنا مختلفة — لا تُحفظ.
بحثنا في بنية قاعدة البيانات كلها (595 ملف ترحيل) عن أي عمود يحمل رقم بطاقة أو رمز تحقق أو تاريخ انتهاء، وفي الكود كله عن أي تكامل مع بوابة دفع. لا يوجد ولا واحد.
حين يدفع العضو بالبطاقة، تتم العملية على جهاز نقاط البيع التابع للبنك. الجهاز يتعامل مع البطاقة، والمنظومة تسجّل ثلاثة أشياء فقط: أن طريقة الدفع «فيزا»، ورقم العملية المرجعي الذي يطبعه الجهاز، وأي جهاز استُخدم. هذه البيانات لا تكفي لإعادة استخدام البطاقة، ولا تُعدّ بيانات حامل بطاقة.
النتيجة العملية: تسريب قاعدة البيانات — في أسوأ احتمال — لا يُخرج رقم بطاقة واحدًا، ببساطة لأنه غير مخزَّن. وهذا يضع المنظومة خارج نطاق متطلبات PCI-DSS الخاصة بتخزين بيانات البطاقات عمليًا.
| البيان | الحالة | لماذا |
|---|---|---|
| رقم البطاقة (PAN) | لا يُخزَّن | لا يمر بالمنظومة أصلًا |
| رمز التحقق (CVV) | لا يُخزَّن | لا حقل له في أي شاشة |
| تاريخ الانتهاء | لا يُخزَّن | لا عمود له في القاعدة |
| بوابة دفع إلكترونية | غير مربوطة | الدفع على جهاز البنك |
| رقم العملية المرجعي | يُخزَّن | لمطابقة التحصيل مع كشف البنك — لا يفتح بطاقة |
الحقول الوحيدة التي تحمل كلمة «بطاقة» في المنظومة هي رقم كارنيه العضوية، ورقم البطاقة الضريبية للموظف — وكلاهما ليس وسيلة دفع.
الصلاحيات
الحماية الحقيقية للبيانات الشخصية ليست في جدار خارجي فقط، بل في أن الموظف نفسه لا يصل إلا لما يحتاجه. هذا مطبَّق على مستوى كل مسار في المنظومة.
اختبار حيّ أثناء إعداد هذا التقرير: سجّلنا الدخول بحساب موظف عضويات وفتحنا شاشة الإيصالات، فردّت المنظومة 403 — غير مصرَّح. وهذا ما نعنيه: المنع سلوك فعلي، لا بند في سياسة.
التشفير
ما الذي يحدث فعليًا لكلمة مرور الموظف، ولتوكن التطبيق، وللنسخة الاحتياطية؟ هذه إجابات محددة، لا عناوين عامة.
| ماذا | كيف | ماذا يعني عمليًا |
|---|---|---|
| كلمات مرور الموظفين والأعضاء | bcrypt (كلفة 12) | لا تُخزَّن ولا تُشفَّر — تُحوَّل لبصمة لا رجعة فيها. حتى نحن لا نستطيع قراءتها. |
| توكنات التطبيقات والـAPI | SHA-256 | يُخزَّن أثر التوكن فقط. تسريب الجدول لا يعطي توكنًا صالحًا. |
| سرّ التحقق بخطوتين | AES-256-GCM | مشفَّر بمفتاح ساكن خارج قاعدة البيانات في ملف بصلاحية 600. |
| النسخ الاحتياطي | AES-256-GCM + ضغط | يومي. نسخة مسروقة بلا المفتاح لا تُقرأ، وأي بايت يتغيّر فيها يُرفض عند الاستعادة. |
| الاتصال بالمتصفح | TLS + HSTS سنة كاملة | مع سياسة محتوى صارمة ومنع التأطير وكوكي الجلسة مقفول عن الجافاسكربت. |
| قاعدة البيانات | غير منشورة على الإنترنت | لا تملك منفذًا عامًا — تُرى من داخل الشبكة الداخلية فقط. |
النسخ الاحتياطي كان الفجوة الأخطر. حين راجعنا المنظومة لم يكن فيها أي آلية نسخ احتياطي: لا مهمة مجدولة ولا سكربت ولا أمر — رغم وجود صلاحية باسم «النسخ الاحتياطي» بلا تنفيذ وراءها. بنينا النظام كاملًا قبل كتابة هذا السطر: نسخة يومية تُؤخذ بمعاملة واحدة متسقة، تُضغط، ثم تُشفَّر بمفتاح مشتق من مفتاح التطبيق المحفوظ خارج القاعدة. واختبرناه في الاتجاهين: النسخة تُستعاد مطابقة للأصل بالبايت، وقلب بِت واحد فيها يجعل الاستعادة ترفضها بدلًا من أن تُخرج بيانات تالفة بصمت.
وجرّبناه على قاعدة البيانات الفعلية للنسخة التجريبية: 442 جدولًا، و459 ميجابايت من البيانات صارت ملفًا مشفَّرًا بحجم 46.7 ميجابايت في 42 ثانية، لا يظهر فيه اسم جدول واحد ولا سطر بيانات واحد، ثم استُعيد كاملًا بجداوله كلها. وأثناء هذه التجربة اكتشفنا أن النسخة كانت تُحمَّل كلها في الذاكرة دفعة واحدة — فأعدنا بناءها لتمر عبر القرص، فنزلت ذروة الذاكرة إلى 169 ميجابايت وصارت تتحمل نمو البيانات لسنوات.
لماذا موقع المفتاح مهم: المفتاح لا يُحفظ في قاعدة البيانات ولا داخل النسخة الاحتياطية. من يحصل على نسخة من البيانات وحدها لا يحصل على ما يفكّها.
سجل المراجعة
السجل الذي يمكن تعديله لا قيمة له. سجل المنظومة مربوط بسلسلة توقيع، فأي حذف أو تبديل يظهر فورًا عند التحقق.
كل عملية إنشاء أو تعديل أو حذف تُكتب في سجل المراجعة بصورة السجل قبل التعديل وبعده، ومع من قام بها ومن أي عنوان وفي أي وقت. وكلمات المرور والتوكنات تُحجب من السجل نفسه.
وما يجعله غير قابل للتزوير: كل سطر يحمل بصمة مبنية على بصمة السطر الذي قبله، موقَّعة بمفتاح محفوظ خارج قاعدة البيانات. من يملك صلاحية كاملة على قاعدة البيانات ولا يملك المفتاح لا يستطيع تعديل سطر أو حذفه دون أن ينكسر التسلسل — والتحقق يكشف موضع الكسر بالتحديد. ورأس السلسلة يُحفظ أيضًا خارج القاعدة، فحتى الحذف من الآخر يظهر.
التحقق متاح كأمر يُشغَّل في أي وقت: php cli.php audit:verify — يطبع عدد السطور الموقَّعة، ويؤكد سلامة السلسلة أو يحدد أول موضع عبث.
الذكاء الاصطناعي
في المنظومة مساعد ذكي يجيب المديرين عن أسئلة بالعربية. وهذا بالضبط النوع الذي يجب أن يُسأل عنه بصرامة: ماذا يرى، وماذا يخرج منه؟
أضفنا في هذه المراجعة منع دوال معلومات الخادم (نسخة قاعدة البيانات، اسم المستخدم، مسار الملفات) حتى لا تتسرب تفاصيل البنية التحتية في إجابة.
الاختبارات
كل رقم في هذا التقرير ناتج سكربت محفوظ مع الكود. هذه قائمتها ونتائجها، ويمكن لأي مراجع إعادة تشغيلها والحصول على النتيجة نفسها.
| الاختبار | الحجم | النتيجة | ماذا يغطي |
|---|---|---|---|
| فحص أمني خارجي (بدون تسجيل دخول) | 33 فحصًا | 33 / 33 | الترويسات، والتشفير، ومنع الوصول للملفات، وتسريب الأخطاء، ومحاولات حقن. |
| فحص أمني بجلسة موظف مسجَّل | 6 فحوص | 6 / 6 | سلامة الجلسة، وحقن SQL، وXSS منعكس، وفرض رمز الحماية، وتسريب المعلومات. |
| اختبارات وحدة أمنية | 47 اختبارًا | 47 / 47 | ترميز المخرجات، ومنع التحويل الخارجي، وتقنيع الرقم القومي، واقتباس المعرّفات. |
| اختبارات التوجيه | 13 حالة | 13 / 13 | تتأكد أن تسريع المطابقة لم يغيّر أي قاعدة توجيه. |
| اختبارات تشفير النسخ الاحتياطي | 8 اختبارات | 8 / 8 | الاسترجاع المطابق، ورفض الملف المعدَّل، ورفض المفتاح المختلف. |
| نسخة احتياطية حقيقية من البداية للنهاية | 442 جدولًا | استُعيدت كاملة — والعبث رُفض | أخذ نسخة من القاعدة الفعلية، وتشفيرها، واستعادتها، ورفض ملف معدَّل ببايت واحد. |
| اختبار الضغط | 5 مراحل | 6,598 طلبًا — صفر أخطاء | بمستخدمين مسجَّلي الدخول، كلٌّ بحسابه، على شاشات عمل حقيقية. |
إعادة التشغيل: سكربتات الاختبار محفوظة في مجلد tests/ داخل المنظومة، والنتائج الخام بصيغة JSON محفوظة في docs/security-report/evidence/. اختبار الضغط يتوقف تلقائيًا إذا ارتفعت الأخطاء، حتى لا يؤذي خادمًا مشتركًا.
الصراحة
تقرير أمني بلا قائمة كهذه تقرير ناقص. هذه هي الخطوات التالية التي نوصي بها، ولا يمنع أيٌّ منها التشغيل اليوم.
| البند | الوضع اليوم | التوصية |
|---|---|---|
| تشفير الحقول الشخصية داخل القاعدة نفسها | اليوم تُحمى بالصلاحيات والتقنيع والعزل الشبكي والنسخ المشفَّرة. | يضيف طبقة أخيرة لو وصل أحد للقرص نفسه. يحتاج فهرسة عمياء للبحث بالرقم القومي. |
| تسجيل من اطّلع على البيانات، لا من عدّلها فقط | السجل اليوم يثبت كل تعديل، ولا يسجّل عمليات العرض والتصدير. | نوصي بتسجيل فتح الشاشات الحسّاسة وتصدير الكشوف باسم الموظف. |
| تشفير الاتصال بين التطبيق وقاعدة البيانات | الاتصال اليوم داخل شبكة داخلية مغلقة غير منشورة على الإنترنت. | تفعيل TLS بينهما يحمي حتى من متنصّت داخل الشبكة نفسها. |
| حجب البيانات الشخصية داخل سجل المراجعة | السجل يحجب كلمات المرور والتوكنات، ويحتفظ بباقي القيم كما هي. | نوصي بتقنيع الرقم القومي والحساب البنكي داخل السجل أيضًا. |
| اختبار سعة على خادم مخصَّص | الأرقام الحالية مقيسة على خادم مشترك محمّل مسبقًا. | القياس على بيئة معزولة سيعطي الرقم الحقيقي للمواصفات المطلوبة. |
بند تشغيلي عاجل خارج الكود: كلمة مرور قاعدة البيانات كانت محفوظة في مستودع الكود، وأزلناها منه. لكنها تبقى صالحة في تاريخ المستودع حتى تُغيَّر فعليًا — وهذه خطوة يدوية نوصي بتنفيذها قبل التسليم، وخطواتها في دليل التشغيل.