LLM API и интеграция
Как хранить API-ключи: где нельзя и что делать
Ключ к API — это платёжное средство: кто им владеет, тот тратит ваши деньги. Три места, где его чаще всего теряют, — исходный код, история коммитов и браузер. Первые два кажутся безопасными и не являются, третий небезопасен принципиально: всё, что попало на страницу, видит пользователь. Правильных мест два — переменные окружения и хранилище секретов, и оба работают только при условии, что ключ никогда не покидает сервер. Разбираем, где нельзя, что делать вместо, зачем разделять ключи по задачам и как действовать при утечке.
Три места, где ключи теряют
По частоте, а не по опасности — опасны все три одинаково.
В коде. Строка API_KEY = "..." прямо в файле. Работает, пока проект живёт на одной машине. Дальше файл попадает в репозиторий, репозиторий — к подрядчику, и ключ живёт своей жизнью.
В истории коммитов. Самый коварный случай: строку удалили, а в истории она осталась. Удаление файла не удаляет прошлые версии, и любой, у кого есть доступ к репозиторию, достаёт ключ одной командой.
Во фронтенде. Ключ в JavaScript, в переменной сборки, в запросе прямо со страницы. Здесь нет никакой защиты в принципе: всё, что приходит в браузер, читается инструментами разработчика. Обфускация не помогает — запрос всё равно уходит с ключом в заголовке, и его видно во вкладке сети.
Отдельно про «приватный репозиторий». Приватность — это настройка, которую меняют. Список доступа растёт, кто-то делает форк, кто-то подключает сторонний сервис для сборки. Ключ, положенный в приватный репозиторий, — это ключ с отложенной утечкой.
Где хранить правильно
Два варианта, и разница между ними — в масштабе, а не в безопасности.
Переменные окружения. Ключ задаётся в окружении процесса, код читает его оттуда и никогда не содержит значения. Подходит почти всем: от одного сервера до небольшого кластера. Обязательное условие — файл с переменными не попадает в репозиторий.
Хранилище секретов. Отдельный сервис, который выдаёт значения по запросу, ведёт журнал обращений и умеет менять ключи без перезапуска. Оправдан там, где серверов много, людей много и нужен ответ на вопрос «кто и когда брал этот секрет».
Общее правило для обоих: ключ живёт только на сервере. Если ваша схема требует, чтобы ключ оказался в браузере или в мобильном приложении, схему надо менять, а не искать способ его спрятать.
Что делать, если ключ нужен клиенту
Ситуация частая: приложение в браузере должно обращаться к модели. Решение стандартное и одно.
Ставьте свой сервер между клиентом и API. Браузер обращается к вашему серверу, сервер — к модели своим ключом. Клиент ключа не видит никогда.
Побочные выгоды такой схемы обычно перевешивают затраты на её написание:
- Свои лимиты. Можно ограничить число запросов на пользователя, а не полагаться на общие лимиты провайдера.
- Свои логи. Видно, кто и что спрашивал, — что писать в логи.
- Смена модели без релиза клиента. Маршрут решает сервер.
- Контроль расхода. Можно остановить дорогой сценарий, не выкатывая новую версию приложения.
Промежуточный сервер — это не «лишний слой», а место, где вообще возможен контроль. Без него у вас нет ни лимитов, ни логов, ни возможности что-то остановить.
Зачем разделять ключи
Один ключ на всё — удобно ровно до первого происшествия.
Раздельный учёт расхода. С отдельными ключами под сценарии видно, куда уходят деньги, без разбора логов. Вопрос «сколько нам стоит помощник в поддержке» получает ответ цифрой, а не оценкой — как считать по строкам, разбирали в материале про стоимость миллиона токенов.
Локализация утечки. Утёк ключ тестового стенда — отозвали его один. При общем ключе пришлось бы переподключать всё сразу, включая прод.
Разные права и лимиты. Разработке — низкий предел, проду — рабочий.
Разумный минимум разделения: отдельные ключи для прода, для разработки и для каждого автономного сценария вроде фоновой обработки или агента.
Что делать при утечке
Порядок действий, где первый шаг важнее всех остальных.
- Отозвать ключ. Немедленно, до разбирательства. Пока ключ жив, им пользуются.
- Выпустить новый и подставить в окружение. Не в код.
- Посмотреть расход за период. Так вы поймёте масштаб и время начала.
- Найти, откуда утёк. Репозиторий, логи, скриншот в переписке, сторонний сервис.
- Убрать из истории, если попал в репозиторий. Удаления строки недостаточно.
Типичная ошибка — переставить первый пункт на последнее место: сначала расследовать, потом отзывать. Расследование занимает часы, за которые расход продолжается.
Отдельно: не отправляйте ключ в мессенджеры, задачи и переписку. Ключ, пересланный «на минуту, чтобы проверить», остаётся в истории переписки навсегда и переживает всех участников разговора.
Что ещё стоит закрыть
Три вещи рядом с ключами, о которых вспоминают позже, чем нужно.
Логи. Заголовок с ключом попадает в лог запроса, лог уезжает в систему сбора, доступ к ней шире, чем к серверу. Заголовок авторизации надо вырезать при записи.
Сообщения об ошибках. Многие библиотеки печатают запрос целиком вместе с заголовками. В журнал ошибок это попадает автоматически.
Данные пользователей в запросах. Отдельная от ключей тема, но живёт там же: что именно вы отправляете в модель и что из этого хранится. Мы описываем свою сторону в llms.txt — и рекомендуем читать такой же документ у любого провайдера, прежде чем отправлять чувствительные данные.
Как заметить, что ключ утёк
Отдельный раздел, потому что чаще всего об утечке узнают из счёта, а это самый поздний из возможных моментов.
Три сигнала, которые видно раньше.
Расход не совпадает с нагрузкой. Ваш сервис обработал столько же запросов, что и вчера, а токенов ушло вдвое больше. Это первый и самый надёжный признак — при условии, что вы вообще сравниваете эти две величины.
Запросы идут в нерабочее время. У большинства сервисов нагрузка имеет форму: днём больше, ночью меньше. Ровный поток круглые сутки — повод посмотреть внимательнее.
Появились модели, которых вы не вызываете. Если в расходе видны позиции, которых нет в вашем коде, вопрос закрыт: ключом пользуется кто-то ещё.
Чтобы эти сигналы вообще были видны, нужны две вещи — разделение ключей по сценариям и регулярный взгляд на расход. Не разбор логов по инциденту, а привычка раз в неделю смотреть на цифры и понимать, откуда они.
Заведите себе ожидаемое значение: «наш помощник в поддержке тратит примерно столько-то в неделю». Пока такого ожидания нет, любая цифра выглядит нормальной, и утечка обнаруживается только когда кончаются деньги.
Отдельно стоит настроить предупреждение по расходу — порог, при превышении которого вам приходит уведомление. Это не защита, но это сокращает срок между утечкой и реакцией с недель до часов.
Чего мы не утверждаем
Не даём инструкцию под конкретное хранилище. Их много, и настройка различается; принцип одинаковый — ключ читается из окружения, а не из кода.
Не советуем по требованиям к персональным данным. Они зависят от вашего регламента и юрисдикции.
Не обещаем, что промежуточный сервер решает всё. Он переносит контроль на вашу сторону; пользоваться этим контролем всё равно придётся вам.
Отдельно про подрядчиков и временный доступ. Ключ, выданный внешнему разработчику «на время задачи», не перестаёт работать вместе с окончанием задачи — его надо отозвать руками, и это тот случай, о котором вспоминают через полгода. Правило простое: у каждого выданного наружу ключа есть дата, после которой он отзывается по умолчанию, а не по напоминанию.
Что держать в голове
Ключ — платёжное средство. Три места, где его теряют: код, история коммитов и браузер. Правильных мест два: переменные окружения и хранилище секретов, и оба работают, только пока ключ не покидает сервер.
Если приложению в браузере нужна модель — между ними ставится ваш сервер. Это не усложнение, а единственное место, где вообще возможны лимиты, логи и контроль расхода.
Как мы проверяем факты и что храним из запросов — на странице о проекте. Завести отдельные ключи под сценарии можно в кабинете: keydealer.ru/login.
Частые вопросы
Где хранить API-ключ?
В переменных окружения или в отдельном хранилище секретов, доступ к которому есть только у сервера. В коде, в репозитории и в браузере ключа быть не должно.
Почему нельзя держать ключ во фронтенде?
Потому что всё, что попадает в браузер, доступно пользователю. Ключ в JavaScript-коде или в запросе со страницы виден любому, кто откроет инструменты разработчика.
Можно ли положить ключ в приватный репозиторий?
Не стоит. Приватность репозитория меняется, доступ получают новые люди, а история коммитов хранит ключ даже после удаления строки.
Что делать, если ключ утёк?
Немедленно отозвать его и выпустить новый. Пытаться «отследить, кто воспользовался» до отзыва — потеря времени: пока ключ жив, им продолжают пользоваться.
Зачем нужны отдельные ключи под разные задачи?
Чтобы видеть расход по каждой раздельно и чтобы утечка одного не требовала переподключать всё сразу.