Почему хранить OPENAI_API_KEY=sk-… в коде и .env — плохая идея
.env выглядит безопасно: файл лежит в .gitignore, в репозиторий не попадает, «никто не увидит». На практике ключ чаще всего утекает не через сам файл, а через десяток мест, куда переменные окружения проходят насквозь, не спрашивая вас.
.env кажется безопасным — и в этом ошибка
.gitignore закрывает ровно один канал утечки: случайный коммит файла целиком. Но переменная окружения, однажды загруженная в процесс, живёт в памяти этого процесса и доступна всему, что имеет право с этим процессом взаимодействовать — от систем мониторинга до соседних процессов на той же машине. Файл можно спрятать; то, что процесс с ним делает дальше, — нет.
Куда утекает переменная окружения, даже если .env не коммитили
| Канал | Как это происходит |
|---|---|
| Логи ошибок / мониторинг | Sentry, Datadog и подобные сервисы по умолчанию прикладывают к отчёту о падении полное окружение процесса — включая ключи. |
| CI | printenv/env в отладочном шаге пайплайна, дамп переменных при падении сборки — всё это оседает в логах CI, которые часто доступны шире, чем сам репозиторий. |
| Docker | docker inspect показывает ENV-переменные контейнера открытым текстом; если ключ передан через ENV в Dockerfile — он застревает в слоях образа и уезжает в registry вместе с ним. |
/proc/<pid>/environ | На многопользовательском сервере любой процесс с тем же UID может прочитать окружение вашего процесса напрямую из /proc. |
| Клиентские бандлы | Сборщик (Next.js, Vite) подставляет переменные с префиксом NEXT_PUBLIC_/VITE_ прямо в JS, который уходит в браузер каждого посетителя — опечатка в префиксе превращает секрет в публичный. |
| «Скрипт на минутку» | test_gpt.py с ключом прямо в аргументе командной строки уходит в bash history и в вывод ps aux. |
Проверьте себя: это уже происходит?
- Поищите свой ключ (или его начало, например
sk-proj-…) в~/.bash_historyи~/.zsh_history. - Откройте последний упавший запрос в Sentry/Datadog и посмотрите, что попало в поле окружения — часто там прямо в открытом виде.
- Прогоните
docker history <image>для образов, где ключ передавался черезENVна этапе сборки. - Поищите свой ключ через GitHub code search — если он хоть раз был закоммичен и запушен, он может там остаться даже после удаления коммита.
Что делать вместо этого
Проблема не в имени файла — в том, что переменная окружения содержит настоящий, ничем не ограниченный ключ. Решение — положить в OPENAI_API_KEY не сам ключ, а отзываемый токен credential proxy:
# было
OPENAI_API_KEY=sk-proj-...настоящий...
# стало — тот же код, та же переменная, другое значение
OPENAI_API_KEY=vlt_openai_...
OPENAI_BASE_URL=https://api.proxykey.org/p/openai
Название переменной и код приложения не меняются — меняется то, что именно в ней лежит. Если этот .env всё равно утечёт (а по каналам выше — рано или поздно утечёт), наружу уйдёт отзываемый токен, а не ключ. Подробнее о том, как устроена сама подстановка, — в статье про API key injection.
Частые вопросы
Разве не проще просто получше спрятать .env?
.gitignore закрывает только один канал — случайный коммит. Он не защищает от логов мониторинга, дампа CI, docker inspect или чтения /proc/pid/environ другим процессом на той же машине — а именно так чаще всего утекают переменные окружения на практике.
А если ключ уже утёк?
Сначала отзовите его у провайдера — это останавливает ущерб немедленно. Дальше — по чеклисту: оценить, что было доступно, найти источник, почистить историю. Подробный порядок — в статье про утечку ключа OpenAI.
Работает ли это для ключей не от OpenAI?
Да — все каналы утечки одинаково применимы к любому ключу: Stripe, Anthropic, GitHub-токену, токену Telegram-бота. Формат sk-… специфичен для OpenAI, сама проблема — нет.
Замените значение, не код
Заведите ключ в proxykey, получите проходку вида vlt_… и подставьте её в ту же переменную окружения. Бесплатно, без карты.