Опубликовано 2026-10-27 · Обновлено 2026-10-27
Безопасность Supabase: чек-лист RLS, ключей и Storage
Коротко: чтобы защитить приложение на Supabase, включите row-level security (RLS) на каждой таблице в открытой схеме, напишите по одной политике на каждую операцию, не пускайте секретный ключ в браузер, проверьте доступ от имени неавторизованного посетителя, закройте бакеты Storage и запустите Security Advisor до запуска. В документации Supabase сказано: таблица в открытой схеме без RLS доступна на чтение и запись любой роли, у которой есть на неё права.
Row-level security (защита на уровне строк) это функция Postgres, которая привязывает к таблице правила, так что каждый запрос видит и меняет только разрешённые строки. Эта страница описывает часть Supabase из общего чек-листа безопасности вайб-кодинга. Проходите её перед запуском и после каждого изменения схемы.
В этой статье
Что открывает Supabase и почему RLS необходим Шаг 1: включите RLS на каждой открытой таблице Шаг 2: напишите политики, по одной на операцию Шаг 3: разберитесь с ключами: publishable и secret Шаг 4: проверьте доступ как посетитель и как другой пользователь Шаг 5: проверьте представления и функции, обходящие RLS Шаг 6: закройте бакеты Storage Шаг 7: усильте Auth Шаг 8: пусть Security Advisor найдёт пропущенное Предзапусковая проверка на одном экране Часто задаваемые вопросыБыстрый результат (20 минут)
- Откройте Security Advisor и исправьте все находки «RLS disabled in public». См. шаг 8.
- Отправьте один запрос на REST-адрес только с publishable-ключом и убедитесь, что приватных строк в ответе нет. См. шаг 4.
- Найдите во фронтенд-коде и репозитории секретный ключ (он начинается с
sb_secret_) или устаревший ключservice_role. - Проверьте, что все бакеты Storage с файлами пользователей приватные, а политики ограничивают каждого пользователя его папкой.
- Убедитесь, что Site URL и список разрешённых redirect-адресов указывают на ваш настоящий домен, а не только на localhost.
Что открывает Supabase и почему RLS необходим
Каждому проекту Supabase выдаёт REST API (Data API) по адресу вида https://YOUR_PROJECT_REF.supabase.co/rest/v1/. В руководстве по API сказано, что он работает за API-шлюзом с включённой проверкой ключа и согласован с row-level security в Postgres. Ваше приложение обращается к нему напрямую, поэтому между посетителем и базой нет вашего сервера. Единственная проверка это правила в самой базе.
Доступ решается в два слоя, как это описано на странице Securing your API: права (grants) определяют, может ли роль вообще обращаться к объекту, а RLS определяет, к каким строкам. Вы будете встречать роли anon (запрос без вошедшего пользователя) и authenticated (вошедший пользователь).
Там же предупреждение: на существующих проектах новые объекты схемы public автоматически получают права для этих ролей, и поэтому доступны через Data API, даже если вы этого не хотели. Поэтому таблица, созданная ИИ-ассистентом, может быть открыта ещё до первой политики. Документация Lovable использует Supabase как бэкенд приложения и советует перед запуском убедиться, что у каждой таблицы есть политики RLS.
Команды ниже взяты из руководства Hardening the Data API. Общие правила обращения с ключами: управление секретами и переменные окружения.
Шаг 1: включите RLS на каждой открытой таблице
В руководстве по Row Level Security сказано включать RLS на каждой таблице в открытой схеме. Таблица, созданная в Table Editor дашборда, получает его автоматически. Таблица, созданная SQL-запросом, миграцией или другим инструментом, не получает, а ИИ-ассистенты обычно создают таблицы именно через SQL.
alter table public.notes enable row level security;
После включения RLS, по документации, через API с publishable-ключом данные недоступны, пока вы не создадите политики. Не «чините» пустой ответ повторным отключением RLS.
- Включите RLS на каждой таблице и представлении, до которых дотягивается Data API, включая мелкие справочники.
- Открывайте меньше. Supabase рекомендует отдельную схему, например
api, для открытых объектов, а внутренние таблицы держать в закрытых схемах. Если приложение не использует Data API, его можно отключить. - Выдавайте каждой роли только нужные права. Отсутствие права даёт ошибку
42501(permission denied): выдавайте право осознанно, а не всё подряд. - Учтите изменение октября 2026 года. По changelog Supabase, новые таблицы в
publicперестают открываться автоматически: на существующих проектах это начинается 30 октября 2026 года, а у существующих таблиц права сохраняются. Каждой новой таблице тогда нужны явныйgrant, RLS и политики в одной миграции. Выполните проверочный запрос из руководства по hardening, потому что значения по умолчанию зависят от возраста проекта.
Чтобы убрать доступ Data API у таблицы, в changelog приведена команда:
revoke all on table public.your_table from anon, authenticated, service_role;
Шаг 2: напишите политики, по одной на операцию
Политика это правило, привязанное к таблице. Важны две части: USING определяет, какие существующие строки операция может видеть или затрагивать (для select, delete и старой строки при update), а WITH CHECK определяет, допустима ли новая или изменённая строка (для insert и новой строки при update). В примере таблица public.notes, где каждая строка принадлежит пользователю:
create table public.notes (
id bigint generated always as identity primary key,
user_id uuid not null references auth.users (id),
body text not null
);
alter table public.notes enable row level security;
grant select, insert, update, delete on table public.notes to authenticated;
create policy "Users read own notes" on public.notes
for select to authenticated
using ( (select auth.uid()) = user_id );
create policy "Users add own notes" on public.notes
for insert to authenticated
with check ( (select auth.uid()) = user_id );
create policy "Users edit own notes" on public.notes
for update to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
create policy "Users delete own notes" on public.notes
for delete to authenticated
using ( (select auth.uid()) = user_id );
Строка с grant нужна только на проектах, где новые таблицы не получают права автоматически (см. шаг 1), и она намеренно не затрагивает anon. Функция auth.uid() возвращает ID вошедшего пользователя. У политики на обновление есть обе части, чтобы пользователь не мог передать строку другому.
- Одна политика на операцию. Документация не советует
for all, потому что оно скрывает, какую операцию покрывает правило. Дляupdateнужна и подходящая политикаselect. - Указывайте роль через
to. Политика вродеto anon using ( true )открывает все строки, до которых роль дотягивается по правам. Используйте её только для данных, которые должны быть публичными. - У неавторизованного посетителя
auth.uid()равен null, и сравнение ничего не находит. Анонимные входы в Supabase Auth получают рольauthenticated, так что «authenticated» не всегда означает зарегистрированного человека. - Никогда не основывайте доступ на
user_metadata, потому что пользователи могут его менять. Для данных авторизации документация указываетapp_metadata. - Производительность: документация советует
(select auth.uid())вместоauth.uid(), чтобы Postgres мог кешировать результат, и индекс на столбцы, по которым фильтруют политики.
Основы описаны в статье глоссария row-level security и в принципе наименьших привилегий.
Шаг 3: разберитесь с ключами: publishable и secret
В руководстве по ключам API описаны два вида ключей для Data API. От того, какой из них попадёт во фронтенд, зависит цена утечки.
- Publishable-ключ (начинается с
sb_publishable_): по документации, его безопасно открывать на веб-странице, в мобильном приложении, CLI и исходном коде. Без вошедшего пользователя он соответствует ролиanon, а возможности ограничивает RLS. Только этот ключ должен быть в браузерном коде. - Secret-ключ (начинается с
sb_secret_): только для серверных компонентов, например серверов, Edge Functions и микросервисов. Он соответствует ролиservice_roleи, по документации, обходит все политики RLS. Никогда не кладите его в браузер, распространяемое приложение или систему контроля версий, даже на localhost. - Устаревшие ключи: прежний ключ
anonэто устаревшая версия publishable-ключа, аservice_roleустаревшая версия secret-ключа. Обе системы работают параллельно; по документации, устаревшие ключи выводятся из обращения до конца 2026 года, и рекомендуется перейти на новые. - Храните secret-ключ как серверный секрет. Не держите его в системе контроля версий и скриптах CI, используйте хранилище секретов вашего инструмента и работайте с ним только на машинах, которые вы контролируете. Подробности: открытые .env и .git и безопасный деплой приложения на VPS.
- Не полагайтесь на блокировку в браузере. Supabase отвечает на secret-ключ из браузера кодом HTTP 401, но, как сказано в документации, злоумышленник всё равно может использовать утёкший ключ из других инструментов.
Если secret-ключ утёк, сначала устраните причину утечки, затем меняйте ключ. Порядок по документации: создайте новый secret-ключ, замените старый везде, убедитесь, что каждый компонент использует новый, и только потом удалите старый, потому что удаление необратимо. Устаревший ключ сначала деактивируйте: это обратимо, если вы забыли какой-то клиент. Полный порядок действий: что делать, если ключ API утёк.
Шаг 4: проверьте доступ как посетитель и как другой пользователь
Не доверяйте политике, которую вы не пытались сломать. Проверяйте снаружи, имея только публичный ключ. В quickstart Supabase показан формат запроса: имя таблицы после REST-адреса, ключ в заголовке apikey.
curl 'https://YOUR_PROJECT_REF.supabase.co/rest/v1/notes?select=*' -H "apikey: YOUR_PUBLISHABLE_KEY"
Токен пользователя не передаётся, так что это неавторизованный посетитель. Подставьте идентификатор проекта и publishable-ключ. Безопасный результат:
- Безопасно: пустой список
[]для приватных таблиц или ошибка прав, например42501. И то и другое значит, что посторонний ничего не получает. - Небезопасно: любые JSON-строки из таблицы с данными пользователей. Включите RLS, добавьте политики и повторите запрос.
Затем проверьте доступ от имени конкретного пользователя. Проще всего тест двух пользователей из чек-листа вайб-кодинга: войдите в приложение как пользователь B и попробуйте открыть заметку пользователя A по её ID. Запрос можно воспроизвести и в SQL. В документации идентичность подменяют через set role и JWT claims, а транзакция ниже всё откатывает в конце (подставьте два настоящих ID пользователей вашего проекта):
begin;
set local role authenticated;
set local request.jwt.claims to '{"role":"authenticated","sub":"USER_A_UUID"}';
select * from public.notes; -- ожидаем только строки пользователя A
insert into public.notes (user_id, body)
values ('USER_B_UUID', 'test'); -- ожидаем ошибку row-level security
rollback;
Для повторяемых проверок в документации описаны тесты pgTAP, которые запускаются командой supabase test db.
Ловушка SQL-редактора. Не проверяйте политику обычным select в SQL-редакторе. В документации объяснено: владелец таблицы и роли с BYPASSRLS политикам не подчиняются, поэтому тот же запрос в SQL-редакторе возвращает строки, а API ничего не возвращает. Запрос, который работает там, ничего не доказывает про то, что видят ваши пользователи. Сначала переключите роль, как выше, или проверяйте через API.
Нужен второй взгляд на ваш стек?
Бесплатная проверка в режиме только чтения покажет, что открыто на сервере, где работает приложение, и что исправлять в первую очередь.
Получить бесплатную проверку сервера Связаться с инженером по безопасностиШаг 5: проверьте представления и функции, обходящие RLS
RLS защищает таблицы. Обойти его можно двумя способами.
- Представления (views). По документации, представления по умолчанию обходят RLS, потому что их обычно создаёт пользователь
postgres. В Postgres 15 и новее создавайте представление сsecurity_invoker = true, и оно будет подчиняться политикам исходных таблиц. В более старых версиях отзовите уanonиauthenticatedдоступ к представлению или держите его в закрытой схеме.create view public.my_notes with (security_invoker = true) as select id, body from public.notes; - Функции. В документации сказано, что RLS к функциям не применяется, поэтому выдавайте
executeтолько тем ролям, которым это нужно. Функциюsecurity definer(она выполняется с правами владельца) проверяйте особенно внимательно, фиксируйте еёsearch_path, указывайте схему в именах и никогда не создавайте её в схеме, открытой в настройках API.
Шаг 6: закройте бакеты Storage
Supabase Storage хранит файлы в бакетах, а доступ к файлам задаётся политиками RLS на таблице storage.objects, как объясняет руководство по контролю доступа в Storage. По умолчанию загрузка в бакеты без политик не разрешена, а в основах бакетов сказано, что бакеты приватны по умолчанию.
- Читайте «публичный бакет» как «любой, у кого есть ссылка, может читать». По документации, публичный бакет фактически обходит контроль доступа при получении и выдаче файлов, и любой, у кого есть URL файла, может его прочитать. Загрузка, удаление, перемещение и копирование по-прежнему проверяются. Счета, документы и загрузки пользователей держите в приватных бакетах.
- Пишите политики по операциям. Для загрузки нужна политика
insert. Для замены файла (upsert) дополнительно нужныselectиupdate. - Ограничьте пользователя его папкой. В документации для проверки первой папки пути используется
storage.foldername(name):
Для чтения файлов нужна подходящая политикаcreate policy "Users upload to own folder" on storage.objects for insert to authenticated with check ( bucket_id = 'user-files' and (storage.foldername(name))[1] = (select auth.uid()::text) );selectс тем же условием. - Избегайте широких политик
selectна публичных бакетах. В Security Advisor есть проверка «public bucket allows listing»: клиенты тогда могут перечислить все файлы бакета.
Шаг 7: усильте Auth
RLS работает, только если можно доверять тому, кто этот пользователь. В Supabase Auth есть несколько настроек, которые стоит проверить до запуска. Они взяты из чек-листа выхода в продакшен и руководств по Auth.
- Требуйте подтверждение email. В руководстве по email-аутентификации сказано, что на размещённых проектах оно включено по умолчанию, а при локальной разработке и на self-hosted выключено, поэтому проверьте настройку там, где реально работаете.
- Используйте собственный SMTP-сервер в продакшене. Встроенная почтовая служба ограничена 2 письмами в час и работает по принципу best-effort.
- Укажите Site URL и список разрешённых redirect-адресов на настоящий домен. Руководство по redirect URL называет Site URL критически важным для писем подтверждения и сброса пароля и советует в продакшене указывать точный путь. Шаблоны вроде
**оставьте для локальной разработки и preview-хостов. - Предложите многофакторную аутентификацию там, где данные чувствительны. Supabase Auth поддерживает MFA через приложение-аутентификатор (TOTP) и SMS, а требовать её можно в политике:
(select auth.jwt()->>'aal') = 'aal2'в restrictive-политике. Включите её и для собственного аккаунта Supabase. См. двухфакторная аутентификация. - Ограничьте одноразовые коды и злоупотребления. Чек-лист рекомендует срок действия OTP не более 3600 секунд, удаление тестовых номеров и CAPTCHA для регистрации, входа и сброса пароля. Добавьте ограничение частоты запросов на собственных эндпоинтах.
Шаг 8: пусть Security Advisor найдёт пропущенное
Советники базы данных (advisors) это детерминированные проверки проекта. Их запускают со страницы Security Advisor в дашборде, командой CLI supabase db advisors, а также через Management API и MCP. Находки бывают намеренными, поэтому перед правкой сверяйте каждую с задуманной моделью доступа. На что смотреть:
- 0013 RLS disabled in public (ошибка): по документации, любой, кто знает URL проекта, может читать, менять и удалять все данные таблицы. Включите RLS.
- 0010 Security definer view (ошибка): представление игнорирует RLS. Пересоздайте его с
security_invoker=on. - 0002 Auth users exposed (ошибка): представление раскрывает персональные данные пользователей. Замените его таблицей профилей.
- 0008 RLS enabled, no policy (информация): через API нельзя ни читать, ни писать. Добавьте политику.
- 0011 Function search path mutable (предупреждение): задайте функции фиксированный search path.
- 0025 Public bucket allows listing (предупреждение): удалите широкую политику
selectили сделайте бакет приватным.
Советник не знает ваших бизнес-правил: политика using ( true ) всё равно открывает всё, поэтому шаг 4 обязателен. Если в приложении данные клиентов и безопасностью никто не занимается, попросите второе мнение; см. управляемая безопасность или DIY.
Предзапусковая проверка на одном экране
Пройдите этот список до первого настоящего пользователя и после каждого изменения схемы или Auth. Аналогичный материал для бэкенда Google: чек-лист правил безопасности Firebase, а правила для ИИ-ассистентов, которые пишут ваши миграции, в статье правила безопасности для ИИ-ассистентов программиста.
- RLS включён на каждой таблице и представлении, до которых дотягивается Data API.
- По политике на операцию, роли указаны через
to, нетusing ( true )на приватных данных; права (grants) явные и минимальные. - Во фронтенде только publishable-ключ; secret-ключ лежит в серверном секрете и никогда не коммитился.
- Запрос только с publishable-ключом не возвращает ничего приватного; тест двух пользователей пройден.
- Представления используют
security_invoker; функцииsecurity definerпроверены и не лежат в открытых схемах. - Бакеты с файлами пользователей приватные, с политиками по папкам.
- Подтверждение email включено, Site URL и список redirect верны, MFA предложена там, где данные чувствительны.
- В Security Advisor нет ошибок, которые вы не можете объяснить.
- Бэкапы базы есть, и восстановление пробовали. См. стратегию бэкапов 3-2-1.
Ещё для вайб-кодеров: все руководства серии собраны в центре безопасности для вайб-кодеров, а пункты можно отмечать в интерактивном чек-листе.
Часто задаваемые вопросы
Что такое row level security в Supabase?
Row-level security (RLS) это функция Postgres, которая привязывает к таблице правила, так что каждый запрос читает и меняет только разрешённые строки. Без RLS таблица в открытой схеме доступна на чтение и запись любой роли, у которой есть на неё права.
Безопасно ли открывать ключ anon в Supabase?
Supabase описывает publishable-ключ и прежний ключ anon как безопасные для веб-страницы и приложения, потому что возможности ограничивает RLS. Это верно только если на каждой открытой таблице включён RLS и есть корректные политики. Secret-ключ и прежний service_role открывать нельзя никогда.
Что делать, если утёк ключ service role в Supabase?
Устраните причину утечки, создайте новый secret-ключ, замените старый везде, убедитесь, что каждый компонент использует новый, и затем удалите старый. Устаревший ключ сначала деактивируйте: это обратимо, если вы забыли какой-то клиент.
Как проверить, что таблицы Supabase не читаются публично?
Отправьте запрос на REST-адрес таблицы только с publishable-ключом и посмотрите, вернутся ли строки. Затем откройте Security Advisor и найдите находку «RLS disabled in public». Пустой список или ошибка прав значит, что посторонний ничего не получает.
Почему запрос работает в SQL-редакторе, но в приложении ничего не возвращает?
SQL-редактор работает с правами, которые обходят политики: владелец таблицы и роли с BYPASSRLS им не подчиняются. Ваше приложение работает под ролью anon или authenticated и видит только то, что разрешают политики. Добавьте политику и проверяйте, переключая роль в SQL или вызывая API.
Нужен ли RLS, если приложение работает с Supabase только через мой сервер?
Если сервер подключается к Postgres напрямую и Data API не используется, Supabase советует отключить Data API. Если Data API остаётся включённым, всё равно включите RLS: всё, что доступно через него, защищено только правами и политиками.
Безопасны ли публичные бакеты Storage?
Только для файлов, предназначенных для всех. По документации Supabase, публичный бакет обходит контроль доступа при чтении, поэтому любой, у кого есть URL файла, может его скачать, а загрузка и удаление по-прежнему проверяются политиками. Документы пользователей держите в приватных бакетах с политиками по папкам.
Не уверены, в каком состоянии ваш сервер?
Получите бесплатную проверку безопасности в режиме только чтения или поговорите с инженером по безопасности об управляемой защите.
Получить бесплатную проверку сервера Связаться с инженером по безопасности