// docs / baas security / supabase service role exposure
Ключ сервисной роли Supabase, раскрытый в JavaScript: что это значит и как его найти
Ключ сервисной роли Supabase — это мастер-ключ к вашей базе данных. Любой, кто его держит, обходит безопасность на уровне строк, может читать каждый столбец каждой таблицы и может писать или удалять всё, что захочет. Он разработан для существования исключительно в серверном коде — никогда в браузере. Когда ИИ-инструмент кодирования отправляет его в JavaScript-бандл, ваша база данных, по сути, становится публичной. Эта статья объясняет форму JWT, которая идентифицирует утёкший ключ, три ИИ-шаблона, которые производят утечку, что делать в первый час после обнаружения, и как автоматически сканировать на это до того, как это сделают пользователи.
Что такое ключ сервисной роли
Supabase выдаёт два разных ключа для каждого проекта: ключ anon (также называемый публикуемым ключом в более новых проектах) и ключ service_role. Оба являются веб-токенами JSON, подписанными секретом JWT вашего проекта. Разница заключается в утверждении role, встроенном в полезную нагрузку JWT — anon для публичного ключа, service_role для мастер-ключа. PostgREST, Supabase Storage и Supabase Auth все переключаются в режим обхода всего, когда видят утверждение 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: Ключ сервисной роли захардкожен в скриптах заполнения
Разработчик просит ИИ-инструмент написать скрипт для заполнения базы данных. ИИ жёстко кодирует ключ сервисной роли непосредственно в файл (вместо чтения из окружения), коммитит файл в репозиторий, и публичный репозиторий GitHub или маршрут /scripts/seed.js развёрнутого приложения теперь обслуживает ключ.
Как сканирование бандла FixVibe обнаруживает утечку
Проверка секретов бандла 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, чтобы очистить его из истории, затем выполните принудительный push (сначала предупредите коллег). - Повторное сканирование после исправления. Запустите свежее сканирование FixVibe против повторно развёрнутого приложения. Находка секретов бандла должна очиститься. Подтвердите, что ни в одном чанке не остаётся 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 защитить меня, если ключ сервисной роли утекает?
Нет. Безопасность на уровне строк полностью обходится утверждением service_role. Это сделано намеренно — ключ существует именно для того, чтобы серверный код мог пропускать RLS для административных операций. Смягчение состоит в том, чтобы убедиться, что ключ никогда не попадает в контекст, где злоумышленник может его прочитать.
Применимо ли это к новой модели публикуемых / секретных ключей Supabase (<code>sb_publishable_</code> / <code>sb_secret_</code>)?
Да — идентичный класс риска. Ключ sb_secret_* — это новый формат секретного ключа, заменяющий JWT сервисной роли для новых проектов. Всё, что несёт sb_secret_* в бандле, так же катастрофично, как утёкший JWT сервисной роли. Детектор секретов бандла FixVibe сопоставляет обе формы.
А как насчёт ключа anon / публикуемого ключа — он безопасен в бандле?
Да, по дизайну. Ключ anon предназначен для жизни в браузере и является тем, что использует каждый веб-клиент Supabase. Его безопасность полностью зависит от того, правильно ли настроен RLS на каждой публичной таблице. См. статью Сканер Supabase RLS о том, что проверять.
Следующие шаги
Запустите сканирование FixVibe против вашего производственного URL — проверка секретов бандла бесплатна, без регистрации и сообщает о раскрытии service_role менее чем за минуту. Сочетайте это со статьёй Сканер Supabase RLS, чтобы проверить, что слой RLS выполняет свою работу, и Чек-лист безопасности хранилища Supabase, чтобы заблокировать доступ к файлам. Для понимания, почему ИИ-инструменты так надёжно генерируют этот класс утечки, прочитайте Почему ИИ-инструменты кодирования оставляют пробелы в безопасности.
