Перейти до вмісту

Основи безпеки

Supabase "Bucket not found": три речі, які це може означати

Supabase "Bucket not found" означає одне з трьох: публічне посилання на приватний бакет, неточну назву або не той проєкт.

Vlad Tkachenko9 хв читання
Полиця з бакетами, один замкнений на замок. Попереду бурштинова бирка з адресою, чия пунктирна лінія впирається в засув перед ним.

Коротко

  • Supabase "Bucket not found" означає, що Storage шукав бакет саме з такою назвою, у проєкті, до якого дійшов ваш запит, і не знайшов жодного, який погодився б віддати цьому запиту. Сам бакет при цьому може спокійно стояти у вашій панелі.
  • Посилання, створене через getPublicUrl для приватного бакета, дає цю помилку, коли його відкривають, бо Storage віддає такі посилання тільки з публічних бакетів.
  • Під час вивантаження це означає, що такої назви в цьому проєкті немає. Перевірте написання й великі літери, а потім те, на який проєкт дивиться ваш застосунок.
  • Публічний бакет прибирає повідомлення і дає змогу будь-кому, хто має адресу файлу, цей файл відкрити. Для файлів, що належать одній людині, використовуйте підписане посилання.

Ваш застосунок показує зламану картинку там, де мало бути фото. Або вивантаження падає, і повідомлення, яке повертається, складається з трьох слів: "Bucket not found". Тоді ви відкриваєте Supabase, ідете в Storage, а бакет стоїть просто там, у списку.

Відкрийте адресу картинки в окремій вкладці, і Storage відповість ось так:

{
  "statusCode": "404",
  "code": "NoSuchBucket",
  "error": "Bucket not found",
  "message": "Bucket not found"
}

Це тіло, яке сьогодні будує власний вихідний код Supabase Storage, прочитаний 17 жовтня 2026 року. Supabase "Bucket not found" покриває цим одним тілом три різні ситуації, і в першій із них бакет лежить точно там, де ви його залишили.

Ось що посібник за посібником подає хибно: перше виправлення, яке ви знайдете, це зробити бакет публічним, і коли воно спрацьовує, то тому, що кожен файл у цьому бакеті теж стає публічним. Для двох із трьох причин воно не змінює взагалі нічого. Для тієї, що лишається, воно міняє зламану картинку на бакет, з якого може читати будь-хто з адресою файлу.

Що означає Supabase "Bucket not found"?

Storage шукав бакет саме з тією назвою, що у вашому запиті, у проєкті, до якого дійшов ваш запит, і не знайшов жодного, який погодився б віддати цьому запиту.

Остання частина там тому, що в бакета більше ніж одні двері, і кожні двері ставлять питання по-своєму. Вихідний код Storage показує троє з них:

  • Публічна адреса файлу, та, яку створює getPublicUrl. Ці двері враховують тільки бакети, позначені як публічні, тож приватний бакет тут "не знайдено", хоч він і існує.
  • Вивантаження. Ці двері враховують кожен бакет у проєкті, приватні теж. "Не знайдено" тут означає, що такої назви в цьому проєкті справді немає.
  • Запит на список файлів бакета. Ці двері ніколи не кажуть "Bucket not found". Бакет, якого не існує, відповідає порожнім списком, тією самою відповіддю, що й порожній бакет, і ми бачимо це ззовні щоразу, коли питає наш сканер.

Тож перше завдання це з'ясувати, через які двері зайшов запит, що впав.

Два числа в цьому повідомленні суперечать одне одному, і це плутає обробку помилок. Тіло каже "statusCode": "404", тоді як сам запит падає з HTTP-статусом 400. Storage надсилає цю помилку з кодом 400 і тримає свій власний 404 усередині тіла. Бібліотека supabase-js передає обидва у ваш код: error.status дорівнює 400, а error.statusCode дорівнює "404". Код, який ваш білдер написав, щоб ловити 404, цю помилку пропустить.

Причина 1: публічне посилання на приватний бакет

Саме ця причина стоїть за зламаними картинками. Адресу збудували для публічного бакета, а бакет приватний.

getPublicUrl нічого не питає в Supabase. Він складає адресу вашого проєкту, назву бакета й назву файлу в посилання, усередині вашого застосунку, і віддає його вам. Примітка над цією функцією у власній клієнтській бібліотеці Supabase каже це двома реченнями:

Ця функція не перевіряє, чи бакет публічний. Якщо публічну URL-адресу створено для бакета, який не є публічним, ви не зможете завантажити файл.

Пізніше браузер відвідувача просить це посилання, і Storage шукає бакет тільки серед публічних бакетів. Приватний бакет не проходить цей пошук так само, як відсутній, і відповідь звучить "Bucket not found".

Цю причину можна впізнати з самого посилання. Публічна адреса файлу містить /storage/v1/object/public/, а за ним назву бакета. Якщо цей бакет не має позначки публічного в Storage → Buckets, посилання не відкрилося б ніколи, хоч як пиши назву.

Нові бакети стартують приватними, тож налаштування поводиться саме так, як задумано. Сталося те, що код, ваш або вашого білдера, написали для публічного бакета і спрямували на приватний.

Один приватний бакет, два способи попросити файл. Публічну адресу розвертають біля дверей, а підписане посилання пропускають до того самого файлу.

Причина 2: назва, яка не зовсім збігається з назвою бакета

Storage порівнює назву бакета символ за символом, тож Avatars і avatars це два різні бакети, так само як avatar і avatars.

У вихідному коді Storage пошук це звичайна рівність за id бакета. Великі літери мають значення, пропущена літера має значення, і дефіс там, де в бакеті підкреслення, теж має значення. Кожен із цих випадків повертається тими самими трьома словами.

Застосунок, збудований за допомогою ШІ-інструмента, може опинитися тут без жодної помилки у введенні. Код, який вивантажує чи показує файл, пишуть на одному кроці, а бакет, який він називає, мають створити на іншому. Якщо цей другий крок так і не відбувся, або відбувся з трохи іншою назвою, код просить бакет, якого ніхто не створив.

Для перевірки потрібні два списки. Відкрийте Storage → Buckets і запишіть назву кожного бакета точно так, як вона там написана. Потім скажіть своєму білдеру простими словами: перелічи кожну назву бакета Storage, яку використовує цей застосунок, і покажи, де використовується кожна. Будь-яка назва з його списку, якої немає у вашому, і є та, що падає.

Причина 3: бакет лежить в іншому проєкті

Ваш застосунок говорить рівно з одним проєктом Supabase, тим, що в його адресі Supabase, і бакета, створеного в будь-якому іншому проєкті, для вашого застосунку просто не існує.

Найчіткіше ця причина видна під час вивантаження. Під час вивантаження Storage шукає бакет у всьому проєкті, не застосовуючи ваших правил доступу, тож "Bucket not found" при вивантаженні ніколи не є проблемою дозволів. Коли бакет існує, а ваші правила відхиляють файл, Storage відповідає іншим повідомленням, про row-level security. Отже, "Bucket not found" при вивантаженні означає, що такої назви немає в проєкті, куди пише ваш застосунок.

Є цілком звичайні шляхи до двох проєктів: тестовий проєкт поруч із робочим, копія застосунку, підключена до свіжого проєкту, проєкт, створений вручну, щоб щось спробувати. Бакет створюється в тому, який на ту мить був відкритий у панелі.

Перевірка це порівняння двох коротких рядків. Адреса Supabase вашого застосунку має вигляд https://<ref>.supabase.co, і частина перед .supabase.co це референс проєкту. Адреса вашої панелі містить той самий референс після /project/. Якщо вони різні, ви дивитеся на один проєкт, поки ваш застосунок говорить з іншим.

Бакет існує. Його створили в проєкті праворуч, а кожен запит вашого застосунку йде до того, що ліворуч.

Чи варто робити бакет публічним, щоб це виправити?

Тільки якщо кожен файл у бакеті призначений будь-кому, хто має його адресу, і тільки для першої причини. Для неправильної назви чи не того проєкту перемикач не змінює нічого.

Публічний бакет робить одну річ, і документація Supabase описує її одним рядком: будь-хто, хто має адресу файлу, може його відкрити. Вхід тут ніяк не задіяний. Власні приклади Supabase це аватари, публічні медіа та вміст дописів у блозі, і логотипи чи фото товарів належать до тієї ж групи.

Для рахунків, сканів документів, експортів чи будь-чого, що належить одній людині, це хибне налаштування. Перемикач діє на весь бакет і на кожен файл, що потрапить туди пізніше, зокрема на файли з функції, яку ви ще не збудували.

Той самий короткий шлях має двійника на боці правил, і саме його ми вимірюємо. Правило на storage.objects, яке називає тільки бакет, змушує завантаження працювати для всіх і заразом віддає список назв у бакеті. Ми просканували 8 435 живих застосунків, що називають проєкт Supabase. Перевірка бакетів отримала відповідь від 4 703 з них, і 792 відповіли на наш запит списку назвами того, що лежало всередині, без жодного входу. Це приблизно кожен шостий із застосунків, які ми змогли запитати. Коли бакет публічний і його список теж відкритий, кожна назва в цьому списку це робоче посилання.

Як показати приватний файл, не роблячи його публічним

Дайте сторінці підписане посилання: адресу, яка працює стільки секунд, скільки ви оберете, а потім перестає працювати.

Документація Supabase називає два способи дістати файл із приватного бакета. Перший це завантаження, яке несе сесію людини, що увійшла. Другий це createSignedUrl, який створює посилання, "до якого можна отримати доступ протягом обмеженого часу". Саме другий заступає getPublicUrl скрізь, де сторінка показує картинку.

Підписане посилання теж підкоряється вашим правилам. Перш ніж Storage щось підпише, його вихідний код перевіряє, чи має людина, яка просить, право читати цей файл, тож приватному бакету потрібне правило select на storage.objects, яке каже, хто що може читати. Помилка вивантаження пояснює, як пишуться такі правила, зокрема чому правило читання, яке називає тільки бакет, відкриває і його список.

Щоб передати це своєму білдеру, простими словами: для бакета, через який виникає помилка "Bucket not found", заміни getPublicUrl на createSignedUrl, залиш бакет приватним і додай policy для Storage, яка дозволяє кожній людині читати тільки ті файли, які їй належить бачити. Потім перезавантажте сторінку, де була зламана картинка.

Що ваші бакети показують стороннім просто зараз

Два екрани в панелі кажуть вам, що ви налаштували, а одна перевірка ззовні каже, що отримує сторонній.

Storage → Buckets показує, які бакети публічні. Для кожного публічного запитайте себе, чи кожен файл у ньому ви показали б сторонньому. Storage → Policies показує правила. Правило select, єдина умова якого це назва бакета, дозволяє будь-кому читати цей бакет, разом зі списком файлів.

Наше безкоштовне сканування просить ваш живий проєкт про список файлів кожного бакета, використовуючи тільки ключ, який уже є в коді вашого застосунку, як зробив би сторонній, і каже, які бакети відповіли назвами. Воно читає назви і ніколи не завантажує файлів. Це займає близько 20 секунд і не потребує облікового запису: проскануйте свій застосунок.

Як тримати файли там, де ви їх поклали

Після сьогоднішнього виправлення ще можуть зрушити дві речі: хтось перемкне налаштування, ганяючись за іншим багом, і буде видалено файл, який комусь був потрібен.

Reeve Monitor і далі перевіряє ззовні:

  • повна перевірка безпеки, повторювана за розкладом, разом із перевіркою списку бакетів
  • чи працює ваш застосунок
  • повідомлення, коли результат змінюється, щоб бакет, який відкрився вночі, не чекав, поки ви глянете
  • щомісячний звіт простою мовою про те, що він бачив

Reeve Care тримає копію того, що там лежить:

  • зашифрована копія вашої бази даних Supabase за розкладом, у власному сховищі Reeve в ЄС, поза вашим обліковим записом Supabase
  • кожна копія перевірена, перш ніж зарахуватися, підрахунком рядків у кожній таблиці
  • ваші вивантажені файли теж, щойно ви підключите доступ до Storage
  • відновлення в один клік, із безпечною копією поточного стану, зробленою заздалегідь
  • усе, що робить Monitor

Що зробити сьогодні

Що робити

  • Відкрийте посилання, що не працює, в окремій вкладці. Якщо адреса містить /storage/v1/object/public/, а бакет приватний, це перша причина.
  • Скопіюйте назву бакета зі Storage → Buckets і порівняйте її, з великими літерами включно, з назвою, яку використовує ваш застосунок.
  • Порівняйте референс проєкту в адресі Supabase вашого застосунку з тим, що в адресі вашої панелі.
  • Тримайте бакет приватним, якщо тільки кожен файл у ньому не призначений будь-кому з його адресою, і віддавайте приватні файли через підписані посилання.
  • Прочитайте кожне правило select на storage.objects. Те, що називає тільки бакет, віддає також список файлів.

Почніть із запиту, який упав, бо двері, якими він зайшов, кажуть вам, яку з трьох причин перевіряти. Чекліст безпеки на 10 хвилин охоплює те, що щойно запущений застосунок зазвичай лишає відкритим окрім цього, а що відкриває публічний бакет це наступне читання, якщо якийсь із ваших бакетів виявився публічним.

Поширені запитання

Чому Supabase каже, що мого бакета не знайдено, якщо я його бачу?

Бо Storage шукає бакет по-різному залежно від запиту. Публічну адресу файлу, ту, яку створює getPublicUrl, він шукає тільки серед публічних бакетів, тож приватний бакет, який ви бачите в панелі, там вважається не знайденим. Якщо ж помилка прийшла з вивантаження, назва у вашому коді не збігається точно з жодним бакетом у проєкті, до якого підключений ваш застосунок.

Чи розрізняє назва бакета в Supabase великі й малі літери?

Так. Storage порівнює назву символ за символом, тож Avatars і avatars це два різні бакети, так само як avatar і avatars. Скопіюйте назву зі Storage → Buckets у своїй панелі й порівняйте її з назвою, яку використовує ваш застосунок.

Чи робити бакет публічним, щоб виправити "Bucket not found"?

Тільки якщо кожен файл у ньому призначений будь-кому, хто має його адресу, наприклад аватари чи фото товарів. Публічний означає, що будь-хто з адресою файлу може відкрити цей файл без входу. Помилку це виправляє тільки тоді, коли публічне посилання створили для приватного бакета; неправильна назва чи не той проєкт лишаються зламаними в будь-якому разі. Для файлів, що належать одній людині, залиште бакет приватним і використовуйте підписане посилання.

Як показати файл із приватного бакета?

Через підписане посилання. createSignedUrl створює адресу, яка працює стільки секунд, скільки ви оберете, а потім перестає, і вона заступає getPublicUrl скрізь, де сторінка показує файл. Той, хто просить посилання, мусить мати право читати файл, тож бакету потрібне правило select на storage.objects, яке каже, хто що може читати.

Чому помилка каже 404, якщо запит упав із 400?

Supabase Storage надсилає цю помилку з HTTP-статусом 400 і кладе свій власний статус, 404, у тіло як statusCode. У supabase-js це приходить як error.status 400 і error.statusCode "404", і саме тому код, написаний ловити 404, цю помилку просто пропускає.

Автор

Vlad Tkachenko

Засновник Reeve

Я щодня дивлюся на застосунки, зібрані в Lovable, Bolt, v0, Cursor і Replit, і на короткий список помилок, які трапляються в них знову і знову.

Більше про автора

Читати далі

Усі статті

Зробіть резервну копію бази Supabase перед наступною зміною

Reeve за розкладом зберігає зашифровану копію і відновить її за вас, якщо щось зламається.

Резервні копії Supabase

Автоматична зовнішня перевірка, а не повний аудит. Відсутність знахідок не є гарантією безпеки.