TL;DR: ל-RLS ב-Supabase יש שני מצבי כשל הפוכים. או שהוא חוסם הכל (שאילתות מחזירות מערך ריק או "permission denied") - בדרך כלל כי הפעלתם RLS על טבלה בלי להגדיר אף פוליסי. או שהוא דולף מידע בין משתמשים (כל אחד רואה את הכל) - בדרך כלל כי אתם משתמשים במפתח
service_roleבפרונטאנד, או שהפוליסי לא באמת בודק את זהות המשתמש. התיקון כמעט תמיד מסתכם בשלושה דברים: להגדיר פוליסי מפורש לכל פעולה, לוודא שההשוואה היאauth.uid() = user_id, ולהוסיףWITH CHECKלפעולות כתיבה. תמיד בודקים עם שני משתמשים נפרדים לפני שמעלים לפרודקשן.
מה רואים - התסמין
RLS "לא עובד" מתבטא בשתי צורות מנוגדות, וחשוב לזהות לאיזו מהן נקלעתם לפני שנוגעים ב-SQL.
מצב א' - הכל חסום. האפליקציה שעבדה מצוין בפיתוח פתאום מחזירה מסכים ריקים. שאילתות select מחזירות [] בלי שגיאה, פעולות insert נכשלות עם הודעה כמו new row violates row-level security policy, או שאתם רואים permission denied for table. המשתמש מחובר, הטוקן תקין, אבל שום דבר לא זז. זה המצב הבטוח יחסית - שום מידע לא נחשף, פשוט הכל נעול.
מצב ב' - דליפת מידע. ההיפך הגמור, וזה המצב המסוכן. משתמש א' מתחבר ורואה גם את הרשומות של משתמש ב'. או גרוע מכך - כל מבקר אנונימי שולף את כל טבלת המשתמשים. אין שגיאה, אין אזהרה, הכל "עובד" - ובדיוק בגלל זה זה מסוכן. גיליתם את זה כשמישהו התלונן, או כשבדקתם עם משתמש שני במקרה.
הבחנה חשובה: אם אתם מקבלים מערך ריק זה כמעט תמיד בעיית פוליסי (מצב א'). אם אתם מקבלים יותר מדי נתונים זו כמעט תמיד בעיית מפתח או פוליסי חסר תנאי (מצב ב').
למה זה קורה - שורשי הבעיה
הרוב המוחלט של תקלות RLS נובע מארבעה גורמים. הנה הם לפי שכיחות, מהנפוץ לפחות.
1. RLS הופעל בלי אף פוליסי (הגורם מספר אחת למצב א'). זו נקודת המפתח שרבים מפספסים: ברגע שאתם מפעילים ENABLE ROW LEVEL SECURITY על טבלה, ברירת המחדל היא לחסום הכל. RLS עובד בגישת "deny by default" - בלי פוליסי מפורש שמתיר גישה, אף שורה לא מוחזרת. הכלי הזה עשה בדיוק מה שנאמר לו: הפעיל אבטחה, אבל לא כתב את הכללים שמתירים למשתמש הלגיטימי לגשת לנתונים שלו.
2. מפתח service_role בפרונטאנד (הגורם מספר אחת למצב ב'). לכל פרויקט Supabase יש שני מפתחות: anon (ציבורי, מכבד RLS) ו-service_role (מפתח על, עוקף RLS לחלוטין). אם משום מה המפתח שנמצא ב-frontend הוא ה-service_role, כל בקשה מהדפדפן עוקפת את כל הפוליסיז שכתבתם. זו טעות נפוצה מאוד בפרויקטים שנבנו במהירות עם כלי Vibe Coding, שבהם לפעמים סוכן ה-AI מדביק את המפתח הראשון שהוא מוצא בלי להבחין בין השניים.
3. הפוליסי בודק את הדבר הלא נכון. הפוליסי קיים, אבל ההשוואה בו שגויה. הטעות הקלאסית היא להשוות עמודה לא נכונה, להשתמש ב-auth.uid() מול עמודה שהיא בכלל לא מזהה המשתמש, או להשתמש ב-USING (true) שמתיר לכולם הכל. לפעמים סוכן AI "ממציא" תחביר או שם עמודה שלא קיים - תופעה שנקראת הזיית קוד - והפוליסי פשוט לא עושה מה שנראה שהוא עושה.
4. חסר WITH CHECK בפעולות כתיבה. פוליסי RLS מבחין בין USING (מי יכול לקרוא/למחוק שורות קיימות) לבין WITH CHECK (אילו ערכים מותר לכתוב בשורה חדשה או מעודכנת). אם הגדרתם רק USING על INSERT או UPDATE, משתמש יכול לכתוב שורה עם user_id של מישהו אחר - דליפה שקטה שקשה מאוד לגלות בלי בדיקה ממוקדת.
להסבר מעמיק על המנגנון עצמו ראו את מונחון אבטחת RLS ו-פוליסיז RLS ב-Supabase.
איך מתקנים - צעד אחר צעד
התיקון תלוי בתסמין. עברו על הצעדים לפי הסדר - הם בנויים כך שקודם מאבחנים, ואז מתקנים.
שלב 1: אבחון - מה מצב ה-RLS בפועל
לפני כל תיקון, בדקו מה באמת קורה. הריצו ב-SQL Editor של Supabase:
-- האם RLS מופעל על הטבלה?
SELECT relname, relrowsecurity
FROM pg_class
WHERE relname = 'your_table';
-- אילו פוליסיז קיימים על הטבלה?
SELECT policyname, cmd, qual, with_check
FROM pg_policies
WHERE tablename = 'your_table';
אם relrowsecurity הוא true אבל השאילתה השנייה לא מחזירה כלום - מצאתם את מצב א': RLS מופעל בלי פוליסיז. אם relrowsecurity הוא false - RLS כבוי לגמרי, וזו כנראה הסיבה למצב ב'.
שלב 2: ודאו שאתם משתמשים במפתח הנכון בפרונטאנד
זה הצעד הכי חשוב למי שחווה דליפת מידע. פתחו את קובץ הסביבה של הפרויקט (בדרך כלל .env) וודאו שהמפתח שמגיע ל-client הוא ה-anon key ולא ה-service_role key.
# נכון - זה מה שהולך לפרונטאנד
VITE_SUPABASE_ANON_KEY=eyJhbGci... # role: anon
# מסוכן - אסור שזה יגיע לדפדפן לעולם
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci... # role: service_role
איך מבדילים? פענחו את ה-JWT (למשל ב-jwt.io, אבל רק את החלק הציבורי) והסתכלו בשדה role. אם כתוב service_role - זו הבעיה. החליפו מייד למפתח ה-anon. אם ה-service_role כבר הודלף לקוד ציבורי או ל-git, צריך לסובב (rotate) אותו בהגדרות הפרויקט, כי הוא נחשב חשוף.
שלב 3: כתבו פוליסי מפורש לכל פעולה
זה הלב של התיקון למצב א'. אחרי שהפעלתם RLS, צריך פוליסי לכל סוג פעולה. הנה התבנית הבסיסית והנכונה עבור טבלה שבה לכל שורה יש עמודת user_id:
-- ודאו ש-RLS מופעל
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
-- קריאה: כל משתמש רואה רק את השורות שלו
CREATE POLICY "users select own rows"
ON profiles FOR SELECT
USING (auth.uid() = user_id);
-- הוספה: משתמש יכול להוסיף שורה רק עם ה-user_id שלו
CREATE POLICY "users insert own rows"
ON profiles FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- עדכון: גם USING (אילו שורות מותר לגעת) וגם WITH CHECK (מה מותר לכתוב)
CREATE POLICY "users update own rows"
ON profiles FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- מחיקה: רק את השורות של המשתמש עצמו
CREATE POLICY "users delete own rows"
ON profiles FOR DELETE
USING (auth.uid() = user_id);
שימו לב לשלוש נקודות קריטיות:
auth.uid() = user_idולא ההיפך או משהו אחר.auth.uid()מחזיר את ה-UUID של המשתמש המחובר מתוך ה-JWT.user_idהיא העמודה בטבלה שלכם. שני הצדדים חייבים להיות מסוגuuid. אם העמודה שלכם היאtext, ייתכן שתצטרכו המרה מפורשת.INSERTדורשWITH CHECK, לאUSING. ב-INSERT אין שורות קיימות לבדוק, יש רק ערכים נכנסים - ולכןWITH CHECKהוא הרלוונטי. פוליסי INSERT עםUSINGבלבד לא יגן עליכם.UPDATEדורש את שניהם.USINGקובע אילו שורות משתמש רשאי לגעת בהן,WITH CHECKקובע לאילו ערכים מותר לו לשנות אותן. בליWITH CHECK, משתמש יכול לעדכן שורה שלו ולשנות בה את ה-user_idלזהות של מישהו אחר.
שלב 4: תקנו פוליסי שבודק את הדבר הלא נכון
אם יש לכם פוליסי אבל הוא מחזיר יותר מדי (או מעט מדי), חפשו את הדפוסים הבעייתיים האלה ותקנו:
-- בעייתי: מתיר לכולם הכל
CREATE POLICY "bad" ON profiles FOR SELECT USING (true);
-- בעייתי: משווה לעמודה הלא נכונה (email במקום המזהה)
CREATE POLICY "bad" ON profiles FOR SELECT USING (auth.uid()::text = email);
-- מתוקן:
DROP POLICY "bad" ON profiles;
CREATE POLICY "users select own rows"
ON profiles FOR SELECT
USING (auth.uid() = user_id);
לטבלאות שקשורות זו לזו (למשל posts ששייכים ל-user_id), אותו עיקרון: הפוליסי צריך להוביל בחזרה ל-auth.uid() דרך העמודה הנכונה.
שלב 5: אתחלו את הקאש ובדקו מחדש
אחרי שינוי פוליסיז, לפעמים חיבורים פתוחים עדיין מחזיקים בהחלטת הרשאה ישנה. רעננו את הדף, התנתקו והתחברו מחדש כדי לקבל טוקן טרי, וודאו שהשינוי תפס.
איך מונעים את זה מראש
הדרך הבטוחה למנוע חזרה של התקלה היא לבדוק את שני מצבי הכשל באופן יזום, לא לחכות שמשתמש יגלה אותם.
בדקו תמיד עם שני משתמשים. זו הבדיקה החשובה ביותר, והיא מגלה כמעט כל בעיית RLS. צרו שני חשבונות נפרדים, התחברו כמשתמש א', צרו רשומה. התחברו כמשתמש ב'. משתמש ב' צריך לראות אך ורק את הרשומות שלו, ולא של משתמש א'. אם הוא רואה את של א' - יש דליפה. אם משתמש א' לא רואה כלום מהנתונים שלו - הפוליסי חוסם יותר מדי.
החזיקו הפרדה קשיחה בין המפתחות. ה-service_role אמור לחיות רק בצד שרת (Edge Functions, שרת backend), לעולם לא בקוד שנשלח לדפדפן. הוסיפו את service_role ל-.gitignore ולבדיקות ה-CI כדי לוודא שהוא לא דולף לקוד לקוח.
הפכו את הבדיקה עם שני משתמשים לחלק מרשימת ה-launch. לפני כל העלאה לפרודקשן, הריצו את בדיקת שני המשתמשים על כל טבלה שמכילה מידע פרטי. אפשר להיעזר ב-כלי בדיקת מוכנות לפרודקשן שמכסה בדיוק את הנקודות האלה.
כתבו INSERT/UPDATE עם WITH CHECK כברירת מחדל. אמצו כלל פשוט: כל פוליסי כתיבה מקבל WITH CHECK, נקודה. עדיף להוסיף אותו גם כשהוא נראה מיותר מאשר לגלות דליפה בדיעבד.
למדריך המקיף על אבטחת RLS בפרויקטים שנבנו ב-Supabase ו-Base44 ראו את המדריך המלא לאבטחת RLS.
מתי זה עמוק יותר מתיקון מהיר
רוב תקלות ה-RLS נפתרות בצעדים למעלה, אבל יש מצבים שבהם התסמין הוא רק קצה הקרחון.
אם גיליתם דליפת מידע שכבר הגיעה למשתמשים אמיתיים, אם ה-service_role הודלף לקוד ציבורי, או אם הפרויקט מכיל עשרות טבלאות בלי אף פוליסי מסודר - אתם לא מול תקלה נקודתית אלא מול חוב טכני מצטבר באבטחה. במצב כזה, תיקון טבלה אחת בכל פעם משאיר פרצות פתוחות במקומות שלא הסתכלתם בהם.
זה בדיוק סוג המצב שבו VibeScale נכנסת. אנחנו לא בונים לכם אפליקציה חדשה - אנחנו לוקחים פרויקט קיים שנבנה מהר (לרוב עם כלי Vibe Coding או סוכני AI) ומביאים אותו למצב שבו הוא באמת בטוח לפרודקשן: מיפוי מלא של כל הטבלאות, פוליסיז עקביים, הפרדת מפתחות, ובדיקות רב-משתמשיות שיטתיות. אם סוכן AI שבר משהו בפרודקשן או שאתם לא בטוחים כמה מהמידע שלכם באמת מוגן, זה הזמן לבדיקה חיצונית.
מוזמנים להתחיל מ-מסגרת האודיט שלנו או לפנות ישירות דרך שירות החילוץ. לשיחה מהירה, כתבו לנו ב-WhatsApp ונעזור לכם לאבחן אם מדובר בתיקון של חצי שעה או במשהו שדורש מבט רחב יותר.
