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

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

Supabase "permission denied for table": бракує GRANT

З 30 жовтня нова таблиця в Supabase відповідає "permission denied for table", доки ви не дасте доступ. Лист показує лише половину виправлення.

Vlad Tkachenko8 хв читання
Ряд дверей до таблиць бази даних. Старіші стоять відчинені, за ними світяться рядки, а найновіші, в кінці ряду, зачинені.

Коротко

  • З 30 жовтня 2026 року нова таблиця у вашому проєкті Supabase відповідає "permission denied for table", доки хтось не відкриє до неї доступ. Таблиці, які у вас уже є, працюють так само, як сьогодні.
  • GRANT вирішує, чи запит узагалі дістанеться таблиці. Row Level Security і далі вирішує, які рядки він забере. Якщо дати anon доступ до таблиці без правил, кожен її рядок зможе прочитати будь-хто.
  • Зміна не закриває нічого, що вже відкрите. Перевірте, що може прочитати незнайомець сьогодні, і повторюйте це після кожної нової таблиці.

Якщо ваш застосунок працює на Supabase, ви, мабуть, уже отримали лист про 30 жовтня. У ньому сказано, що для таблиць, які у вас уже є, нічого не змінюється, а потім вам дають три команди SQL. Або вже листопад: ви попросили Lovable, Bolt чи Cursor про нову функцію, новий екран відкрився порожнім, а десь у консолі лежить повідомлення permission denied for table. І те, і те стосується однієї зміни в Supabase, просто з різних боків від дати.

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

Що означає "permission denied for table" у Supabase?

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

Supabase відносить кожен запит до певної ролі, тобто до типу відвідувача, від якого він прийшов. anon означає когось, хто не увійшов. authenticated означає когось, хто увійшов. service_role означає ваш власний серверний код із секретним ключем. GRANT це команда, яка дає одній із цих ролей доступ до однієї таблиці в Postgres, базі даних, на якій працює Supabase. Немає GRANT, немає доступу, хоч би що ще ви написали.

Коли GRANT бракує, Supabase відповідає ось так, зазвичай як 401 або 403:

{
  "code": "42501",
  "message": "permission denied for table comments",
  "hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.comments TO anon;"
}

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

Уявіть кожну таблицю як кімнату. GRANT це двері, і для кожної ролі двері окремі. Row Level Security, правила для окремих рядків, які ви, можливо, вже бачили, вирішує, які шухляди відвідувач може відкрити, коли вже зайшов. "Permission denied for table" означає, що хтось стоїть перед зачиненими дверима. До ваших правил він так і не дійшов.

Що зміниться 30 жовтня?

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

Досі Supabase відчиняв усі три двері кожної нової таблиці автоматично: читання, додавання, зміну й видалення для anon, authenticated і service_role однаково. Таблиця ставала досяжною для вашого застосунку щойно зʼявлялася, і між незнайомцем та її рядками стояли лише ваші правила Row Level Security. Changelog Supabase прямо пояснює причину: агенти й платформи зі ШІ тепер створюють таблиці так, що зміну ніхто не переглядає, а автоматичні гранти відкривали таблиці, які "a developer forgot to protect", тобто розробник забув захистити.

ДатаЩо сталося або станеться
28 квітня 2026Нові проєкти отримали змогу відмовитися від автоматичних грантів під час створення.
30 травня 2026Відсутність автоматичних грантів почала ставати типовою для нових проєктів.
30 жовтня 2026Наявні проєкти теж перестають їх отримувати.

Якщо ваш проєкт створено після кінця травня, він, можливо, вже так і працює, бо Supabase поширював нову типову поведінку на нові проєкти протягом кількох тижнів після цієї дати.

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

Три деталі варто знати ще до цієї дати:

  • Лише схема public. Storage і вхід тримають свої таблиці в окремих схемах, storage та auth, і Supabase каже, що їхні гранти й типові налаштування лишаються як є.
  • Секретному ключу теж відмовлять. Автоматичні гранти покривали й service_role, тож edge function (серверний код, який Supabase запускає для вас) із вашим секретним ключем отримає ту саму помилку на новій таблиці, доки service_role не матиме власного GRANT.
  • Таблиця, яку видалили й створили знову, це нова таблиця. Гранти належать самій таблиці й зникають разом із нею, коли її видаляють. Якщо ваш конструктор перебудовує таблицю, щоб її змінити, перебудована починає із зачиненими дверима.

Чи зламається мій застосунок 30 жовтня?

Не того дня. Він зламається, коли щось уперше створить нову таблицю й не створить разом із нею GRANT.

Для більшості читачів це щось і є конструктор. Ви просите розділ коментарів. Конструктор пише міграцію, тобто файл змін у базі, який він запускає за вас, і міграція створює таблицю comments. Якщо він пише й гранти, цієї помилки ви не побачите ніколи. Якщо ні, екран коментарів нічого не показує, або коментар не зберігається, або зʼявляється червоне повідомлення, залежно від того, як ваш застосунок обробляє помилки. Усі екрани, які у вас уже були, працюють далі.

Supabase публікує agent skill для інструментів програмування зі ШІ, і в ньому є крок із GRANT. Чи користується ним ваш конструктор, вирішує сам конструктор, тож безпечніше вважати, що наступна таблиця, яку він створить, може зʼявитися із зачиненими дверима.

Чи безпечно виконати GRANT із помилки?

Лише тоді, коли в таблиці увімкнено Row Level Security і для неї написано правило. GRANT вирішує, хто пройде у двері. Які шухляди відвідувач відкриє, він не вирішує жодним чином.

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

Лист показує три гранти й на цьому зупиняється. Changelog самого Supabase показує три кроки й радить виконувати їх як одне ціле ("treat these three steps as a unit"):

-- 1. хто може дістатися таблиці
grant select on public.orders to authenticated;
grant select, insert, update, delete on public.orders to service_role;

-- 2. увімкнути правила для рядків
alter table public.orders enable row level security;

-- 3. саме правило
create policy "Customers read their own orders"
  on public.orders
  for select
  to authenticated
  using (auth.uid() = user_id);

Рядка для anon у цьому прикладі немає, і так задумано. Ніхто без входу не має діставатися таблиці замовлень, тож двері для незнайомців лишаються зачиненими, а покупці, які увійшли, отримують лише читання, потрібне їхньому екрану. Далі правило звужує це до їхніх власних рядків. Supabase і сам радить давати кожній ролі мінімум, який їй потрібен. GRANT для anon доречний на таблиці, чиї рядки призначені для всіх, наприклад на коментарях під публічним дописом.

GRANT вирішує, чи запит дістанеться таблиці. Правила за ним вирішують, скільки він забере. Відчинені двері без правил за ними віддають усю таблицю.

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

Чому виправлення Row Level Security не діють на цю помилку

Бо GRANT перевіряють першим. Запит, якому відмовили на дверях, ніколи не доходить до ваших правил, тож зміна правил нічого в помилці не змінює.

Це важливо, бо код у них спільний. 42501 Postgres повертає й тоді, коли Row Level Security відмовляє у збереженні, як у випадку new row violates row-level security policy. Асистент, який орієнтується лише на код, може взятися за виправлення від тієї помилки: вимкнути Row Level Security або додати правило з using (true), яке пропускає всіх. Жодне з них цієї помилки не прибере. Обидва лишаться і після того, як GRANT нарешті зʼявиться, і тоді двері відчинені до шухляд без замків.

Слова в повідомленні розрізняють ці два випадки, хоча число їх не розрізняє:

Повідомлення кажеЩо саме відмовилоЩо це виправляє
permission denied for tableдвері (GRANT)GRANT для ролі, яку називає підказка
new row violates row-level security policyправилаполітика, яка дозволяє цей рядок
помилки немає, а список порожнійправилаполітика читання для цієї ролі

Чого 30 жовтня не виправить

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

Зміна стосується таблиць, яких ще немає. Досі кожна нова таблиця зʼявлялася з відчиненими дверима, тож у застосунку, зробленому до 30 жовтня, може бути така, де замків ніхто так і не поставив: Row Level Security ніколи не вмикали або увімкнули з правилом, яке впускає всіх. Після 30 жовтня ці кімнати лишаються рівно такими, як були.

Три місця показують, де ви зараз:

  1. Сторінка налаштувань Data API, на яку веде посилання в листі. У дашборді вона в розділі Integrations, далі Data API, і там видно, які з ваших таблиць узагалі досяжні.
  2. Security Advisor, який, за словами Supabase, перелічує таблиці, які варто переглянути до зміни.
  3. Погляд ззовні. Жодне з перших двох місць не каже, що саме отримує незнайомець. Для цього треба спитати ваш живий застосунок так, як спитав би незнайомець, і саме це робить наше сканування.

Як Reeve стежить за таблицями, які ви додаєте

Після 30 жовтня кожна таблиця, яку додає ваш конструктор, це нове рішення про те, хто пройде у двері. Reeve перевіряє відповідь ззовні, а Monitor перевіряє її й далі.

  • Безкоштовне сканування питає в кожної таблиці, яку згадує ваш застосунок, скільки рядків отримав би відвідувач без входу, читає це число й на цьому зупиняється, не завантажуючи жодного рядка. Близько 20 секунд, без облікового запису: просканувати застосунок.
  • Reeve Monitor запускає всі девʼять перевірок щогодини для трьох застосунків максимум і пише вам листа того дня, коли ваша оцінка гіршає, тож деплой, який щось відчинив, не чекатиме, поки ви самі прийдете подивитися.
  • Care запускає ті самі перевірки й зберігає зашифровану копію вашої бази Supabase поза вашим обліковим записом Supabase, тож якщо виправлення від асистента перебудує таблицю, буде копія, до якої можна повернутися. Як копію створюють і повертають.

Що входить у кожен тариф, написано на сторінці цін.

Що зробити до 30 жовтня

Що робити

  • Не чіпайте таблиці, які у вас уже є, якщо вам потрібно лише, щоб застосунок і далі працював. Вони зберігають свої гранти.
  • Попросіть конструктор писати GRANT, enable row level security і політику в тій самій міграції, що створює кожну нову таблицю, як одну зміну.
  • Коли зʼявиться помилка, подивіться, яку роль називає підказка. anon означає кожного відвідувача вашого застосунку.
  • Не давайте anon жодного GRANT на таблицю, доки не увімкнено Row Level Security і не написано правило. Таблицям із людьми чи замовленнями GRANT для anon зазвичай не потрібен узагалі.
  • Відмовляйтеся від будь-якого виправлення, яке вимикає Row Level Security або додає using (true), щоб прибрати помилку доступу. Жодне з них на неї не діє.
  • Перевірте, що незнайомець може прочитати з таблиць, які у вас уже є. Після 30 жовтня вони лишаються такими ж.

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

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

Чи перестане мій застосунок на Supabase працювати 30 жовтня?

Ні. Кожна таблиця, що існує на 30 жовтня, зберігає свій доступ, і Supabase підтвердив, що ці гранти не відкличуть. Змінюється наступна таблиця, створена після цієї дати. Вона зʼявляється недосяжною для вашого застосунку, доки хтось не відкриє до неї доступ, тож першою зламається перша нова функція, якій потрібна нова таблиця.

Що означає "permission denied for table" у Supabase?

Це означає, що тип відвідувача, від якого прийшов запит, узагалі не отримав доступу до цієї таблиці, тож ваша база відмовила ще до того, як подивилася бодай на один рядок. Повідомлення походить від Postgres, бази даних, на якій працює Supabase, має код 42501 і зазвичай доходить до застосунку як 401 або 403. Supabase додає підказку з точною командою GRANT, яка впустила б цього відвідувача.

Чи безпечно виконати GRANT, який пропонує помилка?

Лише тоді, коли в таблиці увімкнено Row Level Security і для неї написано правило. GRANT для anon дозволяє кожному відвідувачу вашого застосунку просити в таблиці рядки, бо ключ, яким робляться ці запити, їде всередині застосунку. Коли правило є, відвідувач отримує ті рядки, які воно дозволяє. Коли правила немає, а Row Level Security вимкнено, він отримує всі.

Чи зачіпає ця зміна Storage, вхід або мої edge functions?

Storage і вхід тримають свої таблиці в окремих схемах, storage та auth, і Supabase каже, що їхні гранти й типові налаштування лишаються як є. З edge functions інакше. Функції, яка користується вашим секретним ключем, усе одно потрібен GRANT на кожну нову таблицю, бо автоматичні гранти, які прибирають, покривали service_role так само, як і дві ролі ваших відвідувачів.

Чи можна увімкнути стару поведінку назад?

Supabase документує спосіб, як налаштування на сторінці Data API в дашборді або як SQL, і не радить цього робити. Коли вона увімкнена, кожна нова таблиця досяжна для кожного відвідувача з моменту появи, і між незнайомцем та її рядками стоїть лише Row Level Security. Так працював кожен проєкт до цієї зміни.

Автор

Vlad Tkachenko

Засновник Reeve

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

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

Читати далі

Усі статті

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

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

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

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