تخطي للذهاب إلى المحتوى

RLS في Supabase وقواعد Firebase: أغلق قاعدة بياناتك

لماذا لا تكمن المشكلة في المفتاح العام، وماذا يحدث حين تكون RLS معطّلة أو القواعد مفتوحة، مع سياسات وقواعد تستطيع نسخها واختبارها
7 نوفمبر 2026 بواسطة
RLS في Supabase وقواعد Firebase: أغلق قاعدة بياناتك
Hassan Tabbal

أمان مستوى الصف في Supabase (Row Level Security / RLS) وقواعد الأمان في Firebase (Security Rules) هي الأقفال التي تحدّد من يستطيع قراءة كل سجل في قاعدة بياناتك وتغييره. فالمنصتان تسمحان للمتصفح أو لتطبيق الهاتف بالتواصل مع قاعدة البيانات مباشرة، باستخدام مفتاح عام يراه الجميع، لذلك يجب أن تتحقق قاعدة البيانات نفسها من كل طلب. في Supabase تفعّل RLS لكل جدول وتكتب سياسة مثل «لا يرى المستخدم إلا الصفوف التي يكون فيها user_id هو معرّفه». وفي Firebase تكتب قواعد تقارن request.auth.uid بمالك كل مستند. فإن كانت RLS معطّلة أو كانت القواعد تقول if true، يستطيع أي شخص يفتح موقعك أن ينسخ بياناتك أو يحذفها.

هذا الدليل موجّه للمؤسسين والمطوّرين الذين بُني تطبيقهم بسرعة، وغالباً باستخدام Lovable أو Bolt أو Replit أو Cursor أو أداة ذكاء اصطناعي أخرى، ولأصحاب الأعمال الذين يريدون معرفة مدى انكشافهم. ويشرح لماذا لا تكمن المشكلة في المفتاح، وما الذي حدث لشركات حقيقية، وكيف تكتب القواعد وتختبرها على المنصتين.

لماذا يتواصل المتصفح مع قاعدة بياناتك مباشرة

في تطبيق الويب التقليدي يتواصل المتصفح مع خادمك، والخادم وحده يتواصل مع قاعدة البيانات، فيقرّر كود الخادم ما يراه كل مستخدم. أما Supabase وFirebase فتزيلان هذه الطبقة الوسطى توفيراً للوقت، إذ يرسل التطبيق في المتصفح استعلامه مباشرة إلى واجهة برمجة قاعدة البيانات (API)، ولا تحتاج إلى كتابة خادم خلفي (Backend) واستضافته لكل شاشة.

هذه السرعة هي سبب اعتماد أدوات بناء التطبيقات بالذكاء الاصطناعي عليهما، وهي تعني أن التحقق الذي كان يجري في كود الخادم صار يجب أن يجري في قاعدة البيانات. وتصف وثائق Firebase نفسها قواعدها بأنها الحماية الوحيدة بين بياناتك وبين أي مستخدم خبيث.

المفتاح العام ليس سراً

افتح أدوات المطوّر في أي تطبيق مبني على Supabase أو Firebase وستجد رابط المشروع ومفتاح API داخل كود JavaScript، وهذا أمر طبيعي. فـالمفتاح العام (Publishable / Anon Key) في Supabase (sb_publishable_...، ويُسمّى مفتاح anon في المشاريع الأقدم) وapiKey في إعدادات Firebase يعرّفان مشروعك، وهما مصمّمان ليُشحنا مع التطبيق. وتقول Firebase بوضوح إن مفاتيح API الخاصة بها لا تُستخدم للتحكم في الوصول إلى بياناتك.

إخفاء المفتاح أو تمويهه لا يغيّر شيئاً، فأي شخص يستطيع نسخه والاستعلام من قاعدة بياناتك عبر حاسوبه، وما يحصل عليه يتوقف فقط على سياسات RLS أو قواعدك.

أما المفاتيح السرية فهي المفتاح السري في Supabase (sb_secret_...) ومفتاح الخدمة (Service Role Key) الأقدم service_role، إضافة إلى بيانات اعتماد حساب الخدمة في Firebase التي يستخدمها Admin SDK. وهذه كلها تتخطى RLS والقواعد تماماً، لذلك مكانها الخادم، في Edge Function أو Cloud Function، ولا تُوضع أبداً في المتصفح أو تطبيق الهاتف أو مستودع Git. وصارت Supabase ترفض المفاتيح السرية المرسلة من المتصفح، لكن المفتاح المسرّب يبقى صالحاً من أي أداة أخرى، لذلك يجب تغيير (Rotate) أي مفتاح انكشف ولو مرة.

ما يستطيع من يملك مفتاحك العام فعله حين تكون القواعد معطّلة، مقارنة بقواعد مفعّلة ومختبرة

المفتاح العام هو نفسه في الحالتين، والقواعد هي التي تحدّد ما يفتحه.

ماذا يحدث حين تكون RLS معطّلة أو القواعد مفتوحة

هذه حالات علنية وأبحاث منشورة، وهي تُظهر الخطأ نفسه على المنصتين.

  • تطبيقات Lovable، 2025. فحص باحث 1,645 تطبيقاً من معرض Lovable العام، فوجد 170 منها يمكن قراءة قواعد بيانات Supabase فيها أو تغييرها دون تسجيل دخول، لأن سياسات RLS كانت غائبة أو متساهلة أكثر من اللازم. وسُجّلت الثغرة باسم CVE-2025-48757، وكان موقف Lovable أن كل صاحب تطبيق مسؤول عن بياناته.
  • Moltbook، 2026. وجد باحثو Wiz أن هذه الشبكة الاجتماعية المخصّصة لوكلاء الذكاء الاصطناعي تضع مفتاح Supabase في واجهتها الأمامية كالمعتاد، دون أي سياسات RLS خلفه. فكان المفتاح يمنح صلاحية قراءة وكتابة كاملة على قاعدة بيانات الإنتاج، بما فيها نحو 1.5 مليون رمز API وعشرات آلاف عناوين البريد الإلكتروني والرسائل الخاصة. وأصلح الفريق المشكلة خلال ساعات من إبلاغه.
  • مواقع Firebase، 2024. فحص ثلاثة باحثين أكثر من خمسة ملايين نطاق، فوجدوا 916 موقعاً قواعد Firebase فيها غائبة أو مضبوطة بشكل خاطئ، وقد كشفت معاً نحو 125 مليون سجل مستخدم، فيها أسماء وعناوين بريد إلكتروني وأرقام هواتف وبيانات فوترة وكلمات مرور مخزّنة كنص مكشوف. ولم يُصلح المشكلة بعد التحذير إلا نحو ربع الشركات.
  • التطبيقات المبنية بأسلوب Vibe Coding، 2025. فحصت Escape، وهي شركة أمنية، نحو 5,600 تطبيق عام مبني على منصات الذكاء الاصطناعي، وأبلغت عن أكثر من 2,000 ثغرة عالية الأثر، وأكثر من 400 سرّ مكشوف، و175 حالة كشف لبيانات شخصية، منها سجلات طبية وأرقام حسابات مصرفية.

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

RLS في Supabase: أغلق كل جدول

الخطوة 1: فعّل RLS لكل جدول

الجداول التي تُنشأ بـSQL أو بملفات الترحيل (Migrations) أو بأدوات الذكاء الاصطناعي لا تكون RLS مفعّلة فيها دائماً. شغّل هذا في محرّر SQL لعرض الجداول التي ما زالت مفتوحة في المخطط public:

select tablename
from pg_tables
where schemaname = 'public' and rowsecurity = false;

ثم فعّلها لكل جدول منها:

alter table public.invoices enable row level security;

حين تكون RLS مفعّلة ولا توجد أي سياسات، لا يستطيع المفتاح العام قراءة أي شيء في ذلك الجدول أو الكتابة فيه، وهذه نقطة البداية الآمنة. ثم تضيف سياسة لكل أمر يُسمح للمستخدم بفعله. كما ينبّه Security Advisor في لوحة تحكم Supabase إلى الجداول التي بلا RLS (lint 0013) وإلى السياسات الموجودة على جدول ما زالت RLS معطّلة فيه (lint 0007).

الخطوة 2: سياسات لصفوف كل مستخدم

لنفترض أن كل صف في invoices يحتوي على عمود user_id يحمل معرّف المستخدم المالك له. هذه السياسات الأربع تسمح للمستخدمين المسجّلين بالعمل على صفوفهم فقط. تعيد auth.uid() معرّف المستخدم صاحب الطلب، ووضعها داخل (select ...) يسمح لـPostgres بحسابها مرة واحدة لكل استعلام بدلاً من مرة لكل صف، كما توصي Supabase.

create policy "Users can read their own invoices"
on public.invoices for select
to authenticated
using ( (select auth.uid()) = user_id );

create policy "Users can create their own invoices"
on public.invoices for insert
to authenticated
with check ( (select auth.uid()) = user_id );

create policy "Users can update their own invoices"
on public.invoices for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );

create policy "Users can delete their own invoices"
on public.invoices for delete
to authenticated
using ( (select auth.uid()) = user_id );

create index on public.invoices (user_id);

يتحقق using من الصفوف الموجودة أصلاً، ويتحقق with check من الصف الذي تجري كتابته. وتحتاج سياسة التحديث إلى الاثنين، وإلا استطاع المستخدم تغيير user_id وتسليم الصف لشخص آخر. ويعني to authenticated أن الزوار غير المسجّلين لا يحصلون على شيء، أما الفهرس فيُبقي التحقق سريعاً مع نمو الجدول.

الخطوة 3: سياسات للفرق والشركات

تتشارك تطبيقات الأعمال السجلات عادة داخل فريق أو شركة. احتفظ بجدول team_members يربط المستخدمين بالفرق، ثم اسمح للمستخدمين بالوصول إلى صفوف الفرق التي ينتمون إليها:

create policy "Team members can read team projects"
on public.projects for select
to authenticated
using (
  team_id in (
    select team_id from public.team_members
    where user_id = (select auth.uid())
  )
);

اكتب سياسات مماثلة للإضافة والتحديث والحذف، وحدّد من يحق له إضافة أشخاص إلى الفريق. ويحتاج جدول team_members إلى RLS أيضاً، فإن كانت سياسته تبحث في team_members نفسه، يعرض Postgres خطأ التكرار اللانهائي (Infinite Recursion). والحل المعتاد دالة صغيرة من نوع security definer، في مخطط لا تكشفه الواجهة البرمجية، تعيد فرق المستخدم.

لا تبنِ أي صلاحية على user_metadata في رمز تسجيل الدخول، لأن المستخدمين يستطيعون تغيير بياناتهم الوصفية بأنفسهم. خزّن الأدوار في جدول لا يكتب فيه إلا المسؤولون أو الخادم. كما أن العروض (Views) تتخطى RLS افتراضياً، لذلك أنشئها في Postgres 15 وما بعده with (security_invoker = true) حتى تحترم سياسات الجداول التي تحتها.

الخطوة 4: مساحات التخزين (Buckets)

الملفات في Supabase Storage هي صفوف في storage.objects، فتنطبق عليها القواعد نفسها. اجعل مساحات التخزين خاصة ما لم تكن كل ملفاتها عامة فعلاً، وأعطِ كل مستخدم مجلداً باسم معرّفه:

create policy "Users upload to their own folder"
on storage.objects for insert
to authenticated
with check (
  bucket_id = 'documents'
  and (storage.foldername(name))[1] = (select auth.uid())::text
);

create policy "Users read their own folder"
on storage.objects for select
to authenticated
using (
  bucket_id = 'documents'
  and (storage.foldername(name))[1] = (select auth.uid())::text
);

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

قواعد الأمان في Firebase: Firestore وStorage

اخرج من وضع الاختبار

حين تنشئ قاعدة بيانات Firestore في وضع الاختبار (Test Mode)، تكتب لوحة التحكم قواعد تسمح لأي شخص بالقراءة والكتابة حتى تاريخ يقع بعد نحو 30 يوماً:

allow read, write: if request.time < timestamp.date(2026, 12, 7);

حتى ذلك التاريخ تبقى بياناتك مفتوحة للإنترنت، وفيه يتوقف التطبيق عن العمل، فيلجأ كثيرون إلى «إصلاحه» بتأجيل التاريخ أو بتغيير السطر إلى allow read, write: if true;. وتقول Firebase إن هذا يجب ألا يُستخدم أبداً في الإنتاج. ولا يكفي كذلك التحقق من أن المستخدم سجّل دخوله فقط (if request.auth != null)، لأن أي شخص يستطيع إنشاء حساب في تطبيقك ثم قراءة بيانات الجميع.

التحقق من المالك وصحة البيانات في Firestore

هنا تخزّن كل ملاحظة معرّف مالكها في ownerId. وresource.data هو المستند كما هو الآن، وrequest.resource.data هو المستند كما سيصبح بعد الكتابة.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /notes/{noteId} {
      allow read, delete: if request.auth != null
        && resource.data.ownerId == request.auth.uid;

      allow create: if request.auth != null
        && request.resource.data.ownerId == request.auth.uid
        && request.resource.data.keys().hasOnly(['ownerId', 'title', 'body'])
        && request.resource.data.title is string
        && request.resource.data.title.size() <= 200;

      allow update: if request.auth != null
        && resource.data.ownerId == request.auth.uid
        && request.resource.data.diff(resource.data).affectedKeys()
             .hasOnly(['title', 'body']);
    }
  }
}

تمنع قاعدة الإنشاء المستخدمين من كتابة ملاحظات باسم غيرهم أو إضافة حقول لم تخطط لها، مثل isAdmin. وتسمح لهم قاعدة التحديث بتغيير العنوان والنص دون المالك أبداً. والقواعد ليست مرشّحات (Filters)، لذلك يجب أن يطلب استعلام تطبيقك ملاحظات المستخدم وحده (where('ownerId', '==', uid))، فالاستعلام الذي قد يعيد ملاحظات أشخاص آخرين يُرفض بالكامل.

وللفرق، احتفظ بالأعضاء في مجموعة فرعية (Subcollection) وتحقق منها بـexists(). وكل عملية بحث تُحتسب قراءةَ مستند في فاتورتك.

match /teams/{teamId}/projects/{projectId} {
  allow read, write: if request.auth != null
    && exists(/databases/$(database)/documents/teams/$(teamId)/members/$(request.auth.uid));
}

قواعد Cloud Storage

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/{fileName} {
      allow read: if request.auth != null && request.auth.uid == userId;
      allow write: if request.auth != null && request.auth.uid == userId
        && request.resource.size < 5 * 1024 * 1024
        && request.resource.contentType.matches('image/.*');
    }
  }
}

هكذا تبقى ملفات كل مستخدم خاصة، وتقتصر الملفات المرفوعة على صور أصغر من 5 MB، فلا يستطيع أحد ملء مساحة التخزين على حسابك. ويتخطى Admin SDK على خادمك هذه القواعد، لذلك أبقِ بيانات اعتماده خارج التطبيق. ويضيف Firebase App Check طبقة أخرى بحجب الطلبات التي لا تأتي من تطبيقك الحقيقي، لكنه لا يحلّ محل القواعد.

كيف تختبر قواعدك

اختبر كل سياسة قبل أن تعتمد عليها، مسجّلاً الدخول باسم ثلاثة أشخاص مختلفين، وهم زائر غير مسجّل، ومستخدم عادي، ومستخدم ثانٍ يجب ألا يرى بيانات المستخدم الأول.

اختبار Supabase

ابدأ بما يراه شخص من الخارج. خذ المفتاح العام من موقعك واستعلم عن جدول من الطرفية (Terminal). فإن كانت RLS تعمل، يجب أن تحصل على قائمة فارغة أو خطأ، ولا تحصل أبداً على بياناتك. (ومع مفتاح anon القديم، أرسله أيضاً بصيغة Authorization: Bearer.)

curl "https://YOUR-PROJECT.supabase.co/rest/v1/invoices?select=*" \
  -H "apikey: YOUR_PUBLISHABLE_KEY"

ثم تحقق مما يراه مستخدم مسجّل. فمحرّر SQL يعمل بصلاحيات المسؤول ويرى كل الصفوف، فتبدو السياسة المعطوبة سليمة. لذلك انتقل إلى دور المستخدم داخل معاملة، أو استخدم أداة اختيار الدور في لوحة التحكم:

begin;
set local role authenticated;
set local request.jwt.claims = '{"sub": "ID-OF-TEST-USER-A", "role": "authenticated"}';
select count(*) from public.invoices;  -- only user A's invoices
rollback;

ولشبكة أمان دائمة، اكتب هذه الفحوص كاختبارات pgTAP وشغّلها بـsupabase test db قبل كل إصدار، حتى لا يعيد جدول جديد أو تعديل من أداة ذكاء اصطناعي فتح البيانات دون أن يلاحظ أحد.

اختبار Firebase

تتيح لك Rules Playground في لوحة تحكم Firebase (من Firestore أو Storage ثم Rules) محاكاة قراءة أو كتابة بصفة مستخدم مسجّل أو زائر قبل النشر. وللاختبارات القابلة للتكرار، شغّل محاكي Firebase المحلي (Local Emulator Suite) (firebase emulators:start) واكتب اختبارات وحدة باستخدام @firebase/rules-unit-testing:

const alice = testEnv.authenticatedContext('alice').firestore();
const bob = testEnv.authenticatedContext('bob').firestore();
const visitor = testEnv.unauthenticatedContext().firestore();

await assertSucceeds(getDoc(doc(alice, 'notes/alice-note')));
await assertFails(getDoc(doc(bob, 'notes/alice-note')));
await assertFails(getDoc(doc(visitor, 'notes/alice-note')));

شغّلها ضمن خط البناء (Build Pipeline)، حتى يفشل أي تعديل على القواعد يفتح البيانات قبل أن يصل إلى المستخدمين.

قائمة التحقق قبل الإطلاق

ثمانية فحوص لـSupabase وFirebase قبل إطلاق التطبيق

أعد إجراءها بعد كل تغيير كبير، خصوصاً إن أجرته أداة ذكاء اصطناعي.

  • RLS مفعّلة لكل جدول في المخططات المكشوفة، ولا يُظهر Security Advisor أي أخطاء.
  • لكل قاعدة بيانات ومساحة تخزين في Firebase قواعد حقيقية، بلا if true وبلا تاريخ وضع الاختبار.
  • كل سياسة أو قاعدة تتحقق من الملكية أو من العضوية في الفريق، ولا تكتفي بأن المستخدم مسجّل الدخول.
  • التحديثات لا تستطيع تغيير المالك، وعمليات الكتابة لا تستطيع إضافة حقول مثل الأدوار أو الأسعار.
  • لا يوجد أي مفتاح سري أو مفتاح service_role أو بيانات اعتماد Admin SDK في الواجهة الأمامية أو المستودع، وكل ما وُجد منها سابقاً تم تغييره.
  • مساحات التخزين خاصة ما لم تكن كل ملفاتها معدّة لتكون عامة، مع مجلدات لكل مستخدم وحدود للحجم.
  • العروض تستخدم security_invoker، والأدوار محفوظة في جداول لا يستطيع المستخدمون تعديلها.
  • اختبرت بصفة زائر والمستخدم A والمستخدم B، والاختبارات تعمل قبل كل إصدار.

وإن أردت قراءة أولى سريعة لتطبيقك ككل، فإن فحص الجاهزية للإطلاق المجاني لدينا يطرح عشرة أسئلة في دقيقتين. ولمزيد عن البناء الآمن بأدوات الذكاء الاصطناعي، اطّلع على دليل البرمجة بأسلوب Vibe Coding.

لأصحاب الأعمال: ما تسأل عنه مطوّرك

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

الأسئلة الشائعة

ما هو أمان مستوى الصف في Supabase؟

أمان مستوى الصف (Row Level Security) ميزة في Postgres تستخدمها Supabase لتقرّر، صفاً بصف، من يستطيع قراءة البيانات أو إضافتها أو تغييرها أو حذفها. تفعّلها لجدول وتكتب سياسات، مثل «لا يرى المستخدمون إلا الصفوف التي يطابق فيها user_id معرّفهم». وكل طلب يمرّ عبر واجهة Supabase البرمجية يُفحص وفق هذه السياسات.

هل كشف مفتاح anon في Supabase أو مفتاح API في Firebase آمن؟

نعم، ما دامت RLS أو قواعد الأمان لديك صحيحة. فهذه المفاتيح تعرّف مشروعك فقط ومصمّمة لتكون داخل التطبيق. أما المفاتيح التي يجب أن تبقى سرية فهي المفتاح السري أو مفتاح service_role في Supabase وبيانات اعتماد حساب الخدمة في Firebase، لأنها تتخطى كل القواعد.

ماذا يحدث إن فعّلت RLS دون أي سياسات؟

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

هل يتخطى مفتاح service_role في Supabase أمان RLS؟

نعم. فمفتاح service_role والمفاتيح السرية الأحدث تتخطى RLS بالكامل، لذلك لا تستخدمها إلا في كود الخادم مثل Edge Functions أو الخادم الخلفي لديك. وإن ظهر أحدها يوماً في واجهتك الأمامية أو في مستودع عام، فغيّره.

كم يدوم وضع الاختبار في Firebase؟

تسمح قواعد وضع الاختبار لأي شخص بالقراءة والكتابة حتى تاريخ يُحدَّد بعد نحو 30 يوماً من إنشاء قاعدة البيانات، وبعده تُرفض كل الطلبات. استبدلها بقواعد سليمة قبل إضافة بيانات حقيقية، بدلاً من تأجيل التاريخ.

اطلب عيناً ثانية تراجع تطبيقك

إن بُني تطبيقك بسرعة وصار يحمل بيانات عملاء حقيقية، فمن المفيد أن يفحصه شخص من خارج فريق البناء. فخدمة تقييم الكود المكتوب بالذكاء الاصطناعيلدينا، التي تبدأ من $1,000، تراجع أمان التطبيق وجودة الكود والجاهزية للإنتاج، وتعطيك تقريراً بما يجب إصلاحه أولاً. وللحديث عن تطبيقك، احجز مكالمة مجانية مدتها 30 دقيقة.

RLS في Supabase وقواعد Firebase: أغلق قاعدة بياناتك
Hassan Tabbal 7 نوفمبر 2026
شارك هذا المنشور
راسلنا على واتسابوكيل ذكي يردّ على مدار الساعة
احجز مكالمة مجانية