AI · 7 мин чтения

API-ключ OpenAI: где хранить, чтобы он не утёк в репозиторий

API-ключ OpenAI, по сути, чек на предъявителя: у кого он есть, тот и тратит деньги с привязанной картой или балансом аккаунта. Я не раз видел ситуации в духе «у меня списалась крупная сумма, я ничего не делал» - и почти всегда причина одна: ключ лежал в публичном репозитории, в JS-коде на сайте или в открытом конфиге на сервере, и кто-то его нашёл раньше, чем владелец спохватился.

В статье раскладываю по полочкам, где хранить API-ключ OpenAI на своей машине, где на сервере, как передавать его в CI/CD пайплайн, как ограничивать доступ по ключу и что делать по шагам, если ключ всё-таки утёк.

Зачем прятать API-ключ OpenAI и что будет, если он утечёт

Ключ OpenAI не привязан к IP-адресу или домену - он просто открывает доступ к API от имени вашего аккаунта, с вашим лимитом и вашей картой. Никакой дополнительной авторизации не требуется: указал ключ в заголовке запроса - получил доступ ко всем моделям, подключённым на аккаунте.

Публичные репозитории на GitHub боты сканируют в течение минут после пуша: ищут по характерным маскам ключи популярных сервисов, включая OpenAI. У GitHub есть программа поиска утёкших секретов, и по ключам OpenAI она умеет сигнализировать о находке автоматически - но полагаться на это как на единственную страховку не стоит, потому что к моменту уведомления ключ уже мог засветиться в истории коммитов и достаться кому-то раньше.

На практике встречал три типичных сценария утечки: ключ забыли в .env и закоммитили вместе с остальным проектом, ключ вставили прямо в JS-код на странице Tilda в настройках сайта или в блоке с кастомным кодом - Tilda выполняет этот скрипт в браузере посетителя, а значит любой человек откроет консоль разработчика и увидит ключ открытым текстом, и ключ захардкодили в исходнике aiogram-бота, который потом выложили на GitHub как часть портфолио.

Ключ на локальной машине: .env и переменные окружения

Для локальной разработки самый рабочий вариант - файл .env в корне проекта и .gitignore, который его туда не пускает. Это касается любого стека: скрипта на Python, бэкенда на Laravel или бота на aiogram.

OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxx

Файл .env добавляю в .gitignore ещё до первого коммита - если сделать это позже, ключ уже попадёт в историю git, и одного удаления файла будет мало, придётся переписывать историю репозитория.

.env
.env.local

В коде ключ читаю только из переменных окружения, никогда не пишу его строкой в исходнике. Для Python-скриптов и aiogram-ботов использую пакет python-dotenv, который подгружает значения из .env в окружение процесса при старте:

import os
from dotenv import load_dotenv

load_dotenv()
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]

Если в проекте несколько разработчиков, вместо реального ключа в репозиторий кладу файл .env.example с пустыми значениями переменных - так новый человек в команде видит, какие переменные вообще нужны, и не тратит время на переписку в духе «а какой у нас ключ».

Отдельно про фронтенд: ключ OpenAI никогда не должен попадать в код, который выполняется в браузере. Это касается не только кастомного кода на Tilda - любой клиентский JS, React-компонент или Vue-виджет, если в него зашит ключ, отдаёт его вместе со страницей всем, кто откроет исходный код или вкладку «Сеть» в инструментах разработчика. Запросы к OpenAI с фронтенда должны идти через свой бэкенд, который уже добавляет ключ на сервере, а не через прямой вызов из браузера.

Где хранить ключ: короткая шпаргалка

Где Как хранится Как передаётся в код
Локальная машина .env в .gitignore os.environ / env()
Продакшен-сервер .env вне веб-корня или переменные окружения процесса конфиг фреймворка или env()
CI/CD пайплайн secrets репозитория (GitHub Actions, GitLab CI) переменная окружения шага сборки
n8n встроенное хранилище credentials ссылка на credential в ноде, не значение
Клиентский JS в браузере нигде - ключ там быть не должен запрос идёт через свой бэкенд

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Хранение секретного токена OpenAI на сервере

На проде подход тот же, но добавляется ещё один слой: файл или переменные должны быть недоступны снаружи и невидимы в логах.

Для проектов на PHP-хостинге - обычная связка для доработок Tilda и сайтов на WordPress - храню ключ в файле .env, который лежит вне публичной директории сайта или закрыт правилом в .htaccess, запрещающим отдавать файлы с точкой в начале имени. В Laravel-проектах ключ читаю через env('OPENAI_API_KEY') и обращаюсь к нему из конфига, а не напрямую из кода - так значение можно централизованно переопределить для тестового и боевого окружения.

Если задача требует отдельного сервера - например бота на aiogram или обработки на Python вроде парсинга и генерации контента через API - ключ передаю через переменные окружения самого процесса, а не через файл, который может случайно попасть в бэкап или архив с логами. Отдельный VPS - это самостоятельная инфраструктура, которую нужно администрировать, обновлять и оплачивать отдельно от хостинга сайта, и это отдельная статья расходов помимо тарифа за сайт. Обычный shared-хостинг для сайта на Tilda или WordPress для этого не подходит и не нужен, если задача не требует именно фонового процесса.

Для сценариев в n8n ключ добавляю через встроенное хранилище credentials - n8n шифрует эти значения и не показывает их в экспорте workflow, в отличие от параметра, вписанного прямо в поле ноды HTTP Request. Если нужно собрать конвейер на n8n с обращением к OpenAI, я делаю автоматизацию сценариев в n8n именно так, чтобы ключ не утёк вместе с файлом экспорта, который кто-то потом выложит в общий чат как пример настройки.

API-ключ в CI/CD: GitHub Actions и GitLab CI

Если сборка или деплой дёргает OpenAI API - например прогоняет тесты промптов или генерирует контент на этапе сборки - ключ передаю через встроенное хранилище секретов конвейера, а не через файл в репозитории.

В GitHub Actions это Repository secrets в настройках репозитория: оттуда значение подставляется в шаг как переменная окружения и автоматически маскируется в логах сборки звёздочками, если скрипт попытается его вывести. В GitLab CI то же самое делают защищённые и маскированные переменные в настройках проекта.

Основная ошибка, которую видел в чужих пайплайнах, - вывод ключа в лог ради отладки, обычный echo переменной или print в скрипте, который потом гоняется на каждый пуш. Даже если сам репозиторий приватный, лог сборки нередко доступен более широкому кругу людей, чем сам код: подрядчикам, сторонним CI-раннерам, интеграциям с уведомлениями в Slack или Telegram, куда лог может улететь целиком при ошибке сборки.

Ограничение и ротация ключа доступа OpenAI

В личном кабинете OpenAI ключи можно создавать отдельно под каждый проект или сервис, а не использовать один и тот же ключ везде. Тогда утечка одного ключа не даёт доступ ко всем интеграциям сразу - можно отозвать скомпрометированный ключ, а остальные сервисы продолжат работать без простоя.

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

Ключи, которые перестали использоваться - тестовый скрипт, закрытый проект, стажёр, который ушёл из команды - лучше отзывать сразу, а не оставлять «на всякий случай». Неактивный ключ с рабочим доступом - это лишняя точка утечки, которая просто ждёт своего часа.

Что делать, если ключ OpenAI утёк

  1. Отозвать ключ в личном кабинете OpenAI немедленно - это останавливает трату денег быстрее любого другого действия.
  2. Создать новый ключ и обновить его во всех местах, где использовался старый: .env на сервере, секреты CI/CD, credentials в n8n, конфиги ботов.
  3. Проверить историю запросов и биллинг на аномалии - резкий рост числа запросов или расходов в период, когда никто из команды не работал с API.
  4. Если ключ попал в git-историю, удалить только файл новым коммитом недостаточно - старое значение останется в истории репозитория. Историю нужно переписать, например инструментом вроде git filter-repo, и сделать force-push, предупредив всех, кто клонировал репозиторий, что ветку нужно перекачать заново.
  5. Проверить .gitignore и добавить туда .env, если этого не было сделано с самого начала.
  6. Если ключ утёк через сайт на Tilda или другой публичный фронтенд, убрать его из кода страницы и перенести обращение к OpenAI на серверную часть, чтобы ключ больше не отдавался вместе со страницей.

Условия и возможности OpenAI меняются, поэтому перед запуском я уточняю их у самого сервиса.

Частые вопросы

Можно ли хранить ключ OpenAI в базе данных проекта?

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

Что будет, если ключ OpenAI полежит в публичном репозитории пару часов?

Зависит от того, найдут ли его раньше вас. Публичные репозитории сканируют боты, которые ищут ключи по характерным маскам, поэтому пары часов вполне достаточно, чтобы кто-то забрал ключ и начал использовать его для своих задач за ваш счёт. Надёжный ответ один - отозвать ключ сразу, как заметили утечку, а не ждать, что пронесёт.

Нужен ли отдельный сервер, чтобы безопасно хранить ключ OpenAI?

Нет, если проект и так работает на обычном хостинге с PHP - файла .env вне публичной директории достаточно. Отдельный VPS нужен только тогда, когда сама задача требует фонового процесса на Python или Node.js, например бота на aiogram, парсера или сценария в n8n, а не для того, чтобы просто спрятать один ключ.

Как понять, что ключ OpenAI уже утёк?

Первый сигнал - неожиданный рост расходов или количества запросов в личном кабинете OpenAI, который не совпадает с активностью команды. Второй - уведомление от GitHub о найденном в репозитории секрете, если ключ случайно попал в коммит публичного проекта. Периодическая проверка биллинга и истории использования ключа - простая привычка, которая экономит нервы.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно