// docs / baas security / supabase service role exposure
Сервісний ключ Supabase, розкритий у JavaScript: що це означає та як це знайти
Сервісний ключ Supabase — це майстер-ключ до вашої бази даних. Будь-хто, хто його має, обходить безпеку на рівні рядків, може прочитати кожен стовпець кожної таблиці та може писати чи видаляти що завгодно. Він спроєктований існувати виключно в серверному коді — ніколи в браузері. Коли ШІ-інструмент кодування надсилає його в JavaScript-бандл, ваша база даних, фактично, стає публічною. Ця стаття пояснює форму JWT, яка ідентифікує витеклий ключ, три шаблони ШІ-інструментів, які створюють витік, що робити в перші години після виявлення та як сканувати це автоматично раніше за користувачів.
Що таке сервісний ключ
Supabase випускає два різні ключі для кожного проєкту: ключ anon (також званий публікованим ключем у новіших проєктах) та ключ service_role. Обидва — це JSON Web Tokens, підписані секретом JWT вашого проєкту. Різниця — це claim role, зашитий у корисне навантаження JWT — anon для публічного ключа, service_role для майстер-ключа. PostgREST, Supabase Storage та Supabase Auth перемикаються в режим обходу всього, коли бачать claim service_role.
Декодуйте будь-який ключ Supabase на jwt.io та подивіться на корисне навантаження. Форма JWT сервісної ролі безпомилкова:
Декодоване корисне навантаження JWT сервісної ролі (показано як блок із підсвічуванням синтаксису нижче).
{
"iss": "supabase",
"ref": "[project-ref]",
"role": "service_role",
"iat": 1700000000,
"exp": 2000000000
}Новіші проєкти Supabase випускають ключі в стилі секретів із префіксом sb_secret_ замість JWT. Поведінка ідентична — все, що несе sb_secret_ у публічному бандлі, так само катастрофічне.
Як ШІ-інструменти кодування зливають сервісний ключ
Ми бачили ті ж три шаблони в тисячах vibe-кодованих застосунків. Кожен починається з того, що розробник просить ШІ-інструмент про допомогу, і закінчується тим, що сервісний ключ інлайниться в бандл.
Шаблон 1: Єдиний файл .env із префіксом NEXT_PUBLIC_
Розробник просить ШІ-інструмент "налаштувати Supabase" і приймає єдиний .env з обома ключами. ШІ-інструмент — натренований на корпусі, де більшість змінних оточення розкриваються через NEXT_PUBLIC_* — додає префікс NEXT_PUBLIC_ до обох. Next.js інлайнить усе, що відповідає цьому префіксу, у клієнтський бандл під час збірки. Випустіть на Vercel — і сервісний ключ опиниться в main.[hash].js.
Шаблон 2: Неправильний ключ у виклику createClient
Розробник вставляє обидва ключі у файл config.ts, згенерований ШІ, і ШІ помилково заповнює виклик createClient() на стороні браузера значенням process.env.SUPABASE_SERVICE_ROLE_KEY. Збірка підтягує змінну, і JWT опиняється в бандлі.
Шаблон 3: Сервісний ключ жорстко прописаний у seed-скриптах
Розробник просить ШІ-інструмент написати скрипт, який наповнює базу даних. ШІ жорстко прописує ключ сервісної ролі безпосередньо у файлі (замість читання зі змінних оточення), комітить файл у репозиторій, і публічний репозиторій GitHub або маршрут розгорнутого застосунку /scripts/seed.js тепер обслуговує цей ключ.
Як сканування бандла FixVibe виявляє витік
Перевірка bundle-secrets FixVibe завантажує кожен JavaScript-файл, на який посилається розгорнутий застосунок — точки входу, ліниво завантажувані фрагменти, веб-воркери, сервіс-воркери — і проганяє їх через детектор, який декодує все, що відповідає формі JWT (eyJ[base64-header].eyJ[base64-payload].[signature]). Якщо декодоване корисне навантаження містить "role": "service_role", сканування звітує про це як про критичну знахідку зі шляхом до файлу та точним рядком, де з'являється ключ. Та сама перевірка також відповідає новішому шаблону sb_secret_* за префіксом.
Сканування ніколи не автентифікується знайденим ключем. Воно ідентифікує форму та звітує про витік — використання ключа для доведення можливості експлуатації було б несанкціонованим доступом до вашої бази даних. Доказ — у самому корисному навантаженні JWT.
Виявлено — що робити в першу годину
Витеклий ключ сервісної ролі — це аварійна ситуація часу виконання. Припускайте, що ключ було зчитано — зловмисники моніторять публічні бандли в реальному часі. Ставтеся до бази даних як до скомпрометованої, доки ви не ротуєте ключ та не проведете аудит нещодавньої активності.
- Негайно ротуйте ключ. На Панелі Supabase перейдіть до Project Settings → API → Service role key → Reset. Старий ключ знецінюється протягом секунд. Будь-який серверний код, що використовує ключ, має бути оновлений і переразгорнутий до того, як ротація приземлиться.
- Проведіть аудит нещодавньої активності бази даних. Відкрийте Database → Logs на панелі. Відфільтруйте за останніми 7 днями. Шукайте незвичайні запити
SELECT *проти таблиць із PII, великі виразиUPDATEабоDELETEта запити з IP поза вашою відомою інфраструктурою. Supabase логує заголовокx-real-ipдля кожного запиту. - Перевірте об'єкти сховища. Відвідайте Storage → Logs та перегляньте нещодавні завантаження файлів. Витеклий ключ сервісної ролі надає доступ із обходом усього й до приватних кошиків теж.
- Видаліть ключ із системи контролю версій. Навіть після ротації залишення JWT у вашій git-історії означає, що його можна виявити в публічному репозиторії. Використовуйте
git filter-repoабо BFG Repo-Cleaner, щоб витерти його з історії, потім зробіть force-push (попередньо попередивши співавторів). - Повторне сканування після виправлення. Запустіть свіже сканування FixVibe проти переразгорнутого застосунку. Знахідка bundle-secrets має зникнути. Підтвердіть, що жоден JWT
service_roleі жодний рядокsb_secret_*не залишився в жодному фрагменті.
Запобігання витоку від самого початку
Структурне виправлення — це дисципліна найменування плюс інструментальні запобіжники:
- Ніколи не додавайте до сервісного ключа префікс
NEXT_PUBLIC_*,VITE_*чи будь-який інший префікс інлайнінгу бандла. Угода про найменування — це межа — кожен фреймворк її поважає. - Тримайте сервісний ключ повністю поза
.envна машині розробника. Читайте його з менеджера секретів (Doppler, Infisical, шифровані змінні оточення Vercel) під час розгортання, ніколи не комітьте локально. - <strong>Mark every Supabase client construction with explicit context.</strong> Files named <code>supabase/browser.ts</code> use the anon key; files named <code>supabase/server.ts</code> use the service-role key with <code>import 'server-only'</code> at the top. The <code>server-only</code> import causes a build error if a client component tries to consume the module.
- <strong>Add a pre-commit hook that greps for JWT-shaped strings.</strong> <code>git diff --staged | grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'</code> catches both anon and service tokens before they leave your machine.
- Додайте CI-гейт, який сканує вихід збірки. Після
next buildзробіть grep у виході.next/static/chunks/за рядкомservice_role. Завершуйте збірку з помилкою, якщо щось знайдено.
# Pre-commit hook: refuse any staged JWT-shaped string.
git diff --staged \
| grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' \
&& echo "JWT detected in staged changes — refusing commit" \
&& exit 1
# CI gate: fail the build if "service_role" shipped to the static bundle.
grep -RE 'service_role|sb_secret_' .next/static/chunks/ \
&& echo "Service-role credential leaked into bundle" \
&& exit 1Поширені запитання
Наскільки швидко зловмисники насправді знаходять витеклі ключі сервісної ролі Supabase?
Сканери публічних бандлів обходять нові розгортання за хвилини. Дослідники задокументували робочі експлойти проти нових проєктів Supabase менш ніж за годину від першого розгортання. Ставтеся до будь-якого розкриття сервісної ролі як до 60-хвилинного вікна, а не 60-денного.
Чи достатньо ротувати ключ, чи я мушу припустити вилучення даних?
Ротація знецінює витеклий ключ, але не скасовує вже витягнуті дані. Якщо ваші таблиці містять PII, платіжні дані або будь-які регульовані дані, у вас може виникнути обов'язок повідомлення згідно з GDPR (72 години), CCPA або HIPAA. Проведіть аудит логів та проконсультуйтеся з юридичним консультантом, якщо аудит покаже підозрілий доступ.
Чи може RLS захистити мене, якщо ключ сервісної ролі витече?
Ні. Безпеку на рівні рядків повністю обходить claim service_role. Це за задумом — ключ існує саме для того, щоб дозволити серверному коду пропустити RLS для адміністративних операцій. Пом'якшення полягає в тому, щоб переконатися, що ключ ніколи не досягає контексту, де зловмисник може його прочитати.
Чи стосується це нової моделі публікованих / секретних ключів Supabase (<code>sb_publishable_</code> / <code>sb_secret_</code>)?
Так — ідентичний клас ризику. Ключ sb_secret_* — це новий формат секретного ключа, який замінює JWT сервісної ролі для новіших проєктів. Усе, що несе sb_secret_* у бандлі, так само катастрофічне, як витеклий JWT сервісної ролі. Детектор bundle-secrets FixVibe відповідає обом формам.
А як щодо ключа anon / publishable — чи безпечно це у бандлі?
Так, за задумом. Ключ anon призначений жити в браузері й це те, що використовує кожен веб-клієнт Supabase. Його безпека повністю залежить від того, що RLS правильно налаштовано на кожній публічній таблиці. Дивіться статтю Сканер Supabase RLS щодо того, що перевірити.
Наступні кроки
Запустіть сканування FixVibe проти вашого продакшен-URL — перевірка bundle-secrets безкоштовна, без реєстрації, та звітує про розкриття service_role менш ніж за хвилину. Поєднайте це зі статтею Сканер Supabase RLS, щоб переконатися, що шар RLS виконує свою роботу, та з Чек-лист безпеки сховищ Supabase, щоб обмежити доступ до файлів. Для розуміння, чому ШІ-інструменти так надійно генерують цей клас витоків, читайте Чому ШІ-інструменти кодування залишають прогалини в безпеці.
