[{"data":1,"prerenderedAt":462},["ShallowReactive",2],{"blog-uk-which-api-keys-are-safe-in-your-frontend":3},{"id":4,"title":5,"body":6,"category":418,"cover":419,"coverAlt":419,"description":420,"draft":421,"extension":422,"faq":423,"image":439,"keywords":440,"meta":449,"navigation":450,"ogTitle":451,"path":452,"published":453,"seo":454,"stem":455,"tldr":456,"updated":460,"__hash__":461},"blog_uk\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend.md","Які API-ключі безпечні у браузері, а які ні",{"type":7,"value":8,"toc":408},"minimark",[9,13,21,26,29,32,47,50,54,57,60,63,72,78,82,205,208,227,231,253,259,275,287,290,300,305,315,322,326,334,337,344,347,354,358,393,401],[10,11,12],"p",{},"Якщо ви зібрали застосунок у Lovable, Bolt, v0, Cursor чи Replit, рано чи пізно\nстанеться таке: хтось відкриє ваш сайт, натисне F12 і повідомить, що ваш\nAPI-ключ «у відкритому доступі». Для застосунку, код якого ви й самі не читаєте,\nце тривожна новина.",[10,14,15,16,20],{},"Ось що гайд за гайдом подає неправильно: ",[17,18,19],"strong",{},"частина цих ключів там і має бути.","\nСтавитися до кожного видимого ключа як до витоку означає або панікувати без\nприводу, або, що значно гірше, навчитися ігнорувати попередження. І\nпроігнорувати його того дня, коли воно справді важить.",[22,23,25],"h2",{"id":24},"чи-погано-що-мій-api-ключ-видно","Чи погано, що мій API-ключ видно?",[10,27,28],{},"Зазвичай ні. Усе залежить від того, який це ключ.",[10,30,31],{},"Кожен сервіс, що спілкується з браузером, видає два різні типи облікових даних.\nПерший з них служить адресою. Другий служить в'язкою ключів від будівлі. Обидва називаються\n«API-ключ», і саме звідси більша частина плутанини.",[10,33,34,37,38,41,42,46],{},[17,35,36],{},"Адресу"," можна публікувати безпечно. Вона лише каже, до якого проєкту\nналежить запит; перевірка прав відбувається деінде. ",[17,39,40],{},"В'язку ключів","\nпублікувати не можна, бо вона ",[43,44,45],"em",{},"і є"," перевіркою прав: хто нею володіє, робить\nусе, що вона дозволяє, звідки завгодно.",[10,48,49],{},"Вашому застосунку потрібна адреса у браузері, щоб узагалі працювати. В'язка\nключів там не потрібна ніколи.",[22,51,53],{"id":52},"чому-ваш-застосунок-узагалі-надсилає-ключі-у-браузер","Чому ваш застосунок узагалі надсилає ключі у браузер",[10,55,56],{},"Бо запит іде з браузера вашого відвідувача, а не з вашого сервера.",[10,58,59],{},"Коли ваш застосунок завантажує список замовлень ваших користувачів, цей запит\nіде напряму з браузера відвідувача до вашого постачальника бази даних. Він має\nвказати, до якого проєкту належить, і цей ідентифікатор має бути на сторінці, бо\nсаме звідти виконується запит.",[10,61,62],{},"Немає варіанту, в якому цей ідентифікатор лишався б таємним. Він доходить до\nкожного відвідувача за задумом. Саме тому постачальники ділять облікові дані\nнадвоє: вони знають, що одні з них стануть публічними, тож зробили їх\nнешкідливими.",[10,64,65,66,71],{},"Безпека береться не з приховування адреси. Вона береться з правил, які ви\nзадаєте на іншому кінці: у випадку Supabase це Row Level Security, яка\nвирішує рядок за рядком, кому що видно. Саме ці правила варто перевіряти, а\n",[67,68,70],"a",{"href":69},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","увімкнути їх не означає бути захищеним",".",[73,74],"diagram",{"alt":75,"caption":76,"src":77},"Два шляхи до однієї таблиці. Ключ anon зупиняється на бар’єрі Row Level Security і дістає лише два рядки, які дозволяють правила. Ключ service_role проходить крізь проміжок у бар’єрі й дістає всі шість.","Саме Row Level Security робить ключ anon безпечним. Ключ service_role проходить повз нього.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fwhat-each-key-reaches-1600x820.png",[22,79,81],{"id":80},"дві-родини-ключів","Дві родини ключів",[83,84,85,104],"table",{},[86,87,88],"thead",{},[89,90,91,95,98,101],"tr",{},[92,93,94],"th",{},"Постачальник",[92,96,97],{},"Ключ",[92,99,100],{},"Безпечний у браузері?",[92,102,103],{},"Що він робить",[105,106,107,134,155,173,190],"tbody",{},[89,108,109,113,124,131],{},[110,111,112],"td",{},"Supabase",[110,114,115,119,120,123],{},[116,117,118],"code",{},"sb_publishable_…",", або ",[116,121,122],{},"anon"," у старіших проєктах",[110,125,126],{},[127,128,130],"key-verdict",{"type":129},"safe","Тут доречно",[110,132,133],{},"Каже, про який проєкт ідеться. Кожен запит і далі фільтрується вашими правилами Row Level Security.",[89,135,136,138,146,152],{},[110,137,112],{},[110,139,140,119,143,123],{},[116,141,142],{},"sb_secret_…",[116,144,145],{},"service_role",[110,147,148],{},[127,149,151],{"type":150},"danger","Ніколи",[110,153,154],{},"Повністю обходить Row Level Security. Читає і змінює кожен рядок у кожній таблиці, хоч би що казали ваші правила.",[89,156,157,160,166,170],{},[110,158,159],{},"Stripe",[110,161,162,165],{},[116,163,164],{},"pk_live_…"," (публічний)",[110,167,168],{},[127,169,130],{"type":129},[110,171,172],{},"Створює форми оплати. Не може ані переказати гроші, ані прочитати клієнтів.",[89,174,175,177,183,187],{},[110,176,159],{},[110,178,179,182],{},[116,180,181],{},"sk_live_…"," (секретний)",[110,184,185],{},[127,186,151],{"type":150},[110,188,189],{},"Повний доступ до акаунта: списання, повернення, виплати, картки клієнтів.",[89,191,192,195,198,202],{},[110,193,194],{},"OpenAI \u002F Anthropic",[110,196,197],{},"будь-який ключ",[110,199,200],{},[127,201,151],{"type":150},[110,203,204],{},"Публічного варіанту не існує. Кожен ключ виставляє рахунок просто вам.",[10,206,207],{},"Ця закономірність працює й за межами цих трьох. Якщо постачальник пропонує лише\nодин тип ключа, вважайте його секретним: його місце на сервері.",[209,210,212],"callout",{"type":211},"warn",[10,213,214,217,218,220,221,223,224,226],{},[17,215,216],{},"Найчастіше люди спотикаються саме на секретному ключі Supabase."," У старішому\nпроєкті він зветься ",[116,219,145],{}," і майже не відрізняється від ключа anon: той\nсамий формат, та сама довжина, те саме місце в панелі, поруч один з одним.\nЩоб скопіювати не той, вистачить одного невдалого кліка, і після цього нічого не ламається:\nваш застосунок працює точно так само, як раніше. Саме це й робить помилку такою\nнепомітною. У нових проєктах покращилося саме це: ",[116,222,142],{}," і\n",[116,225,118],{}," показують, який із них який, уже першими символами.",[22,228,230],{"id":229},"як-розрізнити-їх-за-60-секунд","Як розрізнити їх за 60 секунд",[10,232,233,236,237,240,241,244,245,248,249,252],{},[17,234,235],{},"У Stripe і в більшості постачальників достатньо префікса."," ",[116,238,239],{},"pk_"," означає\nпублічний і безпечний. ",[116,242,243],{},"sk_"," означає секретний, а отже ні. Деякі постачальники\nставлять посередині ",[116,246,247],{},"_test_"," або ",[116,250,251],{},"_live_",": витік тестового ключа створює значно\nменшу проблему, ніж витік бойового, але замініть обидва.",[10,254,255,258],{},[17,256,257],{},"У Supabase усе залежить від того, наскільки старий ваш проєкт."," Supabase\nвидавав ключі двох різних форматів, і обидва сьогодні працюють у живих\nзастосунках.",[10,260,261,236,264,266,267,269,270,274],{},[17,262,263],{},"Нові проєкти: читайте префікс, так само як у Stripe.",[116,265,118],{}," має\nжити у вашому застосунку. ",[116,268,142],{}," – це той, який варто замінити\nсьогодні. Розкодовувати нічого не треба: для чого ключ, написано спереду.\n",[67,271,273],{"href":272},"\u002Fblog\u002Fsupabase-new-api-keys","Що змінилося і що це означає для вашого застосунку",",\nякщо ви ще з ними не стикалися.",[10,276,277,280,281,283,284,286],{},[17,278,279],{},"Старіші проєкти: доводиться зазирнути всередину ключа."," Початкова пара,\n",[116,282,122],{}," і ",[116,285,145],{},", є JWT: три блоки незрозумілих символів, розділені\nкрапками, і середній блок містить читабельну інформацію, а не шифр. Там роль\nключа записана відкритим текстом.",[10,288,289],{},"В обох випадках інструмент не потрібен. Відкрийте Settings → API Keys в панелі\nSupabase, і там написано, який ключ який. Якщо ж вам зручніше перевірити той\nключ, який ви справді знайшли у застосунку, середня частина старого ключа\nрозкодовується приблизно в таке.",[291,292,297],"pre",{"className":293,"code":295,"language":296},[294],"language-text","{\n  \"iss\": \"supabase\",\n  \"ref\": \"abcdefghij…\",\n  \"role\": \"anon\",          ← ось це слово і має значення\n  \"iat\": 1750000000\n}\n","text",[116,298,295],{"__ignoreMap":299},"",[73,301],{"alt":302,"caption":303,"src":304},"Старий ключ Supabase, показаний як три секції, розділені крапками. Середню виділено, і вона розкодовується або в role anon, позначену як безпечну, або в role service_role, позначену як небезпечну.","У старому ключі середня секція містить читабельний текст, а не шифр. Одне слово в ній вирішує, чи можна було публікувати цей ключ.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fread-the-role-1600x640.png",[10,306,307,310,311,314],{},[116,308,309],{},"\"role\": \"anon\""," означає безпечний ключ. ",[116,312,313],{},"\"role\": \"service_role\""," означає той, який варто\nзамінити сьогодні. У старому ключі це одне слово і є всією різницею, і саме тому\nсканер, який просто шукає рядки, схожі на ключі, видає шум замість відповіді:\nвін не може сказати, який із двох однакових на вигляд ключів ви насправді\nопублікували.",[10,316,317,318,71],{},"Якщо перебирати застосунок ключ за ключем не хочеться, наше безкоштовне\nсканування читає ваш живий сайт і каже, які з них видно ззовні. Це триває\nприблизно 20 секунд і не потребує акаунта: ",[67,319,321],{"href":320},"\u002F#scan","сканувати застосунок",[22,323,325],{"id":324},"що-насправді-стається-коли-витікає-секретний-ключ","Що насправді стається, коли витікає секретний ключ",[10,327,328,329,119,331,333],{},"Кожен рядок вашої бази стає доступним для читання й запису тому, хто знайшов\nключ, включно з таблицями, які ви ніколи не відкривали застосунку, і з\nперсональними даними ваших користувачів. Row Level Security не поширюється на\nсекретний ключ Supabase (",[116,330,142],{},[116,332,145],{}," у старішому\nпроєкті). У цьому й полягає його призначення.",[10,335,336],{},"Шкода теж не теоретична. Зазвичай про неї дізнаються з повідомлення в підтримку\nпро дані, які змінилися самі, або з таблиці, яка раптом виявилася порожньою.",[10,338,339,340,343],{},"Витік ключа ",[116,341,342],{},"sk_live_"," у Stripe означає повернення, списання й картки клієнтів.\nВитік ключа постачальника ШІ означає рахунок, інколи дуже великий, який\nприходить раніше, ніж хтось це помітить.",[10,345,346],{},"Нічого з цього не потребує витонченого зловмисника. Автоматичні сканери обходять\nпублічні сайти саме в пошуках таких рядків, і їм не треба знати, хто ви, щоб\nзнайти ваш.",[10,348,349,350,71],{},"Якщо потрібна версія для конкретної платформи, у нас є зрозумілий розбір для\n",[67,351,353],{"href":352},"\u002Fis-your-supabase-app-safe","застосунків на Supabase",[22,355,357],{"id":356},"що-зробити-просто-зараз","Що зробити просто зараз",[359,360,361],"key-takeaways",{},[362,363,364,372,379,382,390],"ul",{},[365,366,367,368,371],"li",{},"Знайдіть кожен ключ у застосунку і визначте, який це. Префікс підходить для Stripe і для нових ключів Supabase; поле ",[116,369,370],{},"role"," всередині підходить для старіших ключів Supabase.",[365,373,374,375,378],{},"Якщо знайшли секретний ключ у браузері, ",[17,376,377],{},"спершу замініть його",". Видалити його з коду двері не зачиняє: старе значення лишається в історії версій і в кешованих копіях вашого сайту.",[365,380,381],{},"Перенесіть на сервер те, для чого цей ключ був потрібен: edge-функцію, serverless-маршрут, будь-що, що не є браузером.",[365,383,384,385,248,387,389],{},"Увімкніть Row Level Security для кожної таблиці, а потім перевірте, що вона справді працює. Публічний ключ (",[116,386,118],{},[116,388,122],{},") безпечний лише завдяки цим правилам; без них він читає всю вашу базу.",[365,391,392],{},"Перевірте рахунки та журнали після будь-якого розкриття секретного ключа. Заміна зупиняє те, що станеться далі, а не те, що вже сталося.",[10,394,395,396,400],{},"Якщо вам зручніше йти за списком, ",[67,397,399],{"href":398},"\u002Fchecklist","10-хвилинний чекліст безпеки","\nохоплює цей пункт і решту речей, які варто вимкнути у щойно запущеному\nзастосунку.",[10,402,403,404,71],{},"А якщо витік колись спорожнить таблицю, чи повернете ви її, цілком залежить від\n",[67,405,407],{"href":406},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","того, що ви копіювали",{"title":299,"searchDepth":409,"depth":409,"links":410},3,[411,413,414,415,416,417],{"id":24,"depth":412,"text":25},2,{"id":52,"depth":412,"text":53},{"id":80,"depth":412,"text":81},{"id":229,"depth":412,"text":230},{"id":324,"depth":412,"text":325},{"id":356,"depth":412,"text":357},"Основи безпеки",null,"Ваш ключ anon у Supabase створений, щоб бути публічним. Ключ service_role для цього не створений, і він ігнорує всі правила, які ви задали. Як їх розрізнити.",false,"md",[424,427,430,433,436],{"q":425,"a":426},"Мені сказали, що мій API-ключ у відкритому доступі. Це привід панікувати?","Не раніше, ніж ви дізнаєтеся, який це ключ. Якщо він починається з pk_ або це ключ anon у Supabase, він і має бути публічним, і все гаразд. Якщо він починається з sk_ або це ключ service_role, замініть його негайно, а потім подивіться, до чого він мав доступ.",{"q":428,"a":429},"Чи можна просто сховати ключ, щоб його ніхто не знайшов?","Ні. Усе, чим може скористатися ваш браузер, може прочитати й відвідувач: мініфікація, перейменування чи обфускація затримають когось на кілька секунд. Рішення ніколи не полягає в тому, щоб сховати секретний ключ у браузері: його треба перенести на сервер або використати публічний ключ, який від початку було безпечно показувати.",{"q":431,"a":432},"Чому Supabase дає мені ключ, який може прочитати будь-хто?","Тому що ваші дані захищає не ключ anon. Він лише каже, з яким проєктом ви говорите. Справжній захист дає Row Level Security, яка вирішує рядок за рядком, що саме дозволено бачити кожному відвідувачу. Саме тому ключ anon з увімкненою RLS цілком нормальний, а той самий ключ без RLS відкриває вашу базу даних.",{"q":434,"a":435},"Мій ключ Supabase починається з sb_publishable_. Це те саме, що ключ anon?","Він виконує ту саму роботу. Supabase перейменував свої ключі: sb_publishable_ заступив ключ anon, а sb_secret_ заступив ключ service_role. Тож ключ, що починається з sb_publishable_, і має жити у вашому застосунку, а той, що починається з sb_secret_, не має жити там ніколи. Старіші проєкти досі мають початкові ключі anon і service_role, і вони продовжують працювати; у панелі ви можете навіть бачити обидві пари поруч.",{"q":437,"a":438},"Я вже вставив секретний ключ у застосунок. Що тепер?","Спершу замініть його: у панелі постачальника створіть новий ключ і відкличте старий. Саме це справді зачиняє двері; прибрати його з коду недостатньо, бо старе значення лишається в історії версій і в кешованих копіях. Далі перенесіть на сервер те, для чого цей ключ був потрібен, і перевірте рахунки та журнали на те, чого ви не робили.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",[441,442,443,444,445,446,447,448],"ключ anon supabase","ключ service_role","ключ sb_publishable","ключ sb_secret","нові api-ключі supabase","публічний ключ stripe","api-ключі у браузері","api-ключ у відкритому доступі",{},true,"Які API-ключі безпечні у браузері?","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","2026-08-09",{"title":5,"description":420},"blog\u002Fwhich-api-keys-are-safe-in-your-frontend",[457,458,459],"Знайти API-ключ у коді вашого застосунку ще не означає проблему. Деякі ключі там і мають бути.","Публічні ключі безпечні у браузері, а секретні ні: секретний ключ у браузері працює як відкрита каса.","Дві перевірки розділяють їх менш ніж за хвилину: прочитати префікс і, якщо ключ Supabase старий, розкодувати роль.","2026-08-12","3AjODFpBQiIj0Q8Y-ulC8kZKC3KGdqNi4xeB7ShU3QI",1787826051180]