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

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

"Edge Function returned a non-2xx status code": справжня помилка

"Edge Function returned a non-2xx status code" означає, що ваша функція Supabase відповіла помилкою. Ось де прочитати справжню.

Vlad Tkachenko10 хв читання
Повернений конверт із жовтим штампом навскіс через лицевий бік і відкритим клапаном, а всередині досі складений лист.

Коротко

  • "Edge Function returned a non-2xx status code" supabase-js каже про будь-яку відповідь вашої функції поза межами 200-х. Справжня причина прийшла в тій самій відповіді, а решта лежить у логах функції.
  • Спершу прочитайте номер статусу. 401 означає, що запит відхилили ще до запуску вашого коду, 404, що функції з такою назвою не розгорнуто, 500, що ваш код упав, 546 або 504, що забракло ресурсів чи часу.
  • Два швидкі виправлення змушують повідомлення замовкнути й відкривають діру: викликати API з фронтенду з ключем усередині та вимкнути перевірку JWT, не поставивши на її місце власної.

Ви натискаєте у своєму застосунку кнопку, що викликає Edge Function, і замість результату застосунок показує ось це:

Edge Function returned a non-2xx status code

Це те саме речення, хоч би що пішло не так. Відсутній ключ, функція, що впала, запит, відхилений ще до запуску вашого коду: усе це звучить як "Edge Function returned a non-2xx status code", тож саме повідомлення майже нічого не каже про те, що лагодити.

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

Що означає "Edge Function returned a non-2xx status code"?

Ваша функція відповіла, і відповіддю була помилка. Це все, що каже речення.

Кожна відповідь у вебі несе код статусу, тризначне число, яке каже, як пройшов запит. Усе в межах 200-х означає, що спрацювало. Коли ваш застосунок викликає функцію через supabase.functions.invoke, supabase-js дивиться на це число, і якщо воно поза межами 200-х, віддає застосунку незмінне речення замість даних. Це речення слово в слово записане у самому supabase-js, а помилка, що його несе, зветься FunctionsHttpError.

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

Двоє близьких родичів виглядають схоже, а означають інше. Failed to send a request to the Edge Function означає, що жодної читабельної відповіді взагалі не прийшло, і в браузері це часто відсутній заголовок CORS, про який розповідає допис про це повідомлення. А якщо кілька не повʼязаних між собою частин застосунку зламалися одночасно, спершу перевірте, чи справа в Supabase, чи у вашому застосунку, перш ніж відкривати будь-який код.

Відповідь несе статус і причину. supabase-js передає застосунку одне речення, а причина лишається прикріпленою до помилки та в лозі функції.

Де справжня помилка?

У двох місцях, і обидва можна прочитати, не змінивши жодного рядка коду.

Сама відповідь. Відкрийте опублікований застосунок у Chrome, Edge чи Firefox, натисніть F12, оберіть вкладку Network і повторіть те, що не спрацювало. Клацніть запит, адреса якого містить /functions/v1/ і далі назву вашої функції. У стовпці Status буде число, а на вкладці Response те, що функція надіслала разом із ним: повідомлення, яке написав ваш власний код, короткий JSON від платформи Supabase або слова Internal Server Error.

Якщо ви або інструмент, що пише ваш код, можете змінити виклик, посібник Supabase з обробки помилок показує, як прочитати ту саму відповідь усередині застосунку, бо початкова відповідь подорожує разом із помилкою як error.context:

const { data, error } = await supabase.functions.invoke('your-function', { body })
if (error) console.log(error.context.status, await error.context.text())

Журнал функції. У панелі Supabase відкрийте Edge Functions, оберіть функцію й прочитайте дві вкладки. Invocations показує кожен запит із його кодом статусу та тривалістю. Logs містить те, що посібник Supabase з логування називає подіями платформи, неперехопленими винятками та власними повідомленнями логу, тобто все, що ваш код вивів через console.log чи console.error. Відтворіть збій один раз, запамʼятайте час і прочитайте обидві вкладки за ту хвилину.

Спершу прочитайте код статусу

Число каже, як далеко зайшов ваш запит, перш ніж його повернули, і від цього залежить, де записана причина.

СтатусЩо сталосяДе причина
401Відхилено до запуску вашого коду, або його відхилив ваш кодВідповідь. Invalid JWT чи Missing authorization header означає платформу
404У цьому проєкті не розгорнуто функції з такою назвоюУ відповіді Requested function was not found
500Ваш код запустився й кинув помилку або навмисно повернув 500Вкладка Logs
503Функція взагалі не запустиласяВкладка Logs: імпорт, синтаксична помилка чи конфігурація функції
504Жодної відповіді за 150 секундВкладка Invocations, а потім те, чого чекав ваш код
546Забракло процесорного часу чи памʼятіВкладка Logs каже, чого саме
Будь-який інший 4xxЙого обрав ваш власний код, наприклад передавши відповідь якогось сервісуВідповідь

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

500: ваша функція запустилася й упала

Щось у вашому коді кинуло помилку, і вкладка Logs має цей рядок.

Сторінка Supabase про цю помилку ділить її на два випадки. Якщо у відповіді рівно Internal Server Error, ваш код упав, а лог показує тип помилки й рядок, де вона сталася, наприклад TypeError: Cannot read properties of undefined. Якщо у відповіді щось інше, ваш код сам написав це повідомлення й повернув 500 навмисно, тож пошукайте у функції статус, який вона надіслала.

Одну причину варто виключити раніше за всі інші, якщо функція викликає OpenAI, Stripe чи будь-яке платне API: секрет, який є на вашому компʼютері й відсутній в опублікованому проєкті.

Усередині функції секрет читають через Deno.env.get('OPENAI_API_KEY'). Коли секрету з такою назвою не задано, документація Deno каже, що це повертає undefined, а не падає. Тож функція йде далі, складає запит зі словом undefined там, де мав бути ключ, і надсилає його. OpenAI відповідає власним 401, "Incorrect API key provided", а ваша функція або передає його далі, або падає, читаючи відповідь, якої не очікувала. У картинці з конвертом: контора відкрила ваш лист, підійшла до шухляди, де мав лежати ключ, знайшла її порожньою й відіслала записку про неправильний ключ.

Лагодять це на сторінці Edge Function Secrets вашого проєкту в Supabase. Посібник Supabase із секретів каже, що функція читає новий секрет одразу, тож розгортати повторно не треба, і що жодна назва секрету не може починатися з SUPABASE_, бо цей префікс зарезервовано для значень, які Supabase задає за вас. Секрет, заданий для локальної розробки, лишається на вашому компʼютері; в опублікованого проєкту власний список.

401: відхилено до запуску вашого коду

Якщо у відповіді Invalid JWT чи Missing authorization header, ваша функція так і не побачила запиту. Платформа Supabase завернула його на порозі.

Цей поріг є перевіркою на імʼя verify_jwt, увімкненою за замовчуванням, яка читає заголовок Authorization кожного запиту до запуску вашого коду. Сторінка Supabase про 401 називає три способи її не пройти:

  • Ключ не збігається з ключами вашого проєкту. Наприклад, ключ, скопійований з іншого проєкту, або старий, що досі десь прописаний після ротації.
  • Заголовка Authorization немає взагалі. supabase-js додає його за вас. Простий fetch на адресу функції чи вебхук від іншого сервісу цього не роблять.
  • Ваш проєкт використовує новіші ключі підпису Supabase. Вбудована перевірка розуміє лише старий формат, і рішення Supabase полягає в тому, щоб вимкнути перевірку для цієї функції.

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

Якщо 401 прийшов із повідомленням, яке написав ваш власний код, платформа пропустила запит, а ваша функція його відхилила або передала 401 від сервісу, який викликала, і часто це той самий порожній секрет із розділу про 500. Крім того, це інша перевірка, ніж та, що стоїть за PGRST301 у ваших запитах до бази, і вона має окрему сторінку. Якщо лог функції показує invalid claim: missing sub claim, вона спитала Supabase Auth, хто викликає, і отримала ключ вашого проєкту замість користувача, що ввійшов; допис про це повідомлення розбирає це крок за кроком.

Статус каже, як далеко зайшов запит. 401 біля брами так і не дістався вашого коду; 401, повернутий сервісом, який функція викликала, дістався.

546 і 504: забракло ресурсів або часу

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

546 стосується обчислень.Сторінка з лімітами дає функції 256 МБ памʼяті, а кожному запиту 2 секунди процесорного часу. Перевищте будь-що з цього, і відповідь буде Function failed due to not having enough compute resources (please check logs), з кодом WORKER_RESOURCE_LIMIT (старі логи кажуть WORKER_LIMIT). Вкладка Logs каже CPU Time exceeded або Memory limit exceeded. Сторінка Supabase про 546 наводить як приклади обробку зображень, ембединги для ШІ та вебскрейпінг, а її порада полягає в тому, щоб робити менше за один запит або віддати важку частину іншому сервісу.

504 стосується часу. Функція, яка не надіслала відповіді за 150 секунд, отримує 504. Ліміт процесорного часу рахує лише час, коли ваш код справді обчислює: сторінка з лімітами каже, що асинхронне введення-виведення сюди не входить, а очікування мережі саме ним і є. Тож функція, яка чекає на повільну модель ШІ, процесорного часу не витрачає. Вона спалює годинник на 150 секунд, і коли той спливає, ви отримуєте 504, а не 546.

Два виправлення, що роблять гірше

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

Викликати API просто зі сторінки. Найшвидший спосіб змусити функцію, що падає, перестати падати, це перестати нею користуватися: покласти ключ у змінну з VITE_ і викликати OpenAI прямо з фронтенду. Це спрацьовує з першої спроби. А ще кладе ключ у файл, який завантажує кожен відвідувач, бо цей префікс є вказівкою вкомпілювати значення у ваш бандл, і там його може прочитати будь-хто. Серед опублікованих застосунків, які ми просканували, ключ OpenAI знайшовся в JavaScript у 33, і про те, що з ним може зробити стороння людина, є окрема стаття.

Вимкнути перевірку й на цьому зупинитися. Позбутися 401, вимкнувши verify_jwt, означає виконати першу половину поради самої Supabase. Посібник Supabase з конфігурації прямо каже, що це дає саме по собі: тоді будь-хто може викликати вашу Edge Function без дійсного JWT. Адреса вашої функції лежить у вашому фронтенді, і її може прочитати кожен. Якщо функція тримає ключ OpenAI, стороння людина тепер може витрачати його через вас. Якщо вона використовує секретний ключ проєкту, який Supabase кладе в оточення кожної функції, запит сторонньої людини працює з ключем, що обходить кожне правило row level security, яке ви написали.

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

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

Щоб ключ лишався там, де ви його поклали

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

Reeve Monitor перевіряє щогодини замість вас:

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

Функція, що працює з вашим секретним ключем, обходить кожне правило row level security. Reeve Care тримає копію вашої бази Supabase там, куди ніщо у вашому проєкті не дістане.

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

Що зробити зараз

Що робити

  • Відтворіть збій один раз і запамʼятайте час.
  • Відкрийте запит на вкладці Network у браузері й прочитайте його статус і відповідь, перш ніж щось змінювати.
  • Прочитайте вкладки Invocations і Logs функції в панелі Supabase за ту хвилину.
  • Якщо 500 повертає функція, що викликає платне API, перевірте, чи задано секрет на сторінці Edge Function Secrets опублікованого проєкту. Повторно розгортати після цього не треба.
  • Якщо 401 повертає платформа, виправте ключ або вимкніть вбудовану перевірку й у тій самій зміні додайте всередині функції перевірку того, хто викликає.
  • Ніколи не прибирайте цю помилку, переносячи ключ у змінну з VITE_.

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

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

Що означає "Edge Function returned a non-2xx status code"?

Це означає, що ваша Edge Function у Supabase відповіла статусом помилки, тобто будь-яким числом поза межами 200-х, а supabase-js перетворив цю відповідь на одне незмінне речення. Речення однакове для падіння коду, відсутнього ключа, запиту, відхиленого ще до запуску вашого коду, і вичерпаного часу. Сама відповідь, яка каже, що саме сталося, лишається прикріпленою до помилки, а функція веде власний лог.

Як побачити справжню помилку за цим повідомленням?

Відкрийте свій опублікований застосунок, натисніть F12, оберіть вкладку Network і повторіть те, що не спрацювало. Клацніть запит, адреса якого містить /functions/v1/, і прочитайте його статус і відповідь: це те, що функція насправді надіслала. Потім відкрийте Edge Functions у панелі Supabase, оберіть функцію й прочитайте вкладки Invocations і Logs за той самий час. У коді посібник Supabase читає ту саму відповідь через await error.context.json().

Чому моя Edge Function працює локально і падає після розгортання?

Спершу перевірте, чи секрет існує на вашому компʼютері й відсутній в опублікованому проєкті. Локальне середовище читає власний файл оточення, а розгорнута функція читає секрети, задані в проєкті. Відсутній секрет повертається як undefined, а не як помилка, тож функція працює далі й падає пізніше, зазвичай коли сервіс, який вона викликає, відхиляє порожній ключ. Задайте його на сторінці Edge Function Secrets; за словами Supabase, новий секрет читається одразу, без повторного розгортання.

Чи варто вимикати перевірку JWT, щоб позбутися 401?

Supabase тепер радить вимкнути вбудовану перевірку й натомість перевіряти того, хто викликає, усередині вашої функції. Важать обидві половини. Вимкнена сама по собі, перевірка вже нічого не зупиняє, і документація Supabase сама попереджає, що тоді будь-хто може викликати вашу функцію без дійсного JWT. Якщо функція тримає ключ OpenAI або секретний ключ проєкту, це будь-хто в інтернеті, хто витрачає вашу квоту чи обходить ваші правила row level security.

Що означає помилка 546 у Supabase Edge Function?

Функції забракло обчислень: понад 2 секунди процесорного часу або більше памʼяті, ніж їй відведено, на одному запиті. Відповідь звучить так: "Function failed due to not having enough compute resources (please check logs)", а вкладка Logs каже, який саме ліміт, CPU Time exceeded чи Memory limit exceeded. Очікування повільного API не рахується як процесорний час; функція, яка чекає надто довго, натомість отримує 504.

Автор

Vlad Tkachenko

Засновник Reeve

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

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

Читати далі

Усі статті

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

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

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

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