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

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

Ключ API у фронтенді: що насправді віддають 30 998 застосунків

Ключ API, відкритий у вашому фронтенді, зазвичай є ключем Google Maps. Ми просканували 30 998 vibe-coded застосунків і порахували, що витікає насправді.

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

Коротко

  • Ключ API, відкритий у вашому фронтенді, майже завжди належить до нешкідливого різновиду. Ми знайшли вартий уваги ключ у 1 332 із 30 998 застосунків, і 1 080 із них несли лише ключ API Google.
  • Ключ Supabase service_role, про який попереджає кожен туторіал, трапився в 3 застосунках із 30 998. Секретний ключ Stripe трапився теж у 3.
  • Ключі, що вже своєю формою витрачають гроші або читають дані, трапилися в 54 застосунках. Відкрита таблиця Supabase трапилася у 2 096.

Хтось відкриває ваш застосунок, натискає F12 і каже, що у вашому фронтенді відкритий ключ API. Слово, яке при цьому вживають, зазвичай «витік». Який саме це ключ, кажуть рідко, а поради, що знайдуться далі, ставляться до всіх ключів як до однакової пожежі.

Пожежа не однакова, і тепер ми можемо назвати цифри цієї відстані. Між 12 і 14 серпня 2026 року ми провели дев’ять зовнішніх перевірок для 30 998 робочих застосунків, створених у Lovable, Bolt, v0, Replit і Base44, і прочитали JavaScript, який кожен із них віддає браузеру. Ось що там було.

Чи справді ключ API у фронтенді є проблемою?

Зазвичай ні, і форма цього «зазвичай» перекошена більше, ніж ми очікували.

Ми знайшли вартий уваги ключ у 1 332 із 30 998 застосунків. У 1 080 із них єдиним, що ми знайшли, був ключ API Google, тобто саме ті облікові дані, які мають бути на вашій сторінці. Цей ключ захищає налаштування, що зберігається на боці Google, а ховати його ніколи не входило в домовленість.

Решта 29 666 застосунків не віддавали нічого, що наша перевірка секретів вважає проблемою. Ця цифра потребує одного уточнення, щоб бути чесною: публічні ключі до неї не входять. Ключ anon від Supabase або ключ pk_ від Stripe належить браузеру, тож ми позначаємо його як те, що ви зробили правильно, і в ці підрахунки він не потрапляє ніколи.

Що ми рахували і чого порахувати не змогли

Ми завантажували кожен застосунок так, як це робить відвідувач, у справжньому браузері, і читали завантажений JavaScript. Усе далі це форма, знайдена в тому коді.

Ми впізнаємо ті формати ключів, які знаємо. Stripe, OpenAI, Anthropic, AWS, Google і Supabase мають упізнавані префікси, а JWT від Supabase називає власну роль читабельним текстом у середній частині. Облікові дані у форматі, якого ми не впізнаємо, до цих цифр не потрапили, тож читайте їх як нижню межу.

Ми не скористалися жодним знайденим ключем. Жодного разу, у жодному застосунку. Ми записали тип і замасковану підказку у формі sk_live_…a1b2, а справжнє значення ніде не записувалося.

Жоден застосунок не названо. Ні тут, ні в наборі даних, ні деінде, де ми публікуємо.

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

Що 30 998 застосунків віддавали насправді

Одна таблиця, впорядкована за тим, як часто ми бачили кожну річ. Перевірка секретів завершилася на кожному з 30 998 застосунків, тож кожне число нижче стосується їх усіх.

Що ми знайшли в коді для браузераЗастосунки
Ключ API Google1 142
Значення з високою ентропією поруч із назвою «secret» чи «password»204
Ключ OpenAI33
Ключ доступу AWS9
Ключ Anthropic5
Ключ service_role від Supabase3
Секретний ключ Stripe3
Обмежений ключ Stripe2

Рядки в сумі дають більше за 1 332, бо один застосунок може нести два з них. Розкладіть ті самі 1 332 застосунки на групи, що не перетинаються, і картина стане ще різкішою: 1 080 мали ключ Google і більше нічого, 198 мали значення з високою ентропією, яке може бути справжніми обліковими даними, а може й ні, і 54 мали ключ, який уже своєю формою є справжнім секретом.

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

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

54 із третьої групи є справжньою річчю. Усі, крім двох, отримали оцінку D або F, бо один критичний знахідок обмежує оцінку до D, хай би що застосунок зробив правильно в усьому іншому. Найбільша окрема група всередині неї це ключ OpenAI, 33 штуки, і в нього немає налаштування, яке зробило б його безпечним у браузері.

Ключ, про який усі попереджають, виявився найрідкіснішим

Три застосунки. Стільки віддавали ключ service_role від Supabase, той, що проходить повз кожне правило таблиці, яке ви написали. Секретний ключ Stripe теж трапився тричі.

Поставте це поруч з іншою половиною того самого проходу. Із просканованих застосунків 8 429 називали проєкт Supabase, і обидві знахідки лежать усередині цієї однієї групи. Три з них віддавали ключ service_role. У 2 096 із них принаймні одна таблиця відповіла на запит зовсім без входу, а в 394 та таблиця мала назву про людей: users, profiles, customers, orders.

Обидва підрахунки походять із тих самих 8 429 застосунків із Supabase. Верхній рядок це та знахідка, про яку попереджають власників.

2 096 є нижньою межею. 4 749 із 8 429 так і не відповіли на нашу перевірку бази даних, з причин, яких ми ззовні не бачимо, і вони записані як невідомі, а не як чисті. Три ключі service_role є точним підрахунком, бо ключ читається з коду, який віддає кожен застосунок.

Чому попередження вказують не на той ключ

Ми бачимо лише зовнішній бік цих застосунків, тож не можемо сказати вам чому. Показати ми можемо те, яка помилка виживає.

Скопіювати не той ключ Supabase справді легко. У старішому проєкті anon і service_role стоять поруч в одній панелі дашборда, вони однакової довжини й однакової форми, і нічого не ламається, якщо взяти не той. І все ж це сталося лише тричі на 30 998 застосунків, бо ніщо вас туди не штовхає. Вставте будь-який із двох, і застосунок працює.

За відкритою таблицею стоїть сила. Row Level Security вмикають, застосунок перестає показувати дані, пишуть політику, яка дозволяє всім, щоб він знову запрацював, і з цієї миті дашборд повідомляє таблицю як захищену. Увімкнути це не те саме, що бути захищеним. Наше сканування оцінює таку таблицю як критичну знахідку в той самий день, коли ваш дашборд показує вам увімкнений перемикач і наявну політику.

Як безкоштовно знайти відкритий ключ API у своєму застосунку

Почніть руками, це коштує п’ять хвилин і не потребує нічого встановлювати. Відкрийте свій живий сайт, подивіться код сторінки й пошукайте чотири рядки: AIza для ключа Google, sk_ для секрету Stripe чи постачальника моделей, service_role для ключа Supabase, який ігнорує ваші правила, і eyJ для будь-якого токена Supabase. Усе, що повернеться, уже перебуває в руках кожного вашого відвідувача.

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

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

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

Що робити, у порядку, який підказують цифри

Що робити

  • Спершу визначте ключ, а вже потім реагуйте. Для Stripe і для новіших ключів Supabase відповідь дає префікс; для старішого ключа Supabase вирішує поле role усередині токена.
  • Якщо це ключ Google, обмежте його, а не ховайте. Обмеження за вебсайтами, ваш домен, лише ті API, якими користуєтесь, плюс денна межа квоти. Безкоштовно, близько п’яти хвилин, без змін у коді.
  • Якщо це справжній секретний ключ, спершу зробіть ротацію. Видалення з коду нічого не зачиняє, бо старе значення лишається у вашій історії версій і в кешованих копіях сайту.
  • Далі йдіть читати правила своїх таблиць, бо саме там цифри бачать справжню відкритість. Почніть із таблиць, де є люди.
  • Після будь-якого розкриття секретного ключа перевірте рахунки та журнали. Ротація зупиняє те, що станеться далі, і нічого не каже про те, що вже сталося.

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

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

Як дізнатися, чи витік мій ключ API?

Відкрийте свій живий сайт, подивіться код сторінки й пошукайте форми: AIza для ключа Google, sk_ для секрету Stripe чи постачальника моделей, і eyJ для JWT Supabase. Усе, що знайдеться, уже перебуває в руках кожного відвідувача. Наше безкоштовне сканування робить те саме читання ззовні й повідомляє, що бачить, приблизно за 20 секунд, без облікового запису для отримання оцінки.

Чи завжди зашитий у код ключ API є проблемою безпеки?

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

Мені кажуть, що мій ключ Supabase відкритий. Це той поганий?

Майже напевно ні. Обидва ключі Supabase схожі, тож прочитайте роль усередині токена або перевірте префікс: anon і sb_publishable_ мають бути публічними, service_role і sb_secret_ ні. Ми знайшли ключ service_role у 3 застосунках із 30 998, тож шанси добряче схиляються до нешкідливого. Якщо це все ж секретний, зробіть ротацію сьогодні в панелі Supabase.

Про що варто хвилюватися натомість?

Про правила на таблицях вашої бази даних. Із застосунків, де ми змогли завершити цю перевірку, більш ніж половина мала принаймні одну таблицю, що відповідала на запит зовсім без входу, а в 394 із них відкритою була таблиця з назвою про людей: users, profiles, customers, orders. Це значно частіше за будь-який ключ, що витік, і значно тихіше, бо застосунок працює точно так само в обох випадках.

Я прибрав ключ зі свого коду. Тепер закрито?

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

Автор

Vlad Tkachenko

Засновник Reeve

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

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

Читати далі

Усі статті

Не впевнені, як справи у вашому застосунку?

Запустіть безкоштовне сканування й отримайте зрозумілу оцінку від A до F приблизно за 20 секунд. Без облікового запису й без картки.

Сканувати безкоштовно

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