// docs / baas security / supabase rls scanner
Сканер Supabase RLS: знайдіть таблиці з відсутньою або зламаною безпекою на рівні рядків
Безпека на рівні рядків (RLS) — це єдине, що стоїть між даними ваших клієнтів та інтернетом, коли ви випускаєте застосунок на базі Supabase. ШІ-інструменти кодування генерують код у формі RLS, який компілюється, випускається та тихо зливає дані — таблиці, створені без увімкненого RLS, політики, які читають, але ніколи не обмежують, предикати, що порівнюють стовпець із самим собою. Ця стаття показує, що сканер Supabase RLS може довести ззовні, чотири форми зламаного RLS, які з'являються у vibe-кодованих застосунках, і як просканувати ваше власне розгортання менш ніж за хвилину.
Що може довести зовнішнє сканування RLS
Пасивне сканування RLS виконується проти кінцевої точки PostgREST, яку Supabase надає за адресою https://[project].supabase.co/rest/v1/. Воно використовує лише публікований ключ anon — той самий ключ, який використовує ваш браузер — і зондує метадані переліку таблиць, анонімні читання та анонімні записи. Воно ніколи не автентифікується як користувач і ніколи не торкається привілеїв сервісної ролі. Усе, що воно може робити, може робити неавторизований зловмисник в інтернеті.
Ззовні бази даних сканер може підтвердити наступне з високою впевненістю:
- RLS вимкнено на таблиці. PostgREST повертає рядки для анонімного
SELECT, коли RLS вимкнено або коли політика це дозволяє. Будь-який випадок — це знахідка. - Анонімна роль може перелічувати таблиці.
GET /rest/v1/із ключем anon повертає схему OpenAPI для кожної таблиці, до якої рольanonмає будь-які привілеї. ШІ-згенеровані застосунки часто надаютьUSAGEдля схеми таSELECTдля кожної таблиці, що оголює повну карту схеми навіть тоді, коли RLS відхиляє фактичні читання. - Анонімна роль може вставляти. Зондуючий
POSTіз припущенням про форму стовпців буде успішним, якщо в RLS немає політикиINSERT, яка це відхиляє — навіть якщоSELECTзаблоковано. - Ключ сервісної ролі знаходиться у браузерному бандлі. Суміжно з RLS: якщо сканер знаходить
SUPABASE_SERVICE_ROLE_KEYабо будь-який JWT ізrole: service_roleу JavaScript-бандлі, RLS стає беззмістовним — власник цього ключа обходить кожну політику.
Що зовнішнє сканування не може довести
Будьте чесними щодо меж сканера. Зовнішнє сканування RLS не може прочитати вашу таблицю pg_policies, ваші файли міграцій або точний предикат будь-якої політики. Воно робить висновки з поведінки чорної скриньки, що означає, що іноді воно повідомить про знахідку, яка виявиться навмисно публічними даними (таблиця маркетингової розсилки, публічний каталог продуктів). Звіт FixVibe позначає їх як середній рівень довіри, коли сканер не може розрізнити наміри — перегляньте назву таблиці та ухваліть рішення.
Чотири форми зламаного RLS, які створюють ШІ-інструменти
Коли ви спрямовуєте Cursor, Claude Code, Lovable або Bolt на Supabase, у тисячах застосунків виникають одні й ті ж чотири шаблони зламаного RLS. Кожен проходить перевірку типів, компілюється та випускається:
Форма 1: RLS ніколи не вмикалося
Найпоширеніший режим відмови. Міграція створює таблицю, але розробник (або ШІ-інструмент) забуває ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST охоче обслуговує всю таблицю будь-кому, хто має ключ anon. Виправлення: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;. FORCE не є необов'язковим — без нього власник таблиці (і будь-яка роль із володінням таблицею) обходить RLS.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;Форма 2: RLS увімкнено, немає політик
Тонша відмова. RLS увімкнено, але політики не написано. За замовчуванням у PostgreSQL — відхиляти, тож автентифіковані користувачі нічого не бачать — і розробник додає USING (true), щоб застосунок працював, що дозволяє всім читати все. Виправлення: напишіть політику з обмеженням через auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); та відповідну політику INSERT/UPDATE/DELETE.
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);Форма 3: Політика порівнює стовпець із самим собою
A copy-paste artefact. The developer writes <code>USING (user_id = user_id)</code> — which is always true — instead of <code>USING (auth.uid() = user_id)</code>. Type-checks pass; the policy permits every row. <strong>Fix:</strong> always compare a column to a function call (<code>auth.uid()</code>, <code>auth.jwt()->>'org_id'</code>, etc.), never to itself or to a constant.
Форма 4: Політика на SELECT, але не на INSERT/UPDATE
Розробник блокує читання, але забуває записи. Політики RLS працюють покомандно. FOR SELECT захищає лише читання; анонімний клієнт усе ще може зробити INSERT, якщо жодна політика цього не відхиляє. Виправлення: створіть політику для кожної команди або використовуйте FOR ALL із явними реченнями USING та WITH CHECK.
Як працює сканер FixVibe Supabase RLS
Перевірка baas.supabase-rls виконується у три етапи, кожен із явними рівнями довіри:
- Етап 1 — зняття відбитків. Сканер обходить розгорнутий застосунок, розбирає його JavaScript-бандл і витягає URL проєкту Supabase та ключ anon із конфігурації часу виконання. Жодних здогадок щодо DNS, жодного перебору — він читає те, що читає браузер.
- Етап 2 — виявлення схеми. Один
GET /rest/v1/із ключем anon повертає схему OpenAPI для кожної таблиці, яку може бачити роль anon. Сканер записує імена таблиць, але не читає дані рядків на цьому етапі. - Етап 3 — зонди читання та запису. Для кожної виявленої таблиці сканер виконує один анонімний
SELECTізlimit=1. Якщо повертаються рядки, RLS дозволяє. Сканер на цьому зупиняється — він не перелічує рядки, не розбиває на сторінки, не змінює дані. Зонди INSERT захищені підтвердженим володінням доменом та явною згодою; вони ніколи не запускаються проти неперевірених цілей.
Кожна знахідка постачається з точним URL запиту, статусом відповіді, формою відповіді (лише заголовки) та ім'ям таблиці. Підказка виправлення від ШІ внизу знахідки — це копійований блок SQL, який ви запускаєте в редакторі SQL Supabase.
Що робити, коли сканер щось знаходить
Кожна знахідка RLS — це аварійна ситуація часу виконання. Публічні кінцеві точки PostgREST скануються зловмисниками за хвилини. Послідовність виправлення механічна:
- Перевірте кожну таблицю. Запустіть
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';у редакторі SQL Supabase. Будь-який рядок ізrowsecurity = false— це проблема. - Увімкніть RLS на кожній публічній таблиці. За замовчуванням ставте
ENABLE ROW LEVEL SECURITYтаFORCE ROW LEVEL SECURITYна кожну створену таблицю — зробіть це шаблоном міграції. - Створюйте політики покомандно. Не використовуйте
FOR ALL USING (true). Пишіть явні політики для SELECT, INSERT, UPDATE, DELETE — кожна обмежена черезauth.uid()або стовпець org-id зauth.jwt(). - Перевірте за допомогою другого облікового запису. Зареєструйтеся як інший користувач, спробуйте прочитати записи іншого користувача через REST API напряму. Якщо відповідь —
200, політика зламана. - Повторне сканування. Після застосування виправлення повторно запустіть сканування FixVibe проти того ж URL. Знахідка
baas.supabase-rlsмає зникнути.
-- Audit every table for missing RLS. Run in the Supabase SQL editor.
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY rowsecurity, tablename;Як це порівнюється з іншими сканерами
Більшість загальних DAST-інструментів (Burp Suite, OWASP ZAP, Nessus) не знають, що таке PostgREST. Вони обходитимуть ваш застосунок, ігноруватимуть шлях /rest/v1/ та звітуватимуть про HTML-сторінки, які вони розуміють. Snyk і Semgrep — це інструменти статичного аналізу — вони знаходять файли міграцій у вашому репозиторії з відсутніми викликами RLS, але не можуть довести, що розгорнута база даних неправильно налаштована. FixVibe займає цю прогалину: пасивний, BaaS-обізнаний, зосереджений на тому, що неавторизований зловмисник може довести з публічного URL.
Поширені запитання
Чи буде сканер читати або змінювати мої дані?
Ні. Пасивні сканування виконують щонайбільше один SELECT ... limit=1 на виявлену таблицю, щоб підтвердити, чи дозволяє RLS анонімні читання. Сканер записує форму відповіді, а не вміст рядків. Зонди INSERT, UPDATE та DELETE захищені підтвердженим володінням доменом і ніколи не запускаються проти неперевірених цілей.
Чи працює це, якщо мій проєкт Supabase призупинено або на користувацькому домені?
Призупинені проєкти повертають 503 на кожен запит — сканер звітує про проєкт як недосяжний. Користувацькі домени працюють, доки розгорнутий застосунок усе ще завантажує клієнтський SDK Supabase у браузері; сканер витягає URL проєкту з бандла в будь-якому випадку.
Що, якщо мій ключ anon ротується або мій публікований ключ змінюється?
Запустіть сканування повторно. Сканер повторно витягає ключ із поточного бандла під час кожного запуску. Ротація знеціняє лише попередній звіт, а не стан політик бази даних.
Чи перевіряє сканер нову модель публікованих ключів Supabase (<code>sb_publishable_*</code>)?
Так. Детектор розпізнає як застарілі anon JWT, так і новіші ключі sb_publishable_* і трактує їх однаково — обидва призначені бути публічними та обидва залишають RLS єдиною лінією оборони.
Наступні кроки
Запустіть безкоштовне сканування FixVibe проти вашого продакшен-URL — перевірка baas.supabase-rls увімкнена на кожному тарифі, включно з безкоштовним. Для глибшого розгляду того, що ще може витекти з проєкту Supabase, дивіться Сервісний ключ Supabase, розкритий у JavaScript та Чек-лист безпеки сховищ Supabase. Для оглядового погляду на всіх постачальників BaaS читайте Сканер неправильних налаштувань BaaS.
