Перейти до вмісту

Основи безпеки

Supabase "RLS disabled in public": чого не бачить попередження

Supabase позначає "RLS disabled in public" як помилку. Про політику читання, яка лишає вашу таблицю так само відкритою, він не каже нічого.

Vlad Tkachenko9 хв читання
Панель дашборду зі списком кількох таблиць, зі знаком попередження біля однієї з них і нічим біля решти.

Коротко

  • "RLS disabled in public" означає рівно одне: у таблиці з вашої схеми public вимкнено Row Level Security, тож прочитати її може будь-хто, хто має адресу вашого проєкту та публічний ключ.
  • Про таблицю, де ви увімкнули перемикач, а потім написали політику, яка дозволяє читати всім, це повідомлення мовчить. Правило для завжди істинних політик існує, і воно свідомо пропускає політики читання.
  • Полагодьте позначені таблиці, а решту перевірте ззовні, бо Advisor читає ваші налаштування й ніколи не питає у вашої бази, що насправді отримує незнайомець.

Ви відкрили Security Advisor у своєму дашборді Supabase, або хтось тицьнув вам його вивід, і ось воно червоним: RLS disabled in public. Нижче по рядку на таблицю, словами самого Supabase:

Table public.profiles is public, but RLS has not been enabled.

Ось те, що посібник за посібником переказує неправильно: розібрати цей список не означає, що ніхто не може прочитати ваші дані. Для політики, яка впускає всіх, в Advisor є окреме правило, і воно пропускає рівно ту форму, яку пише конструктор зі ШІ, коли розблоковує ваш застосунок. Тож список, який ви щойно розібрали, і список таблиць, які може прочитати незнайомець, це два різні списки, і одного з них ніде у вашому дашборді не надруковано.

Що означає "RLS disabled in public"?

У таблиці з вашої схеми public вимкнено Row Level Security, і наслідок цього такий: будь-хто з адресою вашого проєкту може прочитати кожен її рядок.

Обидві половини варто розгорнути. Схема public це місце, куди таблиця потрапляє, коли ніхто не сказав інакше, і саме цю частину вашої бази Supabase публікує в інтернеті: кожен проєкт відповідає на запити за власною адресою, а ключ, потрібний, щоб з ним говорити, лежить у коді, який ваш сайт віддає кожному відвідувачу. Row Level Security це перемикач, який вирішує, чи звірятимуть ваші правила перед тим, як віддати рядки. Коли він вимкнений, звіряти нема з чим, тому відповідь завжди ствердна.

Саме через це поєднання воно повідомляється як помилка, а не як попередження. Advisor розкладає свої знахідки по рівнях, і це найвищий з них.

Уявіть Advisor як інспектора з планшетом. Він уважно читає ваші папери і робить це добре. Він ніколи не смикає ручку дверей. Ніщо на тій панелі не є результатом запиту, який хтось надіслав до вашої бази.

Чи зламає застосунок увімкнення RLS?

Так, одразу, і саме в цьому робота налаштування.

Виправлення, яке дає вам Supabase, це один рядок, і ви виконуєте його в SQL Editor:

alter table public.profiles enable row level security;

Власна документація Supabase прямо каже, що буде далі: з публічним ключем дані стають недоступними через API, доки не визначено політик. Тож ваші списки повертаються порожніми, екрани лишаються білими, а помилку в Advisor заміняє тихіший запис про те, що в таблиці RLS увімкнено, але політик немає.

Це зачинені двері, у списку біля яких поки нікого немає. Далі більшість людей додає політику, яка дозволяє читати всім, бо саме після неї екрани знову щось показують.

Збій, якого попередження не шукає

Таблиця з увімкненим Row Level Security і політикою читання, умова якої USING (true), віддає рівно ті самі рядки рівно тому самому незнайомцю. Advisor її не позначає.

Це не недогляд. Правило для завжди істинних політик у Supabase таки є, і політики читання воно лишає осторонь свідомо. Власний опис правила каже про це так:

SELECT policies with `USING (true)` are intentionally excluded as this
pattern is often used deliberately for public read access.

У загальному випадку правило має рацію. Каталог товарів, список опублікованих статей, карта закладів: усе це створено, щоб його читав будь-хто, і позначати таке означало б привчити кожного розробника на платформі ігнорувати панель. Чого жоден лінтер знати не може, так це чи містить таблиця перед ним заклади, чи клієнтів.

А політика, яка дозволяє читати всім, це найшвидший спосіб знову підняти зламаний застосунок, і тому конструктор зі ШІ хапається саме за неї. Попросіть Cursor чи Lovable полагодити порожні екрани, і FOR SELECT USING (true) буде частою відповіддю. Ваш застосунок завантажується, помилка зникає, панель замовкає, а таблиця читається так само, як і до початку.

Що Advisor може побачитиЯк він це повідомляєЩо отримає незнайомець з вашим публічним ключем
RLS вимкненоПомилкаКожен рядок
RLS увімкнено, жодної політикиІнфоНічого
RLS увімкнено, FOR SELECT USING (true)НічогоКожен рядок
RLS увімкнено, FOR ALL USING (true)ПопередженняКожен рядок, і може їх змінити
RLS увімкнено, USING (auth.uid() = user_id)НічогоЛише свої

Два рядки, де не повідомляється нічого, це пара, біля якої варто затриматися. Один з них це таблиця, до якої поза вашим застосунком ніхто не дотягнеться. Другий це таблиця, яку може прочитати будь-хто. Ваш дашборд однаково мовчить про обидві.

Ті самі чотири рядки, віддані тому самому незнайомцю. Різниця між смугами лише в тому, про яку з них ваш дашборд взагалі щось каже.

Як ця політика там опинилася і чим замінити її по таблиці за раз, розібрано в Row Level Security увімкнено, а таблиця досі публічна. Якщо та сама політика має ще й дозволяти вашому застосунку записувати, наступна помилка, яку ви зустрінете, це new row violates row-level security policy.

Чому таблиця, зроблена міграцією, вас ніколи не попередила

Бо Table Editor вмикає Row Level Security за вас, а SQL цього не робить.

Supabase документує цей поділ без натяків: таблиці, створені через Table Editor у дашборді, типово мають RLS увімкненим, а таблицям, створеним чистим SQL, його треба вмикати явно. Таблиця, яку ви наклацали, стартує захищеною. Таблиця, що приїхала файлом міграції, командою supabase db push, фрагментом у SQL Editor чи інструкцією, яку виконав за вас конструктор зі ШІ, стартує відкритою.

Саме другим шляхом конструктор зі ШІ й створює таблиці. Він пише SQL і виконує його за вас, тож галочки ви ніколи не бачили і ніколи не бачили, що її не поставлено.

Звичка, яка це закриває, полягає в тому, щоб класти рядок у міграцію поруч із тим, що він захищає:

create table public.profiles (
  id uuid primary key references auth.users,
  full_name text
);

alter table public.profiles enable row level security;

Як перевірити таблиці, які Advisor пропустив

Поставте своїй базі те саме запитання, що й незнайомець: надішліть запит ззовні, з публічним ключем, який їде всередині вашого застосунку, і подивіться, що повернеться.

Саме навколо цієї різниці крутиться вся стаття. Advisor читає вашу конфігурацію. Запит читає ваші рядки. Це два різні запитання, і таблиця може пройти перше й не пройти друге, що дозвільна політика читання й робить.

Advisor зупиняється на ваших налаштуваннях. Запит ззовні йде далі до рядків, і саме тому ці двоє можуть казати про ту саму таблицю різне.

Ми зробили цей запит у великому масштабі. Між 12 і 14 серпня 2026 року ми прогнали дев'ять зовнішніх перевірок по 30 998 живих застосунках, опублікованих з Lovable, Base44, Replit, v0 і Bolt. З 3 680 застосунків на Supabase, де перевірку вдалося довести до кінця, 2 096 мали щонайменше одну таблицю, яка відповіла на анонімний запит рядками. Це 57 %, і це частка від застосунків, від яких ми отримали чітку відповідь, а не від усього, що ми просканували. Серед застосунків, зроблених у Bolt, вийшло 27 з 35, і ця вибірка достатньо мала, щоб читати її як напрямок, а не як показник. Повний набір даних опубліковано, а від чого ці 57 % є часткою проходить весь підрахунок.

Той самий запит до однієї таблиці ви можете зробити самі, браузером і власним публічним ключем. Якщо не хочете йти таблиця за таблицею, наше безкоштовне сканування питає ваш живий застосунок ззовні й каже, які таблиці відповіли. Воно триває близько 20 секунд і не потребує акаунта: сканувати застосунок.

Чи потрібна резервна копія перед зміною політик RLS?

Для будь-якої таблиці, у яку ваш застосунок пише, так. Для таблиці лише на читання, яку ви просто звужуєте, ризик у тому, що застосунок побіліє, а не в тому, що зникнуть дані.

Тут варто розділити дві різні речі, бо лише одна з них стосується ремонту.

Що дозвільна політика вже дозволила. Якщо правилом на таблиці було FOR ALL USING (true), а не FOR SELECT, то кожен, хто її знайшов, міг рядки не лише читати, а й змінювати та видаляти, і звузити її сьогодні нічого не змінює щодо вчора. Цей варіант зазвичай виринає як звернення в підтримку про дані, які змінилися самі, або як таблиця, що раптом порожня.

Сам ремонт. Переписати політики на десятку таблиць означає змінити живу базу, і пишуть цю зміну ті самі інструменти, що створили проблему. Міграція, яка видаляє політику й перестворює її криво, це звичайнісінький вівторок, а шлях назад це копія того, як усе було годину тому.

На платному плані Supabase учорашня копія лежить у консолі. На безкоштовному плані відступати нема куди, бо безкоштовний план не робить жодних автоматичних резервних копій. Якщо це про вас, зробіть копію, перш ніж чіпати політику: як зробити резервну копію бази Supabase на безкоштовному плані це десятихвилинна версія.

Копію чого зберігає Reeve Care

Вашої бази Supabase, скопійованої за розкладом, збереженої поза вашим акаунтом Supabase, зашифрованої й перечитаної до того, як дата у вашому дашборді зрушить. Файли, які завантажили ваші користувачі, їдуть разом з нею, щойно ви під'єднаєте ключ Storage.

Дві межі, названі наперед. Резервні копії працюють лише для Supabase: якщо ваші дані живуть деінде, ми так і кажемо, замість продавати вам підписку, яка стереже порожню коробку. А ключ Storage запитується окремо, бо Supabase не видає для файлів ключа лише на читання, тож той, що копіює ваші завантаження, уміє й записувати. Той, що копіює вашу базу, не вміє. Під'єднувати його необов'язково, і база резервується в будь-якому разі.

Відновлення це та частина, яка важлива для цієї статті. Покласти стару копію поверх живої бази означає натиснути найстрашнішу кнопку в продукті, тож Care спершу знімає копію поточного стану й лише потім відтворює ту, яку ви обрали. У самого відновлення теж є скасування.

Що відбувається, коли ви натискаєте відновлення. Поточний стан копіюється назовні до того, як щось буде відтворено, тож натискання кнопки теж можна скасувати.

Друга половина Care це саме те, на що стаття весь час показує. Політику, яка послабилася під час міграції, поглядом не знайти, тож та сама зовнішня перевірка запускається знову за розкладом і повідомляє вас, коли відповідь змінюється. Аптайм, місячний звіт і сканування входять у ту саму підписку.

Чого копія не робить, так це не пише за вас політики, і жодна резервна копія не робить відкриту таблицю закритою. Ці таблиці лишаються вашою роботою. Копія міняє те, що станеться, коли ремонт піде не туди. Що Reeve резервує в Supabase, як часто і що робить відновлення малює весь цикл, а плани та їхні ціни є на сторінці цін.

Що зробити цього тижня

Що робити

  • Спершу розберіть записи "RLS disabled in public". Це таблиці, де взагалі нічого не звіряють, і виправлення для кожної це один рядок alter table.
  • Потім відкрийте Authentication → Policies і прочитайте умову кожної політики, що лишилася. USING (true) на політиці читання для Advisor невидимий, а для незнайомця відчинений навстіж.
  • Вирішіть по таблиці за раз, чи спокійно вам було б опублікувати її вміст на сторінці. Це запитання, на яке лінтер за вас не відповість, і для дозвільної політики читання воно єдине, що має значення.
  • Кладіть alter table ... enable row level security; у кожну міграцію, що створює таблицю, і після кожної запускайте Advisor наново. Типове значення в дашборді стосується лише тих таблиць, які ви наклацали.
  • Зробіть копію бази, перш ніж переписувати політики на живій таблиці, і подивіться, на якому ви плані Supabase, щоб знати, чи копія у вас уже є.

Почніть з таблиці, за яку вам було б найніяковіше як за публічну сторінку. Десятихвилинний чекліст безпеки охоплює це разом з рештою того, що варто перевірити у щойно запущеному застосунку, а посібник з безпеки Supabase проходить те, що ще зазвичай лишається відкритим.

Поширені запитання

"RLS disabled in public" це помилка чи попередження?

Помилка, і це найвищий рівень, яким користується Advisor. Запис звучить так: "Table public.<name> is public, but RLS has not been enabled." Два сусідні записи тихіші: таблиця з увімкненим перемикачем і без жодної політики позначається як INFO, а політика з завжди істинною умовою як WARN. Ці рівні кажуть, наскільки лінтер упевнений у тому, що бачить у вашій конфігурації, а не наскільки великі у вас неприємності.

Чи зламає застосунок увімкнення Row Level Security?

Одразу, так, і саме в цьому робота цього налаштування. Supabase документує, що з публічним ключем дані стають недоступними через API, доки не визначено політик, тож щойно ви виконаєте рядок, ваші списки повернуться порожніми, а екрани лишаться білими. Застосунок повернеться, коли ви додасте політику, яка каже, хто які рядки може бачити. Помилка, якої варто уникнути, це додати таку, що дозволяє всім, бо цей варіант теж змушує застосунок працювати.

Я увімкнув RLS, і тепер нічого не завантажується. Що сталося?

Нічого не зламалося. З увімкненим Row Level Security і без написаних політик Postgres (рушій бази даних під Supabase) відмовляє кожному запиту, зокрема й тим, що йдуть з вашого власного застосунку, а Advisor міняє свою помилку на запис INFO про те, що в таблиці RLS увімкнено, але політик немає. Напишіть політику для рядків, які має показувати ваш застосунок, почавши з тієї, що порівнює авторизованого відвідувача зі стовпцем власника на рядку.

Мою таблицю не позначено, але прочитати її може будь-хто. Чому?

Найімовірніше тому, що Row Level Security увімкнено, а політика читання має умову USING (true). Правило для завжди істинних політик в Advisor таки є, і політики читання воно свідомо пропускає, бо публічний доступ на читання це розумна річ для каталогу товарів чи списку опублікованих статей. Ніщо у вашому дашборді не знає, чи ваша таблиця містить заклади, чи клієнтів, тож ця перевірка лишається вашою.

Чи потрібен Row Level Security, якщо застосунок спілкується лише з моїм сервером?

Якщо браузер справді ніколи не звертається до Supabase і в коді, який ваш сайт віддає відвідувачам, немає публічного ключа, тоді Data API не є входом, і не політики захищають ці таблиці. У застосунку, зробленому в Lovable, Bolt чи v0, таке трапляється рідко, бо ці конструктори типово підключають браузер напряму до Supabase. Відкрийте власний сайт, погляньте, чи є на сторінці URL проєкту та публічний ключ, і хай ця відповідь усе вирішить.

Автор

Vlad Tkachenko

Засновник Reeve

Я щодня дивлюся на застосунки, зібрані в Lovable, Bolt, v0, Cursor і Replit, і на короткий список помилок, які трапляються в них знову і знову.

Більше про автора

Читати далі

Усі статті

Не впевнені, як справи у вашому застосунку?

Запустіть безкоштовне сканування й отримайте зрозумілу оцінку від A до F приблизно за 20 секунд. Без облікового запису й без картки.

Сканувати безкоштовно

Автоматична зовнішня перевірка, а не повний аудит. Відсутність знахідок не є гарантією безпеки.