Основи безпеки
Як замінити злитий ключ service_role у Supabase
Supabase каже спершу закрити витік. Інші посібники кажуть міняти негайно. Що правильно, залежить від того, куди саме потрапив ваш ключ service_role.

Коротко
- Якщо ваш ключ service_role у Supabase лежить у JavaScript, який завантажує браузер, замініть його перш ніж щось лагодити. Копія, яка вже пішла назовні, не має терміну придатності.
- Якщо він потрапив лише в приватний репозиторій чи лог, спершу закрийте джерело, інакше новий ключ вийде назовні слідом за старим на наступному деплої.
- Старий ключ service_role замінити взагалі неможливо. Щоб знецінити його, треба створити нові ключі sb_secret_, перевести застосунок на них і вимкнути стару пару перемикачем, який стосується обох.
Вам сказали, що ваш ключ service_role у Supabase лежить у застосунку, де його
може прочитати будь-хто. Перш ніж щось міняти, переконайтеся, що знайшли саме
цей ключ: наше безкоштовне сканування читає ваш живий сайт так само,
як прочитав би сторонній, і каже, який ключ Supabase воно там справді бачить.
Без облікового запису й без встановлення чогось.
Порада, що йде за такою знахідкою, майже завжди складається з одного слова: міняйте. Далі ви шукаєте, як це зробити, і джерела суперечать одне одному. Власна документація Supabase починає свій посібник із ротації словами, що спершу треба усунути причину витоку. Пів десятка сторонніх посібників радять міняти негайно, а питання ставити потім.
Ось частина, якої не пише жоден із них: обидва мають рацію, але в різних ситуаціях, і питання, яке їх розділяє, це куди саме потрапив ключ.
Одне варто прояснити наперед. Видалити ключ із коду означає зняти власну копію зі зв'язки. Замінити його означає змінити замок. Лише друге дістає до копій, які вже є в інших.
Це справді ключ service_role?
У новішому проєкті відповідь дають перші кілька символів. sb_secret_ на
початку означає секретний. sb_publishable_ це ключ, який і має бути у вашому
застосунку, і знайти його там взагалі не знахідка.
Переконайтеся до того, як робити щось різке, бо ключ, який знаходять у застосунку, зібраному в стилі vibe coding, зазвичай і є тим, якому там місце.
Старіші проєкти видають іншу пару, anon і service_role, і ці два виглядають
майже однаково: той самий формат, та сама довжина, жодного префікса для
читання. Середня секція такого ключа взагалі не зашифрована. Вона розкодовується
в кілька рядків звичайного тексту, і один із них називає роль.
Які ключі API безпечні у фронтенді
проходить обидві перевірки.
Причина переконатися в тому, що це трапляється рідше, ніж підказують
попередження. У серпні 2026 року ми просканували 30 998 живих застосунків,
зібраних у стилі vibe coding, і знайшли опублікований ключ service_role у 3 з
них. Ключ API Google, який зазвичай нешкідливий і часто обмежений одним доменом,
трапився в 1 142.
Повний підрахунок тут.
Спершу замінити чи спершу закрити витік?
Це залежить від того, чи ключ уже публічний, і межа між двома випадками чітка.
Якщо ключ лежить у JavaScript, який завантажують ваші відвідувачі, він уже публічний. Копія є в кожного, хто відкрив ваш сайт, поки ключ був живий, і в кожного автоматичного сканера, який шукав саме цей рядок. Ніщо зі змін у вашому коді до тих копій не дістане. Спершу замініть, потім закрийте джерело.
Якщо він потрапив лише в приватний репозиторій, файл логу, змінну CI чи чат, розголос обмежений. Замінивши тут першою чергою, ви зробите це двічі, бо наступний деплой знову виштовхне старе значення з того, що його породжує, і новий ключ піде слідом за старим. Спершу закрийте джерело, потім замінюйте.
Посібник Supabase написаний для другого випадку. Термінові посібники написані для першого. Якщо ви читаєте це тому, що сканер знайшов ключ на вашій живій URL-адресі, ви в першому випадку.
Як замінити секретний ключ Supabase
Якщо у вашому проєкті є ключі sb_secret_, це робота в панелі й без простою.
Проєкт може тримати кілька секретних ключів одночасно, кожен зі своєю назвою, і саме це робить операцію безпечною: ви додаєте новий до того, як прибрати старий, тож між цими двома моментами ніщо не зламане.
- Відкрийте проєкт, перейдіть у Settings → API Keys і створіть новий секретний ключ.
- Пропишіть його скрізь, де використовувався старий, а це все має бути на сервері: edge-функції, вебхуки, заплановані завдання, власний бекенд. Де лежать ці значення, якщо ви ніколи не відкривали ту сторінку.
- Зробіть деплой, потім пройдіться частинами застосунку, які читають і пишуть дані. Неправильний секретний ключ падає гучно й одразу, і це добрий випадок.
- Тільки після цього видаліть скомпрометований ключ.
Крок 4 остаточний. Supabase справді видаляє секретний ключ: без скасування, без повторного ввімкнення, без відкладеної копії, яку можна повернути. Тому він і йде останнім.
Видалення секретного ключа не виводить ваших користувачів із сеансів. Ключ API каже, який застосунок звертається до вашої бази даних, а токен сеансу відвідувача каже, який це користувач. Друге перевіряється за ключем підпису вашого проєкту, а це окреме значення, якого ви не чіпали.
Чому старий ключ service_role у Supabase не можна замінити
Бо для цього немає кнопки. Нотатка Supabase з усунення несправностей каже, що
пряма ротація старих секретів anon, service_role і JWT більше не
підтримується, і скеровує вас до нових ключів.
Тож знецінити злитий старий ключ service_role це міграція, а не ротація:
- Створіть нові ключі. Це додає
sb_publishable_іsb_secret_поруч зі старою парою. Обидві системи працюють одночасно, і ніщо не ламається. - Переведіть фронтенд на публічний ключ. У Lovable чи Bolt це зазвичай одне значення в налаштуваннях проєкту, а не рядок коду.
- Переведіть кожне серверне використання на секретний ключ.
- Вимкніть старі ключі в налаштуваннях проєкту.
Крок 4 це один перемикач, і він стосується обох старих ключів. anon і
service_role є JWT, підписаними тим самим секретом, тож перемикач, що
відкликає один, відкликає й другий, а фронтенд, який досі несе старий ключ
anon, перестає працювати тієї миті, коли ви його натискаєте. Саме тому крок 2
йде раніше.
Вимкнення оборотне, і це єдина добра новина в цьому розділі: якщо щось забуте досі користується старим ключем, ви вмикаєте їх назад, поки це виправляєте. Що входить у міграцію розказано там докладніше.
Звідки витік ключ і як це закрити
Три причини пояснюють майже все, і всі три лежать усередині вашого власного проєкту.
Префікс VITE_ або NEXT_PUBLIC_. Це не налаштування безпеки, яке хтось
забув увімкнути. Це вказівка збірці: поклади це значення в бандл. Змінна з
назвою VITE_SUPABASE_SERVICE_ROLE_KEY потрапила у ваш JavaScript навмисно,
через інструмент, який зробив рівно те, що йому сказали.
Значення, вставлене просто в компонент. Жодного префікса й жодного файлу
.env, лише ключ у рядку коду, бо це був найшвидший спосіб змусити запит щось
повернути.
Робота, місце якої на сервері. Видалити обліковий запис, записати в таблицю, куди вашим користувачам писати не можна, прочитати рядки по всіх одразу. За ключ узялися, бо браузер не міг виконати цю роботу, і рішення в тому, щоб перенести саму роботу: edge-функція, serverless-маршрут, будь-що, чого ваші відвідувачі не завантажують.
Як дізнатися, чи хтось ним скористався?
Зазвичай напевно дізнатися не вийде. Що ви можете, так це звузити вікно й зазирнути всередину.
Вікно відкривається деплоєм, який уперше опублікував ключ, і закривається тоді, коли ви його вимкнули. Ваш проєкт Supabase веде логи і для API, і для бази даних, і саме за цим діапазоном їх треба фільтрувати. Ви шукаєте читання й записи, яких не можете пояснити: запити до таблиць, яких ваш застосунок ніколи не торкається, трафік у години, коли ним ніхто не користувався, видалення, яких ніхто не робив.
Далі перевірте самі дані. Кількість рядків проти того, що ви очікуєте, ваші власні записи облікового запису, будь-що з міткою часу, яка зрушила, поки ніхто не працював. Повідомлення в підтримку про дані, що змінилися самі собою, це спосіб, у який більшість таких випадків справді виявляють, і воно приходить за кілька тижнів.
Ключ service_role не дістає ні до вашого платіжного провайдера, ні до
розсилання пошти. Усе одно варто знати, чи був він єдиним ключем у вашому
бандлі, бо ті, що витрачають гроші, витікають тим самим шляхом:
що публікували 30 998 застосунків.
Перевірте живий застосунок, перш ніж вважати справу закритою
Завантажте сайт у приватному вікні, відкрийте JavaScript, який завантажив браузер, і пошукайте в ньому старе значення. Потім пошукайте нове, якого там теж бути не повинно.
Дві речі роблять цю ручну перевірку доречною. Збірка може ще якийсь час після деплою віддавати закешований бандл, тож файл, який отримує відвідувач, не завжди той, що ви щойно зібрали. І ключ може лежати не в одному місці: друга точка входу, service worker, стара збірка, яку досі віддають зі шляху, на який ніхто не посилається.
Наше безкоштовне сканування робить цю частину ззовні, за URL-адресою, якою справді користуються ваші відвідувачі, і розкодовує ключ Supabase достатньо глибоко, щоб прочитати роль, тож публічний ключ повертається з галочкою. Це займає близько двадцяти секунд і не потребує облікового запису: просканувати застосунок.
Що робити зараз
Що робити
- Спершу підтвердьте ключ.
sb_secret_спереду або"role": "service_role"усередині старішого. Публічний ключ у вашому фронтенді це не знахідка. - Якщо він у бандлі, який завантажують ваші відвідувачі, замініть його до того, як міняти код. Правки в застосунку не дістають до копій, які вже пішли назовні.
- Якщо він витік лише в репозиторій, лог чи чат, спершу закрийте джерело, щоб наступний деплой не виштовхнув новий ключ назовні слідом.
- У новішому проєкті: створіть другий секретний ключ, переведіть на нього код сервера, зробіть деплой, потім видаліть старий. Видалення остаточне.
- У старому проєкті кнопки заміни немає. Створіть нові ключі, переведіть на них фронтенд і сервер, а тоді вимкніть стару пару тим єдиним перемикачем, що стосується обох.
- Після цього пошукайте у своєму живому бандлі. Закешована збірка може віддавати старе значення ще якийсь час після деплою.
Ключ service_role читає й пише кожен рядок вашої бази даних. Найгірша версія
цієї історії закінчується порожньою таблицею, і чи повернуться ті рядки,
залежить від того,
що саме ви зберігали, перш
ніж усе це сталося.
Поширені запитання
Чи зламає заміна ключа мій застосунок, який уже працює?
Ні, якщо дотримати порядку. У проєкті з новими ключами ви додаєте другий секретний ключ, переводите на нього код сервера, робите деплой і лише тоді видаляєте старий, тож не буває моменту без робочого ключа. У старому проєкті ризик інший: вимкнення старої пари вимикає й ключ anon, який використовує ваш фронтенд, тож застосунок упаде, якщо він на момент перемикання ще не перейшов на публічний ключ.
Чи можна ротувати старі ключі anon і service_role?
Ні. Документація Supabase з усунення несправностей каже, що пряма ротація старих секретів anon, service_role і JWT більше не підтримується. Щоб знецінити злитий старий ключ, треба створити нові ключі sb_publishable_ і sb_secret_, перевести на них застосунок і код сервера, а потім вимкнути стару пару в налаштуваннях проєкту.
Видалити старий ключ чи вимкнути його?
Вибору немає, бо дві системи ключів поводяться по-різному. Новіший ключ sb_secret_ саме видаляється, назавжди, без жодного способу повернути. Стару пару натомість вимикають, і вимкнення оборотне: якщо щось забуте досі користується старим ключем, ви вмикаєте їх назад, поки це виправляєте.
Як дізнатися, чи хтось ним скористався?
Зазвичай напевно дізнатися не вийде. Натомість звузьте це до вікна: ключ був придатний від деплою, який уперше його опублікував, до моменту, коли ви його вимкнули. Відфільтруйте логи API та бази даних вашого проєкту Supabase за цим діапазоном і шукайте читання й записи, яких не можете пояснити, а потім перевірте самі дані: кількість рядків і мітки часу, що не збігаються ні з чим, що ви робили.
Чи треба замінювати й ключ anon?
У старому проєкті вибору немає: вимкнення старої пари забирає обидва ключі одразу, тож ваш фронтенд має отримати новий публічний ключ до того, як ви натиснете перемикач. У новішому проєкті публічний ключ і задуманий як публічний, і чіпати його немає причин. Що варто перевірити в обох випадках, так це Row Level Security, бо це єдине, що стоїть між публічним ключем і всією вашою базою даних.