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

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

Ваш ключ API OpenAI відкритий у фронтенді. Замініть його.

Ключ API OpenAI, відкритий у фронтенді, не можна прив'язати до домену. Замініть його сьогодні, перенесіть виклик на свій сервер і обмежте витрати.

Vlad Tkachenko8 хв читання
Значення у формі ключа всередині сторінки коду застосунку, намальоване настільки довгим, що виходить за праву межу панелі, яка його тримає.

Коротко

  • Ключ API OpenAI, відкритий у вашому фронтенді, є тим випадком, коли тривога має рацію. Жодне налаштування не робить такий ключ безпечним у браузері.
  • Це токен на пред’явника, тож достатньо просто мати його. Немає обмеження за доменом, яке прив’язало б його до вашого сайту, як воно є для ключа Google.
  • Замініть його сьогодні в панелі OpenAI, перенесіть виклик за власний ендпоінт і поставте ліміт витрат на проєкт.
  • Ми знайшли такий ключ у 33 із 30 998 просканованих застосунків. Рідко, і всі 33 вийшли з оцінкою D або F.

Відкрийте свій застосунок у браузері, подивіться вихідний код сторінки й пошукайте в ньому sk-proj-. Якщо повернувся довгий рядок, ваш ключ API OpenAI відкритий у вашому фронтенді, і кожен відвідувач, який у вас коли-небудь був, міг його скопіювати.

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

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

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

Так. Це той випадок, коли тривога має рацію.

Ключ OpenAI є токеном на пред’явника, і саме слово несе все пояснення: хто його пред’являє, той може ним користуватися. Він подорожує в заголовку, який читається як Authorization: Bearer sk-proj-…, і сервери OpenAI не питають нічого більше. Ні який сайт його надіслав. Ні з якої країни він прийшов, ані чи відправник це ви.

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

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

Чому змінна середовища його не сховала

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

Це той крок, після якого люди впевнені, що ключ у безпеці. Ви винесли його з коду у файл .env, назвали VITE_OPENAI_API_KEY або NEXT_PUBLIC_OPENAI_API_KEY, і тепер код називає змінну там, де раніше стояв ключ. У відвантаженому застосунку не змінилося нічого. Інструмент збірки замінив цю змінну її значенням на виході, і значення лежить у тому JavaScript, який завантажує ваш відвідувач.

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

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

Чому його не можна обмежити так, як обмежують ключ Google

Бо OpenAI не пропонує обмеження такого роду.

Якщо ви читали про ключ API Google у фронтенді, ви натрапили на п’ятихвилинне рішення: відкрийте ключ у консолі Google Cloud, поставте обмеження за HTTP-реферером, і надрукований на вашій сторінці ключ працює на вашому сайті й повертає помилку скрізь інде. Ця порада правильна для ключа Google, і вона не переноситься.

У ключа OpenAI немає поля для «лише з yourapp.com». Ні списку дозволених доменів, ні перевірки реферера, ні обмеження за IP, яке пережило б браузер. Те, що OpenAI дає натомість, сидить на обліковому записі позаду ключа: до якого проєкту він належить, скільки цей проєкт може витратити за місяць і чи ключ ще існує. Це обмежує, скільки може коштувати вкрадений ключ. Сам ключ і далі працює звідусіль, доки ви його не видалите.

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

Що насправді робить dangerouslyAllowBrowser

Вимикає запобіжник, а її назва є документацією.

Офіційна JavaScript-бібліотека OpenAI за замовчуванням відмовляється працювати в браузері. README каже, що підтримка браузера є "disabled by default to avoid exposing your secret API credentials", а ввімкнення dangerouslyAllowBrowser "can be dangerous because it exposes your secret API credentials in the client-side code".

Якщо ваш застосунок викликає OpenAI з браузера, ця опція десь у вашому коді стоїть у true, бо без неї бібліотека не стартує. Хтось набрав слово dangerously, щоб позбутися повідомлення про помилку. Саме так цей знахідок зазвичай і з’являється, а бібліотека сказала вам про це першою.

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

Чого коштує те, що хтось знайшов ваш ключ

Рахунку, і застосунку, який перестає працювати.

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

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

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

Як викликати OpenAI зі свого застосунку, не відвантажуючи ключ

Поставте щось своє між вашим відвідувачем і OpenAI.

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

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

Де живе цей ендпоінт, залежить від того, чим ви будували:

  • Supabase у вашому стеку: Edge Function, з ключем, збереженим як secret у панелі Supabase.
  • Розгорнуто на Vercel або Netlify: serverless-функція під /api, з ключем у серверних змінних середовища проєкту.
  • Lovable, Bolt або Replit: у кожного є власне сховище секретів. Правило не змінюється, і пастка теж: сховище з написом «секрети» однаково віддає значення браузеру, якщо код, який його читає, виконується там.

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

Якщо ваш застосунок використовує Realtime API від OpenAI для голосу, браузеру справді потрібні облікові дані, і OpenAI документує, як їх йому дати: ваш сервер карбує короткоживучий client secret і передає сторінці саме його. Квиток і далі існує, він спливає за хвилини, і випустив його ваш власний сервер.

Як безкоштовно знайти кожен ключ, який відвантажує ваш застосунок

Почніть руками, бо це коштує п’ять хвилин і не потребує нічого встановлювати. Подивіться вихідний код свого живого сайту й пошукайте в ньому sk-proj- для ключа OpenAI, sk-ant- для ключа Anthropic, AIza для Google і eyJ для токена Supabase.

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

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

Дев’ять перевірок сканування, які лише читають, у порядку списку нижче. Кожну з них читають ззовні, так само як бачить ваш застосунок незнайомець.
На що дивиться скануванняНа яке питання воно відповідає
Секретні ключі у вашому кодіЧи є платний або адмінський ключ API, який може прочитати будь-хто?
Правила бази данихЧи може незнайомець без входу читати рядки ваших користувачів?
Приватні файлиЧи можна завантажити файли .env або дампи бази за URL?
Заголовки безпекиЧи ввімкнені захисти на боці браузера?
Бакети сховищаЧи може будь-хто перелічити файли, завантажені вашими користувачами?
Source mapsЧи опублікований ваш оригінальний код поруч із застосунком?
Відкриті API та CORSЧи відповідають ваші ендпоінти будь-якому сайту, що запитає?
Строк дії сертифікатаЧи чинний HTTPS і чи не спливає він просто зараз?
Продовження доменуЧи продовжено ім’я до того, як його зможе забрати хтось інший?

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

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

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

Що робити

  • Спершу замініть ключ, у панелі OpenAI в розділі API keys. Видалення його з коду не закриває нічого, бо старе значення лишається у вашій історії версій і в кожній кешованій копії сторінки.
  • Поставте місячний ліміт витрат на проєкт, до якого належить ключ. Це той єдиний важіль, що обмежує, у скільки може обійтися ця чи будь-яка наступна помилка.
  • Перенесіть виклик за власний ендпоінт і дайте цьому ендпоінту власний ліміт частоти. Браузер ніколи не має тримати ключ, що витрачає гроші.
  • Перегляньте сторінку використання за ті дні, коли ключ був живий. Заміна спиняє те, що станеться далі, і нічого не каже про те, що вже сталося.
  • Тримайте префікс VITE_ або NEXT_PUBLIC_ подалі від усього, що вам було б неприємно віддати на прочитання незнайомцеві. Ці префікси означають «публічний», і ваш інструмент збірки розуміє їх буквально.

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

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

Хтось знайшов мій ключ OpenAI у застосунку. Що робити першим?

Замінити його. Відкрийте панель OpenAI у розділі API keys, створіть новий ключ, покладіть новий на свій сервер і видаліть старий. Видалити його з коду це не те саме, бо старе значення лишається у вашій історії версій і в кожній кешованій копії сторінки. Далі поставте ліміт витрат на проєкт і перегляньте сторінку використання за ті дні, коли ключ був живий.

Чи можу я обмежити ключ OpenAI своїм доменом, як обмежую ключ Google?

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

У бібліотеці OpenAI є опція dangerouslyAllowBrowser. Чи робить вона ключ безпечним?

Ні, і назва є попередженням. OpenAI постачає підтримку браузера вимкненою, а власний README каже, що опція небезпечна, бо відкриває ваші секретні дані API в коді на боці клієнта. Її ввімкнення не змінює того, що може прочитати браузер; воно лише не дає бібліотеці відмовитися стартувати. Вузькі випадки, для яких вона задумана, це внутрішні інструменти з відомими користувачами та короткоживучі ключі для розробки, а не публічний застосунок.

Скільки хтось може витратити з ключем, який знайшов?

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

Я користувався ключем лише для швидкої демонстрації. Це все одно має значення?

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

Автор

Vlad Tkachenko

Засновник Reeve

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

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

Читати далі

Усі статті

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

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

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

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