// docs / baas security / supabase rls scanner
Сканер Supabase RLS: найдите таблицы с отсутствующей или сломанной безопасностью на уровне строк
Безопасность на уровне строк (RLS) — это единственное, что стоит между данными ваших клиентов и интернетом, когда вы выпускаете приложение на основе Supabase. ИИ-инструменты кодирования генерируют код в форме RLS, который компилируется, выпускается и тихо сливает данные — таблицы, созданные без включения RLS, политики, которые читают, но никогда не ограничивают, предикаты, которые сравнивают столбец с самим собой. Эта статья показывает, что сканер Supabase RLS может доказать извне, четыре формы сломанного RLS, которые появляются в vibe-кодированных приложениях, и как сканировать ваше собственное развертывание менее чем за минуту.
Что может доказать внешнее сканирование RLS
Пассивное сканирование RLS выполняется против конечной точки PostgREST, которую Supabase предоставляет по адресу https://[project].supabase.co/rest/v1/. Оно использует только публикуемый ключ anon — тот же ключ, который использует ваш браузер — и зондирует метаданные списка таблиц, анонимные чтения и анонимные записи. Оно никогда не аутентифицируется как пользователь и никогда не использует привилегии сервисной роли. Всё, что оно может делать, может делать неавторизованный злоумышленник в интернете.
Извне базы данных сканер может подтвердить следующее с высокой уверенностью:
- RLS отключён на таблице. PostgREST возвращает строки для анонимного
SELECT, когда RLS отключён или когда политика это разрешает. Любой случай — это находка. - Анонимная роль может перечислять таблицы.
GET /rest/v1/с ключом anon возвращает схему OpenAPI для каждой таблицы, к которой у ролиanonесть любые привилегии. ИИ-сгенерированные приложения часто предоставляютUSAGEдля схемы иSELECTдля каждой таблицы, что обнажает полную карту схемы, даже когда RLS отклоняет фактические чтения. - Анонимная роль может вставлять. Зондирующий
POSTс предположением о форме столбцов будет успешен, если у RLS нет политикиINSERT, отклоняющей его — даже еслиSELECTзаблокирован. - Ключ сервисной роли находится в браузерном бандле. Смежно с RLS: если сканер находит
SUPABASE_SERVICE_ROLE_KEYили любой JWT сrole: service_roleв JavaScript-бандле, RLS становится бесполезным — владелец этого ключа обходит каждую политику.
Что внешнее сканирование не может доказать
Будьте честны относительно границ сканера. Внешнее сканирование RLS не может прочитать вашу таблицу pg_policies, ваши файлы миграции или точный предикат какой-либо политики. Оно делает выводы из поведения чёрного ящика, что означает, что иногда оно сообщит о находке, которая окажется намеренно публичными данными (таблица маркетинговой рассылки, публичный каталог продуктов). Отчёт FixVibe помечает их как средний уровень доверия, когда сканер не может различить намерения — пересмотрите имя таблицы и примите решение.
Четыре формы сломанного RLS, которые производят ИИ-инструменты
Когда вы направляете Cursor, Claude Code, Lovable или Bolt на Supabase, в тысячах приложений возникают одни и те же четыре шаблона сломанного RLS. Каждый проходит проверку типов, компилируется и выпускается:
Форма 1: RLS никогда не включался
Наиболее распространённый режим отказа. Миграция создаёт таблицу, но разработчик (или ИИ-инструмент) забывает ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST с радостью обслуживает всю таблицу любому, у кого есть ключ anon. Исправление: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;. FORCE не является необязательным — без него владелец таблицы (и любая роль с владением таблицей) обходит RLS.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;Форма 2: RLS включён, нет политик
Более тонкий отказ. RLS включён, но политики не написаны. По умолчанию в PostgreSQL отклонять, поэтому аутентифицированные пользователи ничего не видят — и разработчик добавляет USING (true), чтобы приложение работало, что разрешает всем читать всё. Исправление: напишите политику с ограничением через auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); и соответствующую политику INSERT/UPDATE/DELETE.
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);Форма 3: Политика сравнивает столбец с самим собой
A copy-paste artefact. The developer writes <code>USING (user_id = user_id)</code> — which is always true — instead of <code>USING (auth.uid() = user_id)</code>. Type-checks pass; the policy permits every row. <strong>Fix:</strong> always compare a column to a function call (<code>auth.uid()</code>, <code>auth.jwt()->>'org_id'</code>, etc.), never to itself or to a constant.
Форма 4: Политика на SELECT, но не на INSERT/UPDATE
Разработчик блокирует чтения, но забывает записи. Политики RLS работают по командам. FOR SELECT защищает только чтения; анонимный клиент по-прежнему может INSERT, если ни одна политика этого не отклоняет. Исправление: создайте политику для каждой команды или используйте FOR ALL с явными предложениями USING и WITH CHECK.
Как работает сканер FixVibe Supabase RLS
Проверка baas.supabase-rls выполняется в три этапа, каждый с явными уровнями доверия:
- Этап 1 — снятие отпечатков. Сканер обходит развёрнутое приложение, разбирает его JavaScript-бандл и извлекает URL проекта Supabase и ключ anon из конфигурации времени выполнения. Никаких догадок DNS, никакого перебора — он читает то, что читает браузер.
- Этап 2 — обнаружение схемы. Один
GET /rest/v1/с ключом anon возвращает схему OpenAPI для каждой таблицы, которую может видеть роль anon. Сканер записывает имена таблиц, но не читает данные строк на этом этапе. - Этап 3 — зонды чтения и записи. Для каждой обнаруженной таблицы сканер выдаёт один анонимный
SELECTсlimit=1. Если возвращаются строки, RLS разрешает. Сканер останавливается там — он не перечисляет строки, не разбивает на страницы, не изменяет данные. Зонды INSERT защищены подтверждённым владением доменом и явным согласием; они никогда не запускаются против непроверенных целей.
Каждая находка поставляется с точным URL запроса, статусом ответа, формой ответа (только заголовки) и именем таблицы. Подсказка исправления ИИ внизу находки — это копируемый блок SQL, который вы запускаете в редакторе SQL Supabase.
Что делать, когда сканер что-то находит
Каждая находка RLS — это аварийная ситуация времени выполнения. Публичные конечные точки PostgREST сканируются злоумышленниками за минуты. Последовательность исправления механическая:
- Проверьте каждую таблицу. Запустите
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';в редакторе SQL Supabase. Любая строка сrowsecurity = false— это проблема. - Включите RLS на каждой публичной таблице. По умолчанию
ENABLE ROW LEVEL SECURITYиFORCE ROW LEVEL SECURITYна каждой создаваемой таблице — сделайте это шаблоном миграции. - Создавайте политики команда за командой. Не используйте
FOR ALL USING (true). Пишите явные политики для SELECT, INSERT, UPDATE, DELETE — каждая с ограничением черезauth.uid()или столбец org-id изauth.jwt(). - Проверьте с другим аккаунтом. Зарегистрируйтесь как другой пользователь, попытайтесь прочитать записи другого пользователя напрямую через REST API. Если ответ
200, политика сломана. - Повторное сканирование. После применения исправления повторно запустите сканирование FixVibe против того же URL. Находка
baas.supabase-rlsдолжна очиститься.
-- Audit every table for missing RLS. Run in the Supabase SQL editor.
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY rowsecurity, tablename;Как это сравнивается с другими сканерами
Большинство общих инструментов DAST (Burp Suite, OWASP ZAP, Nessus) не знают, что такое PostgREST. Они обойдут ваше приложение, проигнорируют путь /rest/v1/ и сообщат о HTML-страницах, которые они понимают. Snyk и Semgrep — это инструменты статического анализа — они находят файлы миграции в вашем репозитории с отсутствующими вызовами RLS, но не могут доказать, что развёрнутая база данных неправильно настроена. FixVibe заполняет эту нишу: пассивный, осведомлённый о BaaS, ориентированный на то, что неавторизованный злоумышленник может доказать с публичного URL.
Часто задаваемые вопросы
Будет ли сканер читать или изменять мои данные?
Нет. Пассивные сканирования выдают не более одного SELECT ... limit=1 на обнаруженную таблицу, чтобы подтвердить, разрешает ли RLS анонимные чтения. Сканер записывает форму ответа, а не содержимое строк. Зонды INSERT, UPDATE и DELETE защищены подтверждённым владением доменом и никогда не запускаются против непроверенных целей.
Работает ли это, если мой проект Supabase приостановлен или на пользовательском домене?
Приостановленные проекты возвращают 503 на каждый запрос — сканер сообщает, что проект недоступен. Пользовательские домены работают, пока развёрнутое приложение всё ещё загружает Supabase Client SDK в браузере; сканер извлекает URL проекта из бандла в любом случае.
Что, если мой ключ anon ротирован или мой публикуемый ключ изменён?
Повторно запустите сканирование. Сканер повторно извлекает ключ из текущего бандла при каждом запуске. Ротация делает недействительным только предыдущий отчёт, а не состояние политики базы данных.
Проверяет ли сканер новую модель публикуемых ключей Supabase (<code>sb_publishable_*</code>)?
Да. Детектор распознаёт как устаревшие JWT anon, так и новые ключи sb_publishable_* и обращается с ними одинаково — оба предназначены для публичного использования и оба оставляют RLS как единственную линию обороны.
Следующие шаги
Запустите бесплатное сканирование FixVibe против вашего производственного URL — проверка baas.supabase-rls включена на каждом тарифе, включая бесплатный. Для более глубокого изучения того, что ещё может утечь из проекта Supabase, см. Ключ сервисной роли Supabase, раскрытый в JavaScript и Чек-лист безопасности хранилища Supabase. Для общего обзора всех поставщиков BaaS прочитайте Сканер неправильных настроек BaaS.
