الأمان لدينا ليس طبقة أُضيفت لاحقًا، بل مركز المعمارية. عزل البيانات، وفصل الهوية، والكتابة المتعادلة (idempotent) منقوشة في أدنى طبقة من الشيفرة — ليست إعدادًا يُغلَق، بل طريقة عمل النظام.
عزل على مستوى الصف
كتابة متعادلة
استعلام بمعاملات
عزل Loopback
عزل البيانات
عزل على مستوى الصف — fail-closed
في كل جدول مرتبط بالمستأجر، تكون خاصية PostgreSQL Row-Level Security بوضع FORCE مُفعّلة؛ حيث تربط السياسة كل صف بالشرط "tenant_id = current_setting('app.current_tenant')".
استعلام يصل دون تهيئة سياق المستأجر يُعيد صفر صفوف — ليس خطأ حتى، بل فارغ بصمت. رؤية بيانات عيادة بجلسة عيادة أخرى مستحيلة معماريًا.
اختبار عزل المستأجرين متقاطعًا أخضر إلزاميًا في التكامل المستمر (CI)؛ إن كان أحمر لا تُدمَج الشيفرة.
كتابة ذرّية
هوية محلية للمعاملة، عملية ذرّية
يُضبط معرّف المستأجر في بداية كل عملية كتابة داخل الـ transaction، ويظل صالحًا طوال تلك الـ transaction فقط؛ فهو لا يلتصق بمجمّع الاتصالات ولا يتسرّب إلى طلبات أخرى.
العمليات متعددة الكيانات (فاتورة + بند + دفعة + خصم مخزون) ذرّية داخل معاملة واحدة: إما أن تُكتَب كلها معًا أو لا شيء منها. لا يُنشأ سجل ناقص أو غير متسق.
الصلاحية
تُفرَض الصلاحية على أساس الدور (RBAC) على الخادم
يُجرى فحص الصلاحية في طبقة API؛ لا يُوثَق أبدًا بالعميل أو الواجهة الأمامية. إخفاء قائمة في المتصفح يتعلق بالمظهر فقط — القرار الحقيقي على الخادم.
طفرات النواة (المرحلة 1) محمية بحسب أدوار المدير والسكرتير والطبيب. حتى لو استدعى دور غير مخوَّل النقطة الطرفية، يُرفَض الطلب.
حماية من التكرار
كتابة متعادلة — لا عملية مزدوجة
يحمل كل طلب كتابة مفتاح Idempotency-Key؛ ويمنع القفل على جانب الخادم العملية الثانية القادمة بالمفتاح نفسه.
حتى عند انقطاع الشبكة أو إعادة العميل إرسال الطلب نفسه، لا تُنشأ دفعة أو فاتورة ثانية. تكون النتيجة دائمًا واحدة وقابلة للتنبؤ.
أمان الاستعلام
SQL injection مستحيل — استعلام بمعاملات
جميع استعلامات قاعدة البيانات عبارات مولَّدة وقت التجميع بـ sqlc، وبمعاملات بالكامل. لا يوجد في قاعدة الشيفرة سطر واحد يبني SQL بدمج نصوص يدوي مع مدخلات المستخدم.
هذا يُزيل بنيويًا فئة هجمات SQL injection — إذ يعتمد على طريقة بناء الاستعلام نفسها، لا على جهد «تصفية» أو «تهريب».
سطح الشبكة
عزل Loopback — النواة مغلقة على الخارج
يستمع الـ API الأساسي إلى 127.0.0.1 (loopback) فقط. أي محاولة للارتباط بعنوان 0.0.0.0 أو بعنوان IP عام تُرفض عند بدء التشغيل؛ ولا يُفتح النواة على الإنترنت تحت أي ظرف.
السطح العام الوحيد هو بوابة رقيقة (BFF): لا تحتفظ بأي منطق أعمال باستثناء الجلسة والعرض وحماية CSRF. لا يستطيع المتصفح أبدًا رؤية الـ API الأساسي أو الوصول إليه مباشرة أو تحديد عنوانه.
الهوية والجلسة
كلمة مرور argon2id، مفتاح جلسة معتم (opaque)
تُشفَّر كلمات المرور باستخدام argon2id، وهي خوارزمية قياسية في الصناعة وصعبة على الذاكرة؛ ولا يُستخدم أي تجزئة عادية أو ضعيفة.
مفاتيح الجلسة هي قيم عشوائية مُعمّاة (opaque) بطول 32 بايت مولّدة تشفيريًا، تُحفظ في Redis وتنتهي صلاحيتها عبر TTL. لا يظهر للمتصفح سوى ملف تعريف ارتباط بخصائص HttpOnly + Secure + SameSite؛ ولا يُكشف المفتاح أبدًا لـ JavaScript.
التدقيق
سجل تدقيق غير قابل للتغيير (بالإضافة فقط)
تُكتَب العمليات المهمة في سجل تدقيق بالإضافة فقط داخل المعاملة ذاتها التي تطلقها الطفرة. إن تراجعت العملية لا يُكتَب سجل التدقيق أيضًا — لا يمكن الفصل بينهما.
سُحبت صلاحية UPDATE/DELETE من جدول التدقيق؛ فلا يمكن تعديل السجلات أو حذفها لاحقًا. يبقى الماضي كما هو.
الخصوصية
تقنيع البيانات الحساسة
لا يدخل TCKN ولا كلمة المرور ولا مفتاح الجلسة ولا البيانات الصحية إلى السجلات بأي شكل. حتى في مخرجات التصحيح تُقنَّع هذه الحقول.
عند الحاجة تُشفَّر الحقول الحساسة على مستوى العمود. تُحمى البيانات الصحية ذات الطبيعة الخاصة بقواعد وُضِعت داخل التصميم منذ البداية.
سلامة المال
أمان المال — numeric، وقيد عكسي
يُحفَظ المال في كل مكان بصيغة numeric(14,2)؛ ولا يُحسَب المال بـ float. يقع التقريب في نقطة واحدة على الخادم — فلا ينشأ انزياح قروش أو فرق تقريب.
يُعالَج الإلغاء والاسترداد كقيد عكسي بدل حذف السجل. لا يُكتَب الرصيد الجاري يدويًا؛ بل يُشتَقّ دائمًا من مجموع الفواتير والمدفوعات. لا يفسد الماضي.
النقل
أمان النقل والمتصفح
تُحمَل كل حركة المرور عبر HTTPS/TLS وHSTS مفعَّل؛ لا يُنشئ المتصفح الاتصال إلا مشفَّرًا.
تمنع سياسة أمان المحتوى الصارمة (CSP) النصوص البرمجية المضمّنة؛ ويحول X-Frame-Options DENY دون هجمات clickjacking، ويمنع X-Content-Type-Options nosniff الخلط في أنواع المحتوى. كما تُطبَّق Referrer-Policy وPermissions-Policy.
مهم
فصل طبقة الهوية + مستوى إدارة منفصل
مستخدم العيادة وبوابة المريض ومشغّل المنصة هي طبقات هوية منفصلة: لكل منها ترويسة خاصة ونطاق جلسة خاص. لا يعمل مفتاح إحدى الطبقات في طبقة أخرى — فحدود الصلاحيات مفصولة فصلًا صارمًا.
لا تظهر نقاط النهاية الخاصة بالمنصة والإدارة (تهيئة المستأجرين، والفوترة، وإدارة الأتمتة/الإضافات، وتوليد مفاتيح API) في الـ API العام إطلاقًا.
تعمل هذه الوظائف على مستوى تحكّم منفصل داخلي يمتلك نمط هوية مختلفًا (مفتاح إدارة خاص)؛ لا يمكن الوصول إليه أو اكتشافه من الخارج. بذلك يتقلّص سطح الهجوم بوضوح.
كيف يعمل
معاملة واحدة، عزل fail-closed
يوضّح التدفق المفاهيمي أدناه كيف تُقفَل كل عملية كتابة على المستأجر وتبقى متعادلة (idempotent). هو لأغراض الشرح فقط.
# 1) يُنشأ سياق المستأجر في بداية الـ transaction (خاص بهذه الـ tx فقط)
BEGIN;
SELECT set_config('app.current_tenant', :session_tenant_id, true);
# 2) قفل Idempotency-Key — لا تُنشأ عملية ثانية بالمفتاح نفسهSELECT lock_idempotency(:idempotency_key);
# 3) الكتابة متعددة الكيانات — الكل ذرّي داخل tx واحدةINSERT INTO invoices ...; # RLS: tenant_id يُشمَل تلقائيًاINSERT INTO payments ...;
INSERT INTO audit_log ...; # append-only, نفس tx
COMMIT;
# إن لم يُهيّأ السياق (context)، لا تُعيد سياسة RLS أي صف:SELECT * FROM patients; --> 0 صف (ليس خطأ، فارغ بصمت)
قابل للتحقق باستقلالية
جزء من أمان النقل قابل للملاحظة من الخارج: يمكنك بنفسك فحص شهادة HTTPS/TLS لاتصالك، وترويسات أمان الاستجابة مثل HSTS وسياسة أمان المحتوى، عبر أدوات المطوّر في متصفحك. تستند ادعاءاتنا إلى ضوابط معمارية، لا إلى عبارات تسويقية.
هل لديك سؤال عن معمارية الأمان؟
يسرّ فريقنا مناقشة التفاصيل التقنية ومتطلبات الامتثال معك.