[{"data":1,"prerenderedAt":839},["ShallowReactive",2],{"blog-uk-vite-and-next-public-env-vars":3,"blog-index-uk":598},{"id":4,"title":5,"body":6,"category":550,"cover":551,"coverAlt":552,"description":553,"draft":554,"extension":555,"faq":556,"image":575,"keywords":576,"meta":586,"navigation":587,"ogTitle":588,"path":589,"published":590,"seo":591,"stem":592,"tldr":593,"updated":590,"__hash__":597},"blog_uk\u002Fblog\u002Fvite-and-next-public-env-vars.md","Змінні оточення Vite відкриті: префікс означає опублікувати це",{"type":7,"value":8,"toc":536},"minimark",[9,25,40,43,48,54,72,86,93,99,105,121,127,133,137,140,151,154,160,164,167,318,324,333,337,340,360,371,375,378,398,408,413,420,424,427,434,455,463,471,475,518,521,528],[10,11,12,13,17,18,21,22,24],"p",{},"Хтось відкрив ваш застосунок на Lovable, натиснув F12 і знайшов у коді\nзначення, яке ви точно поклали у файл ",[14,15,16],"code",{},".env",". Або ви попросили в білдера нову\nфункцію, він написав рядок, що починається з ",[14,19,20],{},"VITE_",", а щось, прочитане вами\nвідтоді, каже, що саме через цей префікс витікають ключі. У назві файлу є\n",[14,23,16],{},", і кожен посібник каже ніколи ним не ділитися. Змінні оточення Vite,\nвідкриті кожному відвідувачу, звучать як помилка у Vite.",[10,26,27,28,32,33,35,36,39],{},"Ось та частина, яку посібник за посібником пояснює неправильно: ",[29,30,31],"strong",{},"ніщо не\nпровалило спробу сховати це значення."," ",[14,34,20],{}," і ",[14,37,38],{},"NEXT_PUBLIC_"," є\nінструкцією для вашого інструмента збірки, і ця інструкція звучить так:\nпоклади значення в застосунок, який завантажує кожен відвідувач. Інструмент\nпрочитав мітку і зробив те, що на ній написано.",[10,41,42],{},"Збірка пакує ваш застосунок у коробку, і кожен відвідувач отримує копію цієї\nкоробки. Префікс є міткою на одному значенні, яка каже: запакуй і це. Решта\nцієї статті про те, для яких значень ця мітка правильна, чому ШІ чіпляє її на\nнеправильні, і як побачити, що лежить у вашій власній коробці.",[44,45,47],"h2",{"id":46},"чи-відкриті-змінні-оточення-vite-для-відвідувачів","Чи відкриті змінні оточення Vite для відвідувачів?",[10,49,50,51,53],{},"Ті, що починаються з ",[14,52,20],{},", так, і саме для цього префікс існує.",[10,55,56,57,59,60,63,64,67,68,71],{},"Vite, інструмент збірки за більшістю застосунків на Lovable і Bolt, читає ваш\nфайл ",[14,58,16],{}," під час кожної збірки. Змінна з назвою ",[14,61,62],{},"VITE_SUPABASE_URL","\nкопіюється в бандл, стиснутий файл JavaScript, який завантажує кожен\nвідвідувач, і там ваш код читає її як ",[14,65,66],{},"import.meta.env.VITE_SUPABASE_URL",".\nЗмінна з назвою ",[14,69,70],{},"DB_PASSWORD",", без префікса, приходить у браузер порожньою.\nВласна документація Vite каже, що значення з префіксом під час збірки\nвбудовуються у ваш вихідний код і не повинні містити ключів API.",[10,73,74,75,77,78,81,82,85],{},"Next.js, на якому будує v0, має те саме правило під іншою назвою:\n",[14,76,38],{},". Його документація описує значення як inlined, жорстко\nвписаний рядок, записаний у браузерний бандл під час збірки. Expo використовує\n",[14,79,80],{},"EXPO_PUBLIC_"," і попереджає тими самими словами. Старіші проєкти Create React\nApp використовують ",[14,83,84],{},"REACT_APP_",". Кожен префікс каже своєму інструменту\nзбірки те саме: це йде в коробку.",[44,87,89,90,92],{"id":88},"чому-файл-env-здається-приватним-хоч-він-не-такий","Чому файл ",[14,91,16],{}," здається приватним, хоч він не такий",[10,94,95,96,98],{},"Бо звичка тримати ключ у файлі ",[14,97,16],{}," прийшла із серверів, де вона працює.",[10,100,101,102,104],{},"На сервері файл і код, який його читає, лежать на машині, яку контролюєте\nви. Відвідувач отримує від цієї машини відповідь і ніколи не бачить файлу.\nВинести ключ із коду у файл, який сервер читає під час запуску, там є доброю\nпрактикою, і саме там написано кожен посібник, який каже «поклади це в\n",[14,103,16],{},"».",[10,106,107,108,110,111,114,115,117,118,120],{},"У вашого файлу ",[14,109,16],{}," є два читачі, а посібники розповідають про одного.\nПерший читач: будь-хто, хто може бачити ваш проєкт, тобто співавтор,\nпублічний репозиторій на GitHub, перегляд файлів у самому білдері. Запис у\n",[14,112,113],{},".gitignore",", тобто у списку файлів, які git пропускає, тримає ",[14,116,16],{}," подалі\nвід цього читача. Другий читач: збірка, яка відкриває файл щоразу, коли ви\nпублікуєте, і копіює звідти все, що має префікс. ",[14,119,113],{}," їй нічого не\nкаже.",[122,123],"diagram",{"alt":124,"caption":125,"src":126},"Файл .env посередині з трьома значеннями, два з них позначені VITE_. Ліворуч пунктирна стіна з підписом .gitignore перекриває шлях до стосу файлів репозиторію. Праворуч два позначені значення проходять крізь крок збірки у вікно браузера, поруч із яким стоїть людина, а непозначене значення лишається у файлі.","Один файл, два читачі. Стіна .gitignore зупиняє одного з них. Збірка несе кожне значення з префіксом до іншого.","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Ftwo-readers-one-file-1600x760.png",[10,128,129,130,132],{},"Тож «мій ",[14,131,16],{}," у gitignore» є правдою, і це відповідь на інше запитання.\nФайл лишився поза вашим репозиторієм. Значення з префіксом усе одно\nпотрапили в застосунок, бо саме цей шлях префікс і відкриває, а в проєкті на\nLovable, Bolt чи v0 більшість коду, який ви редагували, працює в браузері, де\nнемає сервера, за яким файл міг би залишитися.",[44,134,136],{"id":135},"чому-ші-потягнувся-до-префікса","Чому ШІ потягнувся до префікса",[10,138,139],{},"Бо саме так змушують значення працювати в коді браузера, а модель гадки не\nмає, скільки це значення коштує.",[10,141,142,143,146,147,150],{},"Ви попросили мапу або чат, який відповідає на запитання про ваш продукт. Код,\nякий білдер для цього написав, працює в браузері відвідувача, а код браузера,\nщо читає ",[14,144,145],{},"process.env.OPENAI_API_KEY",", не отримує нічого. Спосіб доставити\nзначення: префікс. Білдер перейменовує змінну на ",[14,148,149],{},"VITE_OPENAI_API_KEY",",\nфункція працює в попередньому перегляді, і ніде не з’являється жодної\nпомилки, бо з боку інструмента збірки нічого не пішло не так.",[10,152,153],{},"Префікс не судить. Це та сама інструкція і для ключа Google Maps, який має\nбути публічним після обмеження, і для секретного ключа Stripe, який може\nповернути гроші кожному вашому клієнту. Людина, яка знала б, що другий\nсписує гроші з вашої картки, зупинилася б. Модель знає, що функція не\nпрацювала, доки цього рядка не було, а потім запрацювала.",[10,155,156,157,159],{},"Саме тому це трапляється в застосунках, власники яких зробили все, що їм\nказали. Значення винесли з коду, тримали у ",[14,158,16],{},", файл був у gitignore, а\nзастосунок усе одно його віддає, бо єдиний крок, який його публікує, виглядає\nяк крок, який змушує його працювати.",[44,161,163],{"id":162},"які-значення-можуть-стояти-за-префіксом","Які значення можуть стояти за префіксом",[10,165,166],{},"Адреса і публічний ключ. Усе, що витрачає гроші або ігнорує правила вашої\nбази даних, ні.",[168,169,170,192],"table",{},[171,172,173],"thead",{},[174,175,176,180,189],"tr",{},[177,178,179],"th",{},"Значення",[177,181,182,183,185,186,188],{},"За ",[14,184,20],{}," чи ",[14,187,38],{},"?",[177,190,191],{},"Чому",[193,194,195,212,231,246,259,273,292,306],"tbody",{},[174,196,197,202,209],{},[198,199,200],"td",{},[14,201,62],{},[198,203,204],{},[205,206,208],"key-verdict",{"type":207},"safe","Тут доречно",[198,210,211],{},"Адреса. Каже, з яким проєктом говорить ваш застосунок, і більше нічого.",[174,213,214,224,228],{},[198,215,216,219,220,223],{},[14,217,218],{},"VITE_SUPABASE_PUBLISHABLE_KEY",", або ",[14,221,222],{},"VITE_SUPABASE_ANON_KEY"," у старішому проєкті",[198,225,226],{},[205,227,208],{"type":207},[198,229,230],{},"Створений для браузера. Кожен його запит і далі фільтрує Row Level Security, тобто правила на кожній таблиці, які рядок за рядком вирішують, кому що можна читати.",[174,232,233,239,243],{},[198,234,235,236],{},"Ключ Stripe ",[14,237,238],{},"pk_live_",[198,240,241],{},[205,242,208],{"type":207},[198,244,245],{},"Будує форми оплати. Не може ані списати, ані повернути гроші, ані прочитати клієнтів.",[174,247,248,251,256],{},[198,249,250],{},"Ключ Google Maps",[198,252,253],{},[205,254,255],{"type":207},"Тут доречно, після обмеження",[198,257,258],{},"Публічний за задумом. Обмеження за referrer у Google Cloud і є тим, що не дає чужинцю нарахувати вам витрати з його допомогою.",[174,260,261,264,270],{},[198,262,263],{},"Ключ OpenAI або Anthropic",[198,265,266],{},[205,267,269],{"type":268},"danger","Ніколи",[198,271,272],{},"Публічного варіанта не існує. Хто його тримає, той витрачає ваші гроші.",[174,274,275,285,289],{},[198,276,277,278,281,282],{},"Ключ Supabase ",[14,279,280],{},"service_role"," або ",[14,283,284],{},"sb_secret_",[198,286,287],{},[205,288,269],{"type":268},[198,290,291],{},"Обходить Row Level Security і читає кожен рядок у кожній таблиці.",[174,293,294,299,303],{},[198,295,235,296],{},[14,297,298],{},"sk_live_",[198,300,301],{},[205,302,269],{"type":268},[198,304,305],{},"Списання, повернення, виплати і кожен запис клієнта.",[174,307,308,311,315],{},[198,309,310],{},"Ключ доступу AWS",[198,312,313],{},[205,314,269],{"type":268},[198,316,317],{},"Усе, що може цей обліковий запис, звідусіль.",[10,319,320,321,323],{},"Якщо у вашому застосунку є ",[14,322,62],{}," і поруч публічний ключ, це та\nпара, яку Supabase задумав для браузера, і наше сканування позначає її як\nдоречну. Адреса і публічний ключ і є причиною, чому префікс існує.",[10,325,326,327,332],{},"Перевірка для всього іншого: чи заперечували б ви, якби це значення\nнадрукували на вашій головній сторінці. Префікс кладе його на один клік далі,\nу файл замість сторінки, і файл має кожен, хто його хоче.\n",[328,329,331],"a",{"href":330},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","Як відрізнити ці дві родини",",\nза префіксом, а в старішому ключі Supabase за роллю всередині, є окремою\nстаттею.",[44,334,336],{"id":335},"що-30-998-застосунків-віддавали-за-префіксом","Що 30 998 застосунків віддавали за префіксом",[10,338,339],{},"Здебільшого ключі Google API. 52 застосунки віддавали ключ, який витрачає\nгроші або читає все.",[10,341,342,343,347,348,352,353,185,356,359],{},"У серпні 2026 року ми запустили\n",[328,344,346],{"href":345},"\u002Fresearch\u002Fvibe-coded-app-security-2026","ті самі дев’ять перевірок"," на\n30 998 живих vibe-coded застосунках. 1 332 з них, тобто 4%, віддавали щось\nсхоже на ключ у коді, який завантажує кожен відвідувач. 1 142 з них були\nключами Google API, яким зазвичай потрібне\n",[328,349,351],{"href":350},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","обмеження, задане в Google Cloud",",\nа не ротація. 204 віддавали випадкове на вигляд значення поруч із назвою на\nкшталт ",[14,354,355],{},"secret",[14,357,358],{},"password",", яке може бути справжніми обліковими даними, а\nможе й ні.",[10,361,362,363,365,366,370],{},"Дорогі випадки були рідкісними. 33 застосунки віддавали ключ OpenAI, 9 ключ\nдоступу AWS, 5 ключ Anthropic, 3 секретний ключ Stripe і 3 ключ\n",[14,364,280],{}," від Supabase: 52 застосунки загалом, бо один із них ніс два.\nТож значення за префіксом зазвичай є ключем Google, і виправлення для нього є\nналаштуванням. Рідкісний випадок і є тим, де живе шкода, а\n",[328,367,369],{"href":368},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","скільки коштує витеклий ключ OpenAI",",\nрозповідає стаття саме про нього.",[44,372,374],{"id":373},"як-перевірити-що-віддає-ваш-власний-застосунок","Як перевірити, що віддає ваш власний застосунок",[10,376,377],{},"Відкрийте живий застосунок, натисніть F12 і пошукайте значення в кожному\nзавантаженому файлі.",[379,380,381,385,392,395],"ol",{},[382,383,384],"li",{},"Відкрийте свій опублікований застосунок у браузері за його справжньою\nадресою. Попередній перегляд у білдері є іншою збіркою і може відставати\nна версію.",[382,386,387,388,391],{},"Натисніть F12, щоб відкрити інструменти розробника, і виберіть вкладку\n",[29,389,390],{},"Sources",".",[382,393,394],{},"Натисніть Ctrl+Shift+F або Cmd+Option+F на Mac. Це відкриє пошук по кожному\nфайлу, який завантажила сторінка.",[382,396,397],{},"Вставте перші приблизно десять символів значення, яке вас непокоїть, і\nбільше нікуди його не вставляйте.",[10,399,400,401,404,405,407],{},"Збіг означає, що значення лежить у коробці, яку отримує кожен відвідувач.\nШукайте значення, а не назву: збірка зазвичай замінює\n",[14,402,403],{},"import.meta.env.VITE_OPENAI_API_KEY"," на саме значення, тож пошук за ",[14,406,20],{},"\nможе повернутися порожнім, тоді як кожне значення за ним на місці.",[122,409],{"alt":410,"caption":411,"src":412},"Дві панелі. Ліворуч вихідний файл із рядком import.meta.env.VITE_OPENAI_API_KEY серед звичайних рядків коду. Праворуч, після кроку збірки, той самий файл, стиснутий у щільний шум, де назва змінної зникла, а її значення намальоване як підсвічена смужка під лупою.","Збірка замінює назву на значення. Пошук VITE_ у живому застосунку не знаходить нічого; пошук значення знаходить його.","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fname-goes-value-stays-1600x640.png",[10,414,415,416,391],{},"Якщо ви не хочете проходити свій застосунок значення за значенням, наше\nбезкоштовне сканування читає ваш живий сайт ззовні, усі дев’ять перевірок,\nприблизно за 20 секунд і без облікового запису. Воно називає кожен знайдений\nключ за типом, каже, які з них доречні в браузері, і пише «Не вдалося\nперевірити» для всього, на що не змогло відповісти, замість галочки:\n",[328,417,419],{"href":418},"\u002Fsecurity-scanner","проскануйте свій застосунок",[44,421,423],{"id":422},"куди-натомість-іде-справжній-секрет","Куди натомість іде справжній секрет",[10,425,426],{},"На машину, з якої ваші відвідувачі ніколи нічого не завантажують. У проєкті\nSupabase це Edge Function, невеликий шматок серверного коду, який Supabase\nзапускає за вас; у застосунку на Next.js це серверний маршрут; у застосунку\nна Replit це серверна половина.",[10,428,429,430,433],{},"Форма всюди та сама. Ваш код у браузері просить вашу функцію зробити роботу.\nФункція тримає ключ, робить виклик до OpenAI чи Stripe і повертає відповідь.\nКлюч лишається на машині, а відвідувач отримує результат.\n",[328,431,432],{"href":368},"Стаття про OpenAI"," малює це як\nодин об’єкт, що зсувається на одну клітинку праворуч, і в цьому вся зміна.",[10,435,436,437,440,441,443,444,447,448,451,452,391],{},"Дві речі скажуть вам, що білдер зробив те, про що ви просили. Змінна втратила\nпрефікс, тож тепер це ",[14,438,439],{},"OPENAI_API_KEY",", і живе вона у власних секретах\nфункції, заданих у панелі Supabase у розділі Edge Functions, а в ",[14,442,16],{},"\nзастосунку нічого немає. І файл, який її читає, лежить у ",[14,445,446],{},"supabase\u002Ffunctions\u002F","\nабо ",[14,449,450],{},"app\u002Fapi\u002F",", там, куди збірка ніколи не пакує, а не в ",[14,453,454],{},"src\u002F",[10,456,457,458,462],{},"Один порядок має значення, і його легко переплутати. Якщо секретний ключ уже\nпішов у бандл за префіксом, перенесення нічого не дасть для вже завантажених\nкопій. Спершу зробіть ротацію в провайдера, потім переносьте роботу.\n",[328,459,461],{"href":460},"\u002Fblog\u002Frotate-supabase-service-role-key","Чи спершу ротація, чи спершу закрити витік",",\nзалежить від того, чи копія вже публічна, а щойно вона побувала в\nопублікованому бандлі, вона публічна.",[10,464,465,466,470],{},"У проєкті на Replit той самий поділ має власну назву, Secrets, і\n",[328,467,469],{"href":468},"\u002Fblog\u002Freplit-secrets-explained","що інструмент Secrets покриває, а що ні",", є\nокремою статтею.",[44,472,474],{"id":473},"що-робити-просто-зараз","Що робити просто зараз",[476,477,478],"key-takeaways",{},[479,480,481,494,506,509,512,515],"ul",{},[382,482,483,484,486,487,486,489,35,491,493],{},"Пошукайте у своєму проєкті ",[14,485,20],{},", ",[14,488,38],{},[14,490,80],{},[14,492,84],{},". Кожен збіг є значенням, яке ваша збірка публікує навмисно. Для кожного вирішіть, чи може воно бути публічним.",[382,495,496,497,35,499,501,502,505],{},"Залиште адресу і публічний ключ. ",[14,498,62],{},[14,500,218],{},", або ключ ",[14,503,504],{},"anon"," у старішому проєкті, є тією парою, заради якої префікс існує.",[382,507,508],{},"Секретний ключ за префіксом спершу проходить ротацію в провайдера, а потім переноситься. Видалення рядка не повертає копію, яку вже завантажили.",[382,510,511],{},"Перенесіть роботу, якій був потрібен ключ, в Edge Function або серверний маршрут, із ключем у секретах цієї функції і без префікса в назві.",[382,513,514],{},"Обмежте ключ Google за referrer у Google Cloud. Йому потрібне налаштування, а значення він зберігає.",[382,516,517],{},"Після наступної публікації пошукайте в живому застосунку кожне значення, яке ви перенесли.",[10,519,520],{},"Кожна публікація пакує нову коробку. Наступна функція, яку ви попросите, є\nще однією нагодою для значення отримати префікс, і ніщо між білдером та\nінтернетом не читає бандл на виході. Сканування минулого місяця прочитало\nбандл минулого місяця.",[10,522,523,527],{},[328,524,526],{"href":525},"\u002Fpricing","Reeve Monitor"," читає бандл за вас. Він щогодини повторює всі\nдев’ять перевірок на щонайбільше трьох застосунках, повідомляє вас, коли\nрезультат змінюється, стежить за доступністю кожні 60 секунд і надсилає\nщомісячний звіт. Ключ, який потрапляє в бандл із публікацією у вівторок, є в\nповторному скануванні тієї самої години, згадали ви подивитися чи ні. Коштує\n$12 на місяць за прайсом, із сімома безкоштовними днями до першого списання;\nсторінка цін іноді нижча за цифру тут і ніколи не вища.",[10,529,530,531,535],{},"Якщо вам зручніше пройти це списком,\n",[328,532,534],{"href":533},"\u002Fchecklist","10-хвилинний чекліст безпеки"," покриває це і решту речей, які\nварто вимкнути в щойно запущеному застосунку.",{"title":537,"searchDepth":538,"depth":538,"links":539},"",3,[540,542,544,545,546,547,548,549],{"id":46,"depth":541,"text":47},2,{"id":88,"depth":541,"text":543},"Чому файл .env здається приватним, хоч він не такий",{"id":135,"depth":541,"text":136},{"id":162,"depth":541,"text":163},{"id":335,"depth":541,"text":336},{"id":373,"depth":541,"text":374},{"id":422,"depth":541,"text":423},{"id":473,"depth":541,"text":474},"Основи безпеки","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcover-1200x630.png","Файл .env із трьома значеннями, два з них із міткою VITE_, і ті самі два значення, намальовані ще раз у вікні браузера поруч.","Змінні оточення Vite відкриті у вашому застосунку, бо так просив префікс. VITE_ і NEXT_PUBLIC_ означають опублікувати це, а ШІ, який їх додав, не знав ціни.",false,"md",[557,560,563,566,569,572],{"q":558,"a":559},"Чи є файли .env секретними?","Від вашого репозиторію так, якщо файл записаний у .gitignore. Від ваших відвідувачів ні. Збірка читає .env щоразу, коли ви публікуєте, і копіює кожне значення з префіксом VITE_ або NEXT_PUBLIC_ у JavaScript, який віддає ваш застосунок. Сам файл ніколи не залишає вашу машину; значення, які ви позначили для браузера, залишають.",{"q":561,"a":562},"Чи безпечно відкривати VITE_SUPABASE_ANON_KEY?","Він створений, щоб бути там. Ключ anon, який у новішому проєкті зветься публічним ключем, розрахований на життя в браузері. Він лише каже, до якого проєкту належить запит, і кожен запит, який він робить, фільтрують ваші правила Row Level Security. Це тримається рівно доти, доки ці правила ввімкнені й правильні, а це окрема перевірка.",{"q":564,"a":565},"Чи NEXT_PUBLIC_ чимось відрізняється від VITE_?","Те саме правило, інший інструмент збірки. Next.js під час збірки записує значення кожної змінної NEXT_PUBLIC_ у браузерний бандл як жорстко вписаний рядок, а змінна без префікса приходить у код браузера порожньою. Expo робить те саме з EXPO_PUBLIC_, а старіші проєкти Create React App із REACT_APP_. Хоч би який інструмент зібрав ваш застосунок, префікс означає опублікувати.",{"q":567,"a":568},"Як перевірити, що є в моєму бандлі?","Відкрийте живий застосунок, натисніть F12, виберіть Sources і натисніть Ctrl+Shift+F (Cmd+Option+F на Mac), щоб шукати в кожному файлі, який завантажила сторінка. Вставте перші кілька символів значення. Шукайте значення, а не назву змінної, бо збірка зазвичай замінює назву на значення, тож VITE_ може бути відсутнім, тоді як ключ на місці. Наше безкоштовне сканування робить те саме читання ззовні приблизно за 20 секунд.",{"q":570,"a":571},"Де має жити секретний ключ у застосунку на Lovable чи Bolt?","У Supabase Edge Function, із ключем, заданим у секретах цієї функції в панелі Supabase, і без префікса VITE_ у назві. Ваш код у браузері викликає функцію, функція викликає провайдера з ключем, і ключ ніколи не дістається відвідувача. Якщо ключ уже пішов у бандл, зробіть ротацію в провайдера, перш ніж переносити його.",{"q":573,"a":574},"Чи захищає .gitignore мої ключі?","Він тримає файл .env поза git, тож ніхто, хто читає ваш репозиторій, його не бачить. На збірку він не впливає: вона читає файл напряму й публікує кожне значення з префіксом. Файл .env у .gitignore з VITE_OPENAI_API_KEY усередині все одно віддає цей ключ кожному відвідувачу.","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",[577,578,579,580,581,582,583,584,585],"змінні оточення vite відкриті","префікс VITE_ безпека","next_public_ змінна оточення секрет","чи безпечні змінні .env у react","чи безпечний vite_","змінні оточення у фронтенді","файл .env видно в браузері","expo_public env","сховати api key у фронтенді",{},true,"Змінні оточення Vite відкриті: VITE_ означає опублікувати це","\u002Fblog\u002Fvite-and-next-public-env-vars","2026-09-18",{"title":5,"description":553},"blog\u002Fvite-and-next-public-env-vars",[594,595,596],"Змінні оточення Vite відкриті у вашому застосунку, бо саме цього просив префікс. Змінна з назвою VITE_ або NEXT_PUBLIC_ копіюється в JavaScript, який завантажує кожен відвідувач, і те, що вона лежить у файлі .env, цього не зупиняє.","Префікс є правильною міткою для адреси чи публічного ключа і неправильною для всього, що витрачає гроші або ігнорує правила вашої бази даних. Збірка не відрізняє одне від іншого, і ШІ, який написав цей рядок, теж.","Із 30 998 живих vibe-coded застосунків, які ми просканували, 1 332 віддавали щось схоже на ключ. 1 142 з них були ключами Google API, яким зазвичай потрібне обмеження, а не ротація. 52 віддавали ключ, який витрачає гроші або читає все.","EsIrCzV5SAPs7kF4Bdxa-Xk-1oVu8DCxV8xOlsjjU_M",[599,606,612,618,624,630,631,637,643,649,655,661,667,673,679,685,690,696,702,708,714,719,725,731,736,742,748,754,760,766,772,778,784,789,795,801,806,812,818,824,829,835],{"path":600,"title":601,"description":602,"published":603,"category":604,"image":605,"draft":554},"\u002Fblog\u002Fsupabase-backup-auth-users","Чому у вашому дампі Supabase немає користувачів","Запустіть supabase db dump окремо і отримаєте форму вашої бази та жодного її рядка, без схеми auth, у якій живуть ваші користувачі.","2026-09-23","Backups","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":607,"title":608,"description":609,"published":610,"category":550,"image":611,"draft":554},"\u002Fblog\u002Fdomain-and-certificate-expiry","Домен закінчився, сайт лежить: що буде далі","Ваш домен закінчився і сайт лежить. Ось який годинник уже пішов, чому прострочений сертифікат це менша з двох бід, і як перевірити обидва.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":613,"title":614,"description":615,"published":616,"category":550,"image":617,"draft":554},"\u002Fblog\u002Fis-lovable-safe","Чи безпечний Lovable? Що показали 18 554 застосунки Lovable","Чи безпечний Lovable? Ми прогнали 18 554 живі застосунки Lovable через дев’ять перевірок. Платформа виявилася найчистішою з п’яти. Знахідки були в застосунках.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":619,"title":620,"description":621,"published":622,"category":550,"image":623,"draft":554},"\u002Fblog\u002Fvibe-coded-app-security-checklist","Чек-лист безпеки для vibe coding, у девʼяти перевірках","Чек-лист безпеки для vibe coding із девʼяти пунктів, кожен з яких будь-хто може перевірити у вашому живому застосунку ззовні, і кожен має тест на один рядок.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":625,"title":626,"description":627,"published":628,"category":550,"image":629,"draft":554},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Секретний ключ Stripe у фронтенді може рухати гроші","Секретний ключ Stripe, відкритий у вашому фронтенді, може повертати кошти, списувати й читати кожну картку клієнта. А ключ pk_live_ там і має бути.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":589,"title":5,"description":553,"published":590,"category":550,"image":575,"draft":554},{"path":632,"title":633,"description":634,"published":635,"category":550,"image":636,"draft":554},"\u002Fblog\u002Fbase44-source-maps","Безпека Base44: що позначає скан і що з цього виправляти вам","Безпека Base44 на 5 438 просканованих застосунках: три знахідки майже на кожному належать платформі, source map є бейджем Base44, а ваша частина коротка.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":638,"title":639,"description":640,"published":641,"category":550,"image":642,"draft":554},"\u002Fblog\u002Fis-cursor-ai-safe","Чи безпечний Cursor AI? Редактор, код і опублікований застосунок","Чи безпечний Cursor AI? Три запитання в одному пошуку: що Cursor зберігає, у чому помиляється його код і чи відкритий застосунок, який ви опублікували.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":644,"title":645,"description":646,"published":647,"category":550,"image":648,"draft":554},"\u002Fblog\u002Fis-replit-safe","Чи безпечний Replit? Що ми знайшли в 3 042 живих застосунках Replit","Чи безпечний Replit? Ми прогнали 3 042 живі застосунки Replit через дев’ять зовнішніх перевірок. Знахідки були в опублікованому застосунку, а не в хостингу.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":650,"title":651,"description":652,"published":653,"category":604,"image":654,"draft":554},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: у копії бази даних немає жодного файлу","Supabase storage backup це окрема робота. Копії бази даних зберігають список ваших файлів і жодного з них, тож після відновлення кожне завантаження зламане.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":656,"title":657,"description":658,"published":659,"category":604,"image":660,"draft":554},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Point-in-time recovery у Supabase: ціна та межі","Point-in-time recovery у Supabase відмотує базу на будь-яку секунду за останній тиждень. Коштує $100 на місяць понад Pro і покриває лише базу даних.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":662,"title":663,"description":664,"published":665,"category":604,"image":666,"draft":554},"\u002Fblog\u002Fsupabase-project-paused-recover","Проєкт Supabase на паузі? Ваші дані нікуди не зникли","Supabase поставив ваш проєкт на паузу після тижня без активності. Нічого не видалено, кнопка Restore стоїть поруч із назвою, і у вас є рік, щоб натиснути.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":668,"title":669,"description":670,"published":671,"category":550,"image":672,"draft":554},"\u002Fblog\u002Fsafest-ai-app-builder","Який білдер застосунків з ШІ найбезпечніший? 30 998 тестів","Який білдер застосунків з ШІ найбезпечніший? Ми просканували 30 998 живих застосунків з Lovable, Base44, Replit, v0 і Bolt. Оцінку вирішує не білдер.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":674,"title":675,"description":676,"published":677,"category":550,"image":678,"draft":554},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Увімкніть Row Level Security на кожній таблиці Supabase і перевірте","Row Level Security у Supabase без політики закриває таблицю повністю. Політика без увімкненого перемикача не робить нічого. Ось SQL, і ось перевірка.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":680,"title":681,"description":682,"published":683,"category":550,"image":684,"draft":554},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": чого не бачить попередження","Supabase позначає \"RLS disabled in public\" як помилку. Про політику читання, яка лишає вашу таблицю так само відкритою, він не каже нічого.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":460,"title":686,"description":687,"published":688,"category":550,"image":689,"draft":554},"Як замінити злитий ключ service_role у Supabase","Supabase каже спершу закрити витік. Інші посібники кажуть міняти негайно. Що правильно, залежить від того, куди саме потрапив ваш ключ service_role.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":691,"title":692,"description":693,"published":694,"category":550,"image":695,"draft":554},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Сканери безпеки для vibe coding у порівнянні, наш теж","Найкращий сканер безпеки для vibe coding залежить від трьох питань, на які не відповідає жоден перелік функцій. Десять інструментів і ціни.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":697,"title":698,"description":699,"published":700,"category":550,"image":701,"draft":554},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: п’ять перевірок, які зробите самі","Supabase security checker читає ваш опублікований застосунок, а не налаштування проєкту. Ось п’ять перевірок і як виконати кожну самостійно.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":703,"title":704,"description":705,"published":706,"category":550,"image":707,"draft":554},"\u002Fblog\u002Fvibe-coding-security-scanner","Сканер безпеки для vibe coding: чого не бачить скан URL","Сканер безпеки для vibe coding читає ваш робочий застосунок ззовні. Що це охоплює, чотири речі, яких він не бачить, і як читати результат.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":709,"title":710,"description":711,"published":712,"category":550,"image":713,"draft":554},"\u002Fblog\u002Fmissing-security-headers","Відсутні заголовки безпеки: коли це справді важливо","Відсутні заголовки безпеки трапляються в наших звітах найчастіше. Від чого вони захищають і коли це найменш терміновий рядок вашого звіту.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":368,"title":715,"description":716,"published":717,"category":550,"image":718,"draft":554},"Ваш ключ API OpenAI відкритий у фронтенді. Замініть його.","Ключ API OpenAI, відкритий у фронтенді, не можна прив'язати до домену. Замініть його сьогодні, перенесіть виклик на свій сервер і обмежте витрати.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":720,"title":721,"description":722,"published":723,"category":550,"image":724,"draft":554},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","Ключ API у фронтенді: що насправді віддають 30 998 застосунків","Ключ API, відкритий у вашому фронтенді, зазвичай є ключем Google Maps. Ми просканували 30 998 vibe-coded застосунків і порахували, що витікає насправді.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":726,"title":727,"description":728,"published":729,"category":604,"image":730,"draft":554},"\u002Fblog\u002Fsupabase-backup-tools-compared","Інструменти резервного копіювання Supabase, і наш серед них","Чотири види інструментів резервного копіювання Supabase, що копіює кожен і коли безкоштовна GitHub Action виграє в будь-якої оплати, нашої теж.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":468,"title":732,"description":733,"published":734,"category":550,"image":735,"draft":554},"Як користуватися секретами в Replit і що все одно публікується","Як користуватися секретами в Replit: додати, прочитати і усунути дві причини undefined. А ще ключі, які цей інструмент не здатен зберегти в таємниці.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":737,"title":738,"description":739,"published":740,"category":604,"image":741,"draft":554},"\u002Fblog\u002Fback-up-supabase-free-tier","Резервні копії на безкоштовному тарифі Supabase без терміналу","На безкоштовному тарифі Supabase резервних копій немає: копію робите ви. Як зробити її з панелі та що втрачає шлях через CSV.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":743,"title":744,"description":745,"published":746,"category":550,"image":747,"draft":554},"\u002Fblog\u002Fis-supabase-secure","Чи безпечний Supabase? Так. Ваш проєкт це інше питання","Чи безпечний Supabase? Платформа проходить аудити, шифрує дані й тримає пентести. Їхня документація каже, де це закінчується і починаються ваші налаштування.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":749,"title":750,"description":751,"published":752,"category":550,"image":753,"draft":554},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Де знайти API-ключі Supabase: anon, service_role і URL","URL проєкту, ключ anon і ключ service_role лежать на одній сторінці панелі. Ось де вона, і яке з чотирьох значень має бути у вашому застосунку.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":755,"title":756,"description":757,"published":758,"category":550,"image":759,"draft":554},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","New row violates row-level security policy в Supabase. Що далі?","\"New row violates row-level security policy\" означає, що Supabase відмовив у записі. Швидке виправлення знову відкриває таблицю для всіх.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":761,"title":762,"description":763,"published":764,"category":550,"image":765,"draft":554},"\u002Fblog\u002Fcors-wildcard-security-risk","Чи є вайлдкард CORS ризиком для безпеки? Майже ніколи.","Чи є вайлдкард CORS ризиком для безпеки? Зазвичай це типове налаштування вашого білдера, і воно не видає нічого, що ваш сервер і так віддавав кожному.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":767,"title":768,"description":769,"published":770,"category":604,"image":771,"draft":554},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Branching у Supabase це не бекап. Він іде тільки вперед.","Чому branching у Supabase це не бекап: гілка стартує без ваших даних, а мердж переносить саму лише схему. Для чого він потрібен і що брати замість нього.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":773,"title":774,"description":775,"published":776,"category":604,"image":777,"draft":554},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Як відновити резервну копію Supabase, і що ламається потім","Як відновити резервну копію Supabase з панелі або з файлу дампа, що саме замінює відновлення і чому ваш застосунок може лишитися зламаним після нього.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":779,"title":780,"description":781,"published":782,"category":550,"image":783,"draft":554},"\u002Fblog\u002Fsupabase-storage-bucket-public","Ваш бакет Supabase Storage публічний. Чи це проблема?","Публічний бакет Supabase Storage означає, що той, хто має URL файлу, може його відкрити. Він не означає, що хтось може переглянути список вмісту.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":350,"title":785,"description":786,"published":787,"category":550,"image":788,"draft":554},"Чи є проблемою ключ API Google у вашому браузері?","Ключ API Google у відкритому коді сторінки наш сканер знаходить найчастіше, і зазвичай усе гаразд. Вирішує це одне безкоштовне налаштування.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":790,"title":791,"description":792,"published":793,"category":604,"image":794,"draft":554},"\u002Fblog\u002Fai-agent-deleted-my-database","ШІ-агент видалив мої дані в Supabase. Що можна відновити?","ШІ-агент видалив дані з вашої бази. Те, що ви зможете відновити, вирішилося раніше, а наступні хвилини вирішують, скільки з цього вціліє.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":796,"title":797,"description":798,"published":799,"category":550,"image":800,"draft":554},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","Чи може будь-хто прочитати вашу базу Supabase? 3 680 застосунків","Чи може будь-хто прочитати вашу базу даних Supabase без входу? Ми просканували 30 998 живих застосунків, зроблених ШІ-білдерами, і виміряли, як часто так.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":802,"title":803,"description":804,"published":799,"category":550,"image":805,"draft":554},"\u002Fblog\u002Fsource-maps-exposed-in-production","Відкриті source maps: ваш застосунок публікує свій вихідний код","Відкрита source map дає будь-кому прочитати оригінальний код вашого застосунку, разом із коментарями. Перевірка за 30 секунд, і що важить насправді.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":807,"title":808,"description":809,"published":810,"category":550,"image":811,"draft":554},"\u002Fblog\u002Fsupabase-new-api-keys","Нові API-ключі Supabase: який із них безпечний у застосунку?","Supabase замінив anon і service_role публічними та секретними ключами. Який із них має бути у вашому застосунку, а який не має бути ніколи?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":813,"title":814,"description":815,"published":816,"category":604,"image":817,"draft":554},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","Чи робить Supabase резервні копії моєї бази? Залежить від тарифу.","Чи робить Supabase резервні копії вашої бази даних? На платних тарифах щодня, на безкоштовному жодної. Як дізнатися, що у вас насправді є.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":819,"title":820,"description":821,"published":822,"category":550,"image":823,"draft":554},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","Supabase Row Level Security увімкнено. Таблиця досі публічна.","Увімкнути Supabase Row Level Security не означає захистити таблицю. Захищають політики, а та, що полагодила застосунок, часто впускає всіх.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":825,"title":826,"description":827,"published":822,"category":604,"image":828,"draft":554},"\u002Fblog\u002Fversion-history-is-not-a-backup","Історія версій не є резервною копією. Вона не поверне таблицю.","Lovable і Bolt ведуть історію версій вашого коду. Ваша база даних працює як окремий сервіс, тож відкат назад не поверне ваші дані.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":830,"title":831,"description":832,"published":833,"category":604,"image":834,"draft":554},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Три способи зробити резервну копію бази Supabase, і чого бракує","Панель, pg_dump і керований сервіс. Що насправді зберігає кожен спосіб, що тихо лишає осторонь і який із них переживе втрату акаунта.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":330,"title":836,"description":837,"published":833,"category":550,"image":838,"draft":554},"Які API-ключі безпечні у браузері, а які ні","Ваш ключ anon у Supabase створений, щоб бути публічним. Ключ service_role для цього не створений, і він ігнорує всі правила, які ви задали. Як їх розрізнити.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1790150956239]