// docs / baas security / supabase storage
Чек-лист безопасности хранилища Supabase: 22 пункта
Supabase Storage — это тонкая обёртка вокруг корзины, совместимой с S3, плюс та же модель безопасности на уровне строк, что и у базы данных. Это означает, что те же подводные камни RLS, которые влияют на таблицы, влияют на доступ к файлам — и несколько специфичных для хранилища, которые проявляются, когда ИИ-инструменты кодирования настраивают загрузки. Этот чек-лист — 22 пункта в пяти разделах: конфигурация корзины, политики RLS, проверка загрузки, подписанные URL и операционная гигиена. Каждый можно проверить менее чем за 15 минут.
Каждый пункт ниже является существенным. По основным механикам RLS см. Сканер Supabase RLS. По классу раскрытия ключей, смежному с хранилищем, см. Ключ сервисной роли Supabase, раскрытый в JavaScript.
Конфигурация корзины
Начните с правильных значений по умолчанию. Неправильно настроенная корзина утекает файлы независимо от того, правильный ли ваш RLS.
- По умолчанию делайте каждую корзину приватной. В панели Supabase → Storage → Buckets установите переключатель Public bucket в выключенное положение, если у вас нет явной причины (маркетинговые ресурсы, публичные аватары без PII). Публичные корзины обходят RLS для операций чтения — любой, кто знает имя корзины, может перечислять и загружать.
- Установите жёсткое ограничение размера файла на каждой корзине. Панель → Настройки корзины → Ограничение размера файла. 50 МБ — разумное значение по умолчанию для пользовательских загрузок; намеренно повышайте для случаев использования видео / больших файлов. Без ограничения одна вредоносная загрузка может исчерпать вашу квоту хранилища или ваш месячный трафик.
- Ограничьте разрешённые MIME-типы для каждой корзины. Список разрешённых MIME-типов — явный список разрешённых, а не запрещённых.
image/jpeg,image/png,image/webpдля корзин только с изображениями. Никогда не разрешайтеtext/html,application/javascriptилиimage/svg+xmlв корзине пользовательского контента — они выполняются в браузере при подаче через подписанный URL. - Используйте одну корзину на тип контента, а не одну общую корзину. Настройки на корзину (размер, MIME-типы, политики RLS) — это уровень детализации, который у вас есть. Корзину
user-avatars, корзинуdocument-uploadsи корзинуpublic-assetsлегче заблокировать, чем одну смешанную корзину. - Проверьте конфигурацию CORS, если фронтенд загружает. Если пользователи загружают напрямую из браузера в подписанный URL, CORS корзины должен включать ваше производственное происхождение.
*приемлемо только для публичных корзин — никогда для корзин, содержащих PII пользователей.
Политики RLS на storage.objects
Supabase Storage хранит метаданные файлов в таблице storage.objects. RLS на этой таблице контролирует, кто может читать, загружать, обновлять или удалять файлы. Без RLS публичный/приватный флаг корзины — это ваша единственная защита.
- Подтвердите, что RLS включён на storage.objects.
SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects';должен вернутьtrue. Supabase включает его по умолчанию в новых проектах; убедитесь, что он не был отключён. - Напишите политику SELECT с ограничением через
auth.uid()для приватных корзин.CREATE POLICY "users_read_own_files" ON storage.objects FOR SELECT USING (auth.uid()::text = (storage.foldername(name))[1]);. Соглашение состоит в том, чтобы хранить файлы по адресу[user-id]/[filename]и использоватьstorage.foldername()для извлечения владельца из пути. - Напишите политику INSERT, которая обеспечивает то же соглашение о пути.
CREATE POLICY "users_upload_own" ON storage.objects FOR INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);. Без WITH CHECK аутентифицированный пользователь может загружать в папку другого пользователя. - Добавьте политики UPDATE и DELETE, если ваше приложение поддерживает редактирование или удаление файлов. Каждая команда нуждается в собственной политике. Пропуск DELETE означает, что аутентифицированные пользователи не могут удалять свои собственные файлы; пропуск UPDATE означает, что перезапись файлов тихо терпит неудачу.
- Протестируйте межпользовательский доступ в двух сеансах браузера. Войдите как Пользователь A, загрузите файл, скопируйте путь. Войдите как Пользователь B в другом браузере, попытайтесь получить файл через REST API. Ответ должен быть
403или404, никогда200.
-- Confirm RLS on storage.objects
SELECT rowsecurity
FROM pg_tables
WHERE schemaname = 'storage' AND tablename = 'objects';
-- SELECT policy: scope reads to the owning user's folder.
CREATE POLICY "users_read_own_files"
ON storage.objects
FOR SELECT
USING (auth.uid()::text = (storage.foldername(name))[1]);
-- INSERT policy: enforce the [user-id]/[filename] path convention.
CREATE POLICY "users_upload_own"
ON storage.objects
FOR INSERT
WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);Проверка загрузки
Проверяйте каждую загрузку на стороне сервера, даже когда у корзины есть ограничения MIME и размера. ИИ-инструменты кодирования генерируют только клиентскую проверку по умолчанию; это ничего не защищает.
- Перепроверяйте MIME-тип на стороне сервера из фактических байтов файла, а не заголовка
Content-Type. Используйте библиотеку вродеfile-type(Node) или магический байт-сниффинг. Злоумышленник может заявитьContent-Type: image/jpegдля файла, который на самом деле является полиглот HTML / JavaScript нагрузкой. - Удаляйте метаданные EXIF из загруженных изображений. EXIF может содержать GPS-координаты, серийные номера устройств и временные метки. Используйте
sharpс.withMetadata(false)илиexif-parserдля удаления перед сохранением. - Отклоняйте SVG, содержащие теги
scriptили обработчикиonload. SVG — это XML — и многие ИИ-сгенерированные приложения разрешают загрузки SVG как «просто изображение». ИспользуйтеDOMPurifyна стороне сервера или полностью отказывайтесь от загрузок SVG. - Используйте детерминированные, непредугадываемые имена файлов. Не сохраняйте исходное имя файла. Используйте UUID или хеш содержимого файла. Исходные имена файлов утекают ("
passport_scan_2024_01_15.jpg"), а предсказуемые имена позволяют перечисление.
Подписанные URL
Подписанные URL — это то, как клиенты получают доступ к приватным корзинам. Срок действия, область корзины и то, что регистрируется, имеет значение.
- По умолчанию срок действия подписанного URL — 1 час или меньше.
createSignedUrl(path, expiresIn)Supabase JS SDK принимает секунды. Никогда не используйте значения вроде31536000(один год) — URL становится постоянной полупубличной ссылкой. - Никогда не храните подписанные URL в вашей базе данных. Генерируйте свежие на стороне сервера для каждого запроса. Сохранённый подписанный URL с годовым сроком действия, который утекает через дамп базы данных, даёт долгосрочный доступ.
- Регистрируйте генерацию подписанных URL, а не только загрузки файлов. Если позже вы подозреваете компрометацию, вам нужно знать, кто сгенерировал какой URL и когда. Регистрируйте
auth.uid()+ корзина + путь объекта + временная метка. - Используйте опцию
downloadAsпри предоставлении файлов, загруженных пользователями.createSignedUrl(path, expiresIn, { download: '.jpg' })принудительно устанавливает заголовокContent-Disposition: attachment, поэтому файл загружается, а не отображается — побеждает класс выполнения HTML / SVG / HTML в PDF.
Операционная гигиена
Конфигурация хранилища со временем смещается. Эти четыре операционных пункта держат поверхность плотной.
- Проверяйте корзины ежеквартально. Панель → Storage → Buckets. Подтвердите, что состояние public/private и списки MIME-типов соответствуют ожиданиям приложения. Корзины, созданные «временно», становятся постоянными, если никто их не удаляет.
- Мониторьте операции анонимного списка. Журналы хранилища (Панель → Logs → Storage) записывают запросы
LIST. Всплеск анонимных запросов списка против приватной корзины означает, что кто-то зондирует её извне. - Установите политику хранения для эфемерных загрузок. Временные корзины (предварительный просмотр изображений, черновики загрузок) должны автоматически удаляться через 24-72 часа через запланированную функцию. Неопределённое хранение — это ответственность согласно обязательствам по минимизации данных GDPR / CCPA.
- Запускайте сканирование FixVibe ежемесячно. Проверка
baas.supabase-storage-publicзондирует корзины, которые отвечают на анонимныеGET+LIST. Новые корзины добавляются; старые меняют видимость — только непрерывное сканирование ловит смещение.
Следующие шаги
Запустите сканирование FixVibe против вашего производственного URL — анонимные списки хранилища отображаются под baas.supabase-storage-public. Сочетайте этот чек-лист со сканером Supabase RLS для уровня таблиц и ключом сервисной роли Supabase, раскрытым в JavaScript для смежности раскрытия ключей. Для неправильных настроек хранилища у других поставщиков BaaS см. Сканер неправильных настроек BaaS.
