sbarbase
المحتويات

نموذج التهديد

ما هو

تعرض هذه الصفحة ما يحميه صباربيز، وممن يحميه، وأين تقع حدود الثقة، وأي تخفيف وأي فحص مسجّل يسند كل ادعاء. وتنتهي بالثغرات المعروفة التي ما زالت مفتوحة. تصف الصفحة الكود في هذه المراجعة، وهو قيد التطوير وليس جاهزًا للإنتاج؛ وهي ليست شهادة أمنية.

طريقة الإبلاغ عن مشكلة موجودة في سياسة الأمان.

لماذا

يضع صباربيز مشاريع Supabase كثيرة على خادم واحد. ولا تستحق مشاركة الخادم العناء إلا إذا عجز زوار بيئة ومفاتيحها وحسابات دخول خدماتها عن الوصول إلى بيئة أخرى، وعرف المشغّل بدقة أي الأجزاء مشتركة. نموذج تهديد مكتوب مقابل الكود، يرتبط فيه كل ادعاء باختبار أو ملف أدلة، يُبقي التصميم صادقًا: التخفيف الذي لا فحص له يُسرد ثغرةً، لا ميزةً.

الخيار: مشغّلون موثوقون، وزوار تطبيقات غير موثوقين. هذا نموذج الثقة نفسه في العزل والثقة ومراجعة الأمان والتشغيل. لا نحاول الحماية من مشغّل خادم معادٍ.

كيف بنيناه

زوار التطبيقات لا يصلون إلا إلى وكيل TLS، ثم إلى لوحة الإدارة أو البوابة؛ كل بيئة حدٌّ قائم بذاته، والمحرك المشترك وStorage حدود فشل مشتركة

حدود الثقة: الزوار غير الموثوقين لا يدخلون إلا عبر وكيل TLS والبوابة.

الأصول

الأصل أين يوجد
بيانات التطبيق لكل بيئة قاعدة بيانات PostgreSQL واحدة لكل بيئة على المحرك المشترك؛ وكائنات تلك البيئة في عملية Storage المشتركة
بيانات اعتماد الخدمات ثلاثة حسابات دخول للخدمات في كل بيئة (Auth وREST وStorage) بكلمات مرور تُولَّد لكل بيئة (secrets.token_hex(32) في Runtime.provision، lab/durable_runtime.py)، محفوظة في مجلد الحالة الخاص
مفاتيح التوقيع سر توقيع Auth ومفاتيح توقيع Storage لكل بيئة؛ ومفاتيح API السرية، ولا يُحفظ منها إلا بصمة SHA-256، والمفاتيح العامة، وهي عامة بطبيعتها وتُحفظ لتعرضها اللوحة مرة أخرى (src/control/keys.ts)
رموز الوصول رموز شخصية (sbp_...) للوكلاء والأدوات، لا يُحفظ منها إلا بصمة SHA-256 مع نطاقها وتاريخ انتهائها (src/control/access-tokens.ts)
أسرار Vault الجدول vault.secrets في كل بيئة، مشفّرًا بمفتاح جذري واحد يخص الخادم
هوية المشغّل حسابات في نطاق إدارة مخصص في Auth، منفصل عن كل Auth للتطبيقات (src/control/auth.ts)؛ والعضويات والأدوار في فهرس التحكم
النسخ الاحتياطية حزم التصدير المشفّرة لكل بيئة ومفاتيحها؛ وأي نسخ من وحدات تخزين قواعد البيانات يصنعها المشغّل

الأطراف

الطرف الثقة ما يستطيع فعله
مشغّل الخادم موثوق صلاحيات root أو Docker على الخادم، وحساب دخول مدير PostgreSQL، ومجلد الحالة الخاص، وأي SQL في أي بيئة. لا دفاع ضده.
زائر التطبيق غير موثوق طلبات مجهولة الهوية أو بعد تسجيل الدخول إلى أي بيئة عبر البوابة، بالمفتاح العام لتلك البيئة ورمزه الخاص.
خدمة بيئة مخترقة أو مفتاح بيئة مسرّب غير موثوق يملك كلمة مرور حساب دخول خدمة في بيئة واحدة، أو مفتاحها العام أو السري، أو رمز Auth فيها، ويحاول الوصول إلى بيئة أخرى أو إلى المنصة.
رمز وصول مسرّب أو وكيل مُضلَّل غير موثوق يملك رمز وصول شخص (sbp_...)، أو هو وكيل ذكاء اصطناعي يعمل على محتوى غير موثوق، ويستدعي نقطة MCP أو واجهة /v1. يستطيع ما يستطيعه ذلك الشخص ضمن نطاق الرمز وقيد القراءة فقط، عبر مسارات لوحة الإدارة نفسها؛ ولا يستطيع إدارة الرموز ولا الأعضاء ولا إعدادات تسجيل الدخول ولا الخادم. ويُنهيه الإلغاء وانتهاء الصلاحية وإعادة كلمة المرور من الخادم (وكلاء الذكاء الاصطناعي).
اعتمادية مخترقة غير موثوق صورة أصل أو حزمة أو إجراء CI معدّل، يعمل بصلاحيات المكوّن الذي يحمّله.
مهاجم على الشبكة غير موثوق يراقب الحركة بين الزوار أو المشغّلين والخادم أو يعبث بها، أو يرسل طلبات مصنوعة إلى كل ما هو مكشوف.

حدود الثقة

الزوار والمهاجمون على الشبكة في الإنترنت غير الموثوق لا يصلون إلا إلى وكيل TLS على المنفذ العام. B1 عند البوابة التي تطلب مفتاحًا عامًا ورمزًا؛ وB3 عند واجهة الإدارة ولوحتها التي تطلب رمز مشغّل؛ وB4 عند الوكيل، الجزء الوحيد المواجه للشبكة؛ وB2 بين كل بيئة ومحرك PostgreSQL المشترك عبر زوج قواعد الاتصال الخاص بها؛ وB5 حول وصول المشغّل إلى سطر الأوامر وDocker وSQL والحالة الخاصة؛ وB6 للصور المثبّتة

ستة حدود، لكل منها اسم في القائمة أدناه وفي الجدول بعدها.

التهديدات والتخفيف

تكتب الفحوص الحية ملفات الأدلة، ولكل ملف نطاقه الخاص؛ وأسماء الفحوص أدناه منقولة منها حرفيًا. الاختبارات تعمل ضمن حزم اختبارات الوحدة.

الحد التهديد التخفيف الإثبات
B1 طلب بلا مفتاح، أو بمفتاح البيئة A على مسار البيئة B تعبير نمطي على المسار يختار البيئة؛ ويجب أن يُحَل المفتاح لتلك البيئة في مخزن المفاتيح؛ والبيئات المعطّلة أو المجهولة تجيب بـ404 (src/gateway/handler.ts) tests/gateway.test.ts ("missing key"، "cross environment"، "disabled environment")؛ storage-gateway-checks.json ("API key cannot select neighbor Storage route")
B1 العميل يختار خادم الأصل أو يخرج من المسار الأصل يأتي من سجل المسارات وحده؛ والشرطات المائلة المرمّزة والشرطات العكسية وNUL مرفوضة؛ ولا تُمرَّر إلا قائمة ثابتة من الترويسات tests/gateway.test.ts ("encoded slash"، "forward selected route and strip client-injected internal headers")
B1 العميل يختار مستأجر Storage آخر ترويسة المستأجر لا تُضبط إلا من إعدادات المسار الموثوقة storage-gateway-checks.json ("injected tenant header cannot select neighbor")؛ tests/gateway.test.ts
B1 مفتاح ملغى يظل يعمل، أو عطل في مخزن المفاتيح يرتد إلى قبول المفاتيح البحث يستثني الصفوف الملغاة؛ وخطأ البحث يجيب بـ503، ولا يرتد أبدًا إلى مفتاح ثابت tests/gateway.test.ts ("removed key stops subsequent requests"، "key store failure cannot fall back to static key acceptance")؛ connection-checks.json ("revocation immediately removes gateway access")
B1 قراءة Storage بلا مفتاح تصل إلى بيانات خاصة لا يُعفى من المفتاح إلا قراءة الكائنات العامة والروابط الموقّعة ذات الرمز الواحد؛ ويتحقق Storage من الظهور والتوقيع tests/gateway.test.ts ("only public and signed Storage reads may omit API keys")؛ storage-url-checks.json ("public URL cannot disclose private bucket"، "signed URL cannot select another environment")
B1 بيئة واحدة تُغرق البوابة قبول تزامني لكل بيئة بلا طابور، وحد 1 MiB للجسم (ورفع Storage حتى حد الرفع، ويفشل بعد توقف ثلاثين ثانية)، ومهلة لقراءة الجسم، ومهلة 15 ثانية للأصل tests/concurrency.test.ts؛ tests/gateway.test.ts ("oversize stream rejected before upstream"، "slow body deadline ...")؛ gateway-overload-checks.json
B2 رمز مستخدم من البيئة A يُستعمل في Auth أو REST في البيئة B سر توقيع Auth منفصل لكل بيئة upstream-environment-checks.json ("env_alpha token denied by env_beta Auth"، "... by env_beta REST")
B2 حساب دخول خدمة في البيئة A يتصل بقاعدة B أو يصبح مديرًا لا تُمنح صلاحية CONNECT إلا لحسابات دخول البيئة نفسها؛ وقواعد HBA تسمّي أزواج القاعدة وحساب الدخول بدقة مع SCRAM، وتنتهي بـreject لكل ما عداها (hba_content في lab/durable_runtime.py) upstream-environment-checks.json ("env_alpha auth denied database env_beta"، "env_alpha REST role cannot assume global admin")؛ shared-storage-checks.json ("env_alpha storage credential denied by env_beta")
B2 قواعد الاتصال تضيع أو تُكتب نصف كتابة بعد انهيار، فتُفتح أزواج خاطئة أو تُغلق نشر HBA مسجَّل في سجل العملية ومسيّج، بملكية دقيقة وتأكيد لإعادة التحميل lab/test_hba_authority.py، lab/test_atomic_hba.py؛ upstream-hba-apply-checks.json
B2 مسار الكائن نفسه في بيئتين يعيد بايتات البيئة الأخرى مستأجر Storage لكل بيئة بقاعدة بياناته ومفاتيح توقيعه shared-storage-checks.json ("env_alpha same object path returns only its own bytes"، "env_alpha rejects other environment service token")
B3 واجهة الإدارة تقبل رمز مستخدم من التطبيق هوية الإدارة تأتي من نقطة Auth ثابتة ومخصصة؛ والمستخدمون المجهولون مرفوضون management-checks.json ("application realm token rejected by live management Auth")؛ tests/management.test.ts
B3 انتحال الفاعل أو الدور في ترويسة أو جسم طلب الفاعل يُستخرج من المصادقة وحدها؛ والعضويات تُقرأ من الفهرس في كل طلب management-checks.json ("body actor spoofing rejected"، "valid nonmember denied despite injected owner header")؛ tests/catalog.test.ts
B3 هوية التطبيق تُصدر المفاتيح أو تسردها مسارات المفاتيح تتطلب فاعلًا إداريًا له صلاحيات على تلك البيئة؛ والمفاتيح الخام تُعاد مرة واحدة، والقوائم لا تحمل إلا البيانات الوصفية connection-checks.json ("application identity cannot issue management keys"، "key list never discloses raw token")؛ tests/key-http.test.ts، tests/keys.test.ts
B4 إعادة توجيه مفتوحة أو حقن ترويسة host عند الحافة العامة إعادة التوجيه والمضيف المُمرَّر يأتيان من --public-host؛ وترويسات التمرير التي يرسلها العميل تُحذف؛ ويجب أن يكون الأصل على loopback (deploy/console-tls-proxy.ts) tls-termination.json ("the redirect never points at a client supplied host"، "a non-loopback upstream is refused")؛ lab/test_tls_termination.py
B4 تسجيل بيانات الاعتماد عند الحافة، أو مفتاح شهادة قابل للقراءة الوكيل لا يسجّل إلا الطريقة والمسار والحالة؛ ويرفض مفتاحًا تقرؤه المجموعة أو الجميع tls-termination.json ("a group or world readable key is refused")؛ lab/test_tls_termination.py (test_the_proxy_never_logs_bodies_or_credentials)
B5 ملفات الأسرار يقرؤها مستخدمون آخرون ملف المشغّل ومخزن المفاتيح يُكتبان بالصلاحية 0600 ملف المشغّل مختبر في lab/test_operator_file.py؛ وصلاحية مخزن المفاتيح مضبوطة في src/control/keys.ts (chmodSync(path,0o600))، في المصدر فقط، بلا اختبار
B5 كلمات مرور في مخرجات الأوامر أو الإشعارات أو الأدلة المسجّلة حجب الأسرار قبل الإخراج؛ والملخصات تُسقط قيم الأسرار lab/test_mail_config.py، lab/test_notifications.py، lab/test_deployment_rehearsal.py، lab/test_runtime_reuse.py
B5 حزمة التصدير تحمل بيانات اعتماد على مستوى المنصة التصدير لا يضم إلا حسابات دخول البيئة المختارة وإعداداتها recovery-export-checks.json ("shared platform credentials not added to configuration")
B6 صورة أصل عُبث بها أو تغيّرت بصمت الصور تعمل ببصمة مثبّتة؛ والفحص المسبق يرفض تثبيتًا مفقودًا أو غير مطابق ولا يسحب شيئًا أبدًا؛ والحاويات المحفوظة تُفحص بحثًا عن الانحراف قبل إعادة استخدامها lab/test_pinned_images.py، lab/test_runtime_reuse.py؛ pinned-images.json

مقارنة بحساب دخول خدمة مشترك

مشروع GustavoMartins123/supabase-multitenant هو الأقرب إلينا في البنية بين المشاريع الموجودة. في قوالبه لكل مشروع عند الإيداع 0ec74541 (2026-08-11)، تتصل Auth وREST وStorage في كل مشروع بالأدوار العامة على مستوى العنقود supabase_auth_admin وauthenticator وsupabase_storage_admin، بكلمة المرور الوحيدة POSTGRES_PASSWORD المقروءة من الملف المشترك servidor/.env؛ ولاحقة .project_id على اسم المستخدم تختار مستأجر المشروع في مجمّع الاتصالات (dockercomposetemplate، .envtemplate). أما صباربيز فينشئ ثلاثة حسابات دخول لكل بيئة بكلمات مرور مولَّدة خاصة بها، وقاعدة HBA واحدة لكل زوج دقيق من القاعدة وحساب الدخول. هذه قراءة للمصدر، لا اختبار لذلك المشروع، ولا تقول شيئًا عن بقية تصميمه؛ والمقارنة الأوسع في البدائل.

الحدود

الثغرات المعروفة

هذه الثغرات مفتوحة. نسردها ليقرر المشغّل هل يناسبه صباربيز، ولكي يُقاس أي بلاغ عنها بما هو معلن أصلًا.

مجموعة docker: قرار

يدير المشرف حاوياته عبر واجهة Docker البرمجية. يسحب الصور المثبّتة الإصدار، وينشئ الشبكات ووحدات التخزين، وينشئ الحاويات ويشغّلها، ويشغّل psql وpg_isready داخل حاوية قاعدة البيانات عبر docker exec، ويقرأ inspect وstats والسجلات. وتقرأ عملية لوحة الإدارة استهلاك الحاويات وسجلاتها عبر المقبس نفسه (src/control/observe.ts). تمنح الوحدة هذا الوصول بـSupplementaryGroups=docker. ومن يستطيع الكتابة إلى مقبس Docker يستطيع تشغيل حاوية بـ--privileged أو بجذر الخادم / مركّبًا فيها، فحساب الخدمة، وأي كود يعمل به، هو root على الخادم فعليًا. لذلك فإن ثغرة تنفيذ كود في المشرف أو العامل أو لوحة الإدارة اختراق للخادم كله، لا للتثبيت وحده.

درسنا ثلاثة خيارات.

يبقى حساب الخدمة في مجموعة docker، ويعدّه نموذج التهديد هذا root. وما يترتب على المشغّل: شغّل صباربيز على خادم لا يحمل شيئًا يجب حمايته من التثبيت نفسه، وعامل الوصول إلى حساب الخدمة معاملة الوصول إلى root، وأبقِ لوحة الإدارة خلف وسيط TLS على loopback كما يشرح دليل النشر على الخادم. وDocker بلا صلاحيات root هو الخيار الذي نبدأ بتجربته، لأنه ينقل الحد نفسه بدل أن يضيف مرشّحًا أمامه، ويحتاج إلى تجربة كاملة خاصة به على خادم قبل أن نَعِد بشيء.

للتعمق

عدّل على GitHub