Основи безпеки
Ваш бакет Supabase Storage публічний. Чи це проблема?
Публічний бакет Supabase Storage означає, що той, хто має URL файлу, може його відкрити. Він не означає, що хтось може переглянути список вмісту.

Коротко
- Публічний бакет Supabase Storage означає одну річ: той, хто має URL файлу, відкриє його без входу в систему. Про те, чи побачить він решту вмісту, це не каже нічого.
- Перелічення дозволяє політика доступу, а не перемикач публічності, тож приватний бакет може бути перелічуваним, а публічний ні.
- Саме перелічення варто виправити сьогодні, бо воно позбавляє незнайомця клопоту вгадувати бодай одну назву файлу.
Хтось відкрив ваш застосунок, хвилину в ньому покопирсався й надіслав вам повідомлення: ваш бакет Supabase Storage публічний, і будь-хто може переглянути список того, що в ньому лежить.
Це два окремі твердження. Одне з них, найпевніше, стосується налаштування, яке ви обрали навмисно й маєте лишити. Друге варте цілого пообіддя.
Ось що гайд за гайдом подає неправильно: публічність і перелічуваність це два різні перемикачі, і тривожний із них не той, який усі радять перемкнути. Зробити бакет приватним закриває перший. Другий це може лишити навстіж відчиненим, а бакет, який ніколи в житті не був публічним, цілком може бути перелічуваним сьогодні.
Чи є публічний бакет Supabase Storage проблемою безпеки?
Сам собою ні. Він означає одну цілком конкретну річ, і дуже часто саме ту, якої ви й хотіли.
Позначити бакет публічним означає дати кожному файлу в ньому URL, що працює без входу в систему. У цьому вся функція. Так завантажується аватар під коментарем для людини без облікового запису, так з'являється ваш логотип у листі, так бачить фото товару той, хто ще вирішує, чи реєструватися.
Публічний бакет ближчий до телефонного номера поза довідником, ніж до незамкнених дверей. Лінія з'єднає будь-кого, хто набере номер, і немає довідника, де його знайти. Чи це нормально, залежить від одного питання: наскільки легко хтось дійде до цього номера, якщо йому його не називали.
Supabase створює нові бакети приватними й ставить попередження біля перемикача. Тож публічний бакет це те, що хтось увімкнув: ви або ваш білдер, аби запрацювала функція завантаження файлів.
Що насправді вмикає перемикач публічності
Один рядок таблиці нижче, і більше нічого в ній.
| Що хтось намагається зробити | Публічний бакет | Приватний бакет |
|---|---|---|
| Відкрити файл, точний URL якого вже має | Спрацює, без входу в систему | Потрібна політика або підписане посилання |
| Попросити список вмісту бакета | Лише якщо дозволяє політика | Лише якщо дозволяє політика |
| Завантажити файл | Лише якщо дозволяє політика | Лише якщо дозволяє політика |
| Видалити чи замінити файл | Лише якщо дозволяє політика | Лише якщо дозволяє політика |
Власна довідкова сторінка Supabase каже це так ясно, як це взагалі можна сказати: публічний бакет означає, що існує публічний URL, за яким можна завантажити файл, а всі інші дії й далі мусять задовольняти політики цього бакета.
Тобто перемикач це менший важіль, ніж підказує його назва. Три з тих чотирьох рядків вирішуються геть в іншому місці, у Storage → Policies, і повідомлення, яке ви отримали, майже напевно стосувалося другого.
Чому приватний бакет усе одно може бути перелічуваним
Бо перелічення дозволяє політика доступу, а політика, яку вставляє більшість, дозволяє його всім.
Кожне читання зі Storage проходить через Row Level Security на таблиці з назвою
storage.objects. Перелічити бакет це читання, віднесене до SELECT, точно так
само як і завантажити файл. Власний швидкий старт Supabase показує політику саме
такої форми, звідки зазвичай і беруться копії:
create policy "Public Access"
on storage.objects for select
using ( bucket_id = 'public' );
Прочитайте, що там написано. Будь-який запит може читати будь-що в цьому бакеті. Він не питає, хто питає, і не питає, чи має файл узагалі стосунок до того, хто його просить. Читання охоплює завантаження файлу, і охоплює видачу списку файлів, бо це один і той самий дозвіл у двох капелюхах.
Перемикач самого бакета сюди ніколи не втручається. Така політика на приватному бакеті робить його перелічуваним, а панель і далі цілком правдиво називатиме його приватним.
Слово, яке чинить шкоду, це «будь-який». Запит із публічним ключем, що їде всередині коду вашого застосунку, задовольняє цю політику без жодних зусиль, і цей ключ і має бути доступним для читання всім, що якраз і є причиною, чому працювати мала б політика. Це та сама форма, що й у сусідньої проблеми, де Row Level Security увімкнено, і вона все одно все пропускає.
Що дає комусь список ваших файлів
Насамперед назви. А це значно більше, ніж звучить.
Назви файлів зазвичай описують вміст, бо людина обирала їх, думаючи про те, що
всередині: rahunok-berezen-acme.pdf, pasport-perednia.jpg,
zarplata-final-v2.xlsx. Список таких назв це непоганий переказ вашого бізнесу, а
його довжина приблизно каже, скільки у вас клієнтів. Якщо бакет ще й публічний,
кожна назва в тому списку є робочим посиланням.
Повернімося до телефонного номера. Бути поза довідником чогось варте рівно доти, доки довідник не вийшов друком, а після того вже ніколи не мало значення, наскільки важко було вгадати номер.
Виявляється це щоразу буденно. Документ, що належить одному клієнту, опиняється перед іншим. Хтось називає вам назву файлу, якої не мав звідки знати. Теку із завантаженнями копіюють цілком автоматичним сканером, що читав усі проєкти Supabase, які міг знайти.
Наше безкоштовне сканування просить у вашого Storage перелік, використовуючи лише той ключ, що вже є в коді вашого застосунку, і каже, які бакети відповіли вмістом. Воно читає назви й ніколи не завантажує файл. Триває близько 20 секунд і не потребує облікового запису: просканувати застосунок.
Як зрозуміти, який із двох випадків у вас
Дві перевірки в панелі Supabase, і друга стосується того, про що повідомлення йшлося насправді.
Storage → Buckets. Публічні позначені як публічні. Про кожен запитайте себе, чи справді кожен окремий файл у ньому це те, що ви показали б незнайомцю. Не більшість файлів. Кожен, включно з тим, що потрапить туди наступного тижня з функції, якої ви ще не збудували.
Storage → Policies. Прочитайте кожну політику SELECT, що стосується бакета.
Умова, яка згадує лише назву бакета, дозволяє читати його будь-кому. Умова, що
порівнює власника файлу з тим, хто робить запит, виконує справжню роботу.
Порожній список політик на приватному бакеті означає, що не читається взагалі
нічого, і це суворо й безпечно.
Якщо ваш застосунок мусить показати приватний файл потрібній людині, інструментом для цього є підписане посилання: ваш сервер просить у Supabase URL, який працює задану кількість хвилин, а потім перестає. Так файл лишається приватним і все одно потрапляє на сторінку.
Коли публічність і є правильною відповіддю
Частіше, ніж зазвичай визнає стаття про безпеку, і це варто сказати.
Якщо файл призначений для всіх, публічність правильна, а обхідний шлях купує вам повільніший застосунок і більше коду для підтримки. Аватари, логотипи, обкладинки, усе, що має бачити відвідувач без входу в систему: у публічний бакет, і більше про це не думайте.
Єдине, що варто зробити навіть там, це лишити перелічення вимкненим. Публічний бакет з аватарами це функція. Той самий бакет, що видає повний список усіх, хто бодай колись завантажив аватар, це вже інша річ, і ви її не замовляли.
Якщо файл належить одній конкретній людині, він іде в приватний бакет із політикою, яка перевіряє, хто питає, і потрапляє на сторінку через підписане посилання. Перевірка зводиться до одного питання: для кого цей файл? Для всіх чи для одного названого облікового запису.
Що зробити цього тижня
Що робити
- Відкрийте Storage → Buckets і випишіть, які з них публічні. Про кожен вирішіть, чи тримає він файли для всіх, чи файли для однієї людини.
- Прочитайте кожну політику
SELECTнаstorage.objects. Умова, що називає лише бакет, видає список файлів будь-кому; саме вона зробила правдивим отримане вами повідомлення. - Перенесіть усе, що належить одному користувачеві, у приватний бакет і віддавайте через підписані посилання, які спливають самі.
- Перестаньте називати завантажені файли за їхнім вмістом. Випадковий ідентифікатор нічого не коштує й робить так, що витеклий список видає набагато менше, ніж видав би.
- Робіть резервну копію Storage окремо від бази даних. Ваші завантаження не лежать ні в коді, ні в таблицях Postgres, тож ніщо з того, що копіює ці двоє, не копіює ваші файли.
Відкрийте Storage → Policies у своєму проєкті й прочитайте, що там є, перш ніж чіпати бодай один перемикач. Саме той екран вирішує, чи була правдивою друга половина отриманого вами повідомлення. Десятихвилинний чекліст безпеки охоплює це поряд з рештою того, що щойно запущений застосунок зазвичай лишає відкритим, а якщо бакет таки виявився доступним для читання, то що незнайомець дістає у вашій базі даних буде наступним для читання, адже політика, яка видає список файлів, має ту саму форму, що й та, яка видає таблицю.
Поширені запитання
Мені сказали, що мій бакет Supabase Storage публічний. Чи робити його приватним?
Не раніше, ніж ви дізнаєтеся, що в ньому лежить. Публічні бакети існують не просто так: фото профілів, логотипи, зображення товарів і все інше, що ваш застосунок показує відвідувачу без входу в систему. Для таких файлів публічність і є правильним налаштуванням. Для рахунків, документів, експортів чи будь-чого, що належить одній конкретній людині, вона неправильна, і тоді вам потрібен приватний бакет плюс підписані посилання. Питання ніколи не стояло так, чи публічність погана. Воно стоїть так: чи саме ці файли призначалися для будь-кого, хто попросить.
Яка різниця між публічним бакетом і перелічуваним?
Публічність вирішує, чи відкриється файл тому, хто вже має його URL. Перелічуваність вирішує, чи може незнайомець попросити у вашого проєкту весь вміст бакета й отримати відповідь. Ці дві речі налаштовуються в різних місцях панелі Supabase, і саме друга перетворює здогад на покажчик.
Чи може хтось вгадати URL файлів у моєму публічному бакеті?
Це цілком залежить від того, як їх називає ваш застосунок. Якщо він завантажує їх під первісною назвою або під чимось охайним на кшталт rahunok-4.pdf, то так, і зусиль на це майже не треба. Якщо він завантажує їх під довгим випадковим ідентифікатором, вгадування стає непрактичним. Саме тому перелічення важить так багато: воно прибирає крок вгадування повністю.
Як завадити перелічувати мій бакет?
Перелічення проходить через політику Row Level Security на таблиці storage.objects, тож виправляють його саме там, а не на самому бакеті. Відкрийте Storage → Policies у панелі Supabase і прочитайте кожну політику SELECT, що стосується бакета. Та, єдина умова якої це назва бакета, дозволяє будь-якому запиту читати цей бакет разом зі списком файлів. Звузьте її так, щоб вона порівнювала файл із тим, хто його просить, або приберіть її й видавайте підписані посилання зі свого сервера.
Чи охоплює резервна копія бази даних завантажені файли?
Ні. Storage живе поза вашою базою Postgres, тож копія бази містить рядки, що вказують на ваші файли, і жодного з самих файлів. Відновлення дасть вам таблицю, повну посилань на те, чого вже немає. Завантажені файли треба копіювати окремо, і більшість людей дізнається про це того дня, коли це має значення.