LLM API и интеграция

Как хранить API-ключи: где нельзя и что делать

Как хранить API-ключи: где нельзя и что делать

Ключ к API — это платёжное средство: кто им владеет, тот тратит ваши деньги. Три места, где его чаще всего теряют, — исходный код, история коммитов и браузер. Первые два кажутся безопасными и не являются, третий небезопасен принципиально: всё, что попало на страницу, видит пользователь. Правильных мест два — переменные окружения и хранилище секретов, и оба работают только при условии, что ключ никогда не покидает сервер. Разбираем, где нельзя, что делать вместо, зачем разделять ключи по задачам и как действовать при утечке.

Три места, где ключи теряют

По частоте, а не по опасности — опасны все три одинаково.

В коде. Строка API_KEY = "..." прямо в файле. Работает, пока проект живёт на одной машине. Дальше файл попадает в репозиторий, репозиторий — к подрядчику, и ключ живёт своей жизнью.

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

Во фронтенде. Ключ в JavaScript, в переменной сборки, в запросе прямо со страницы. Здесь нет никакой защиты в принципе: всё, что приходит в браузер, читается инструментами разработчика. Обфускация не помогает — запрос всё равно уходит с ключом в заголовке, и его видно во вкладке сети.

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

Где хранить правильно

Два варианта, и разница между ними — в масштабе, а не в безопасности.

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

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

Общее правило для обоих: ключ живёт только на сервере. Если ваша схема требует, чтобы ключ оказался в браузере или в мобильном приложении, схему надо менять, а не искать способ его спрятать.

Что делать, если ключ нужен клиенту

Ситуация частая: приложение в браузере должно обращаться к модели. Решение стандартное и одно.

Ставьте свой сервер между клиентом и API. Браузер обращается к вашему серверу, сервер — к модели своим ключом. Клиент ключа не видит никогда.

Побочные выгоды такой схемы обычно перевешивают затраты на её написание:

  • Свои лимиты. Можно ограничить число запросов на пользователя, а не полагаться на общие лимиты провайдера.
  • Свои логи. Видно, кто и что спрашивал, — что писать в логи.
  • Смена модели без релиза клиента. Маршрут решает сервер.
  • Контроль расхода. Можно остановить дорогой сценарий, не выкатывая новую версию приложения.

Промежуточный сервер — это не «лишний слой», а место, где вообще возможен контроль. Без него у вас нет ни лимитов, ни логов, ни возможности что-то остановить.

Зачем разделять ключи

Один ключ на всё — удобно ровно до первого происшествия.

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

Локализация утечки. Утёк ключ тестового стенда — отозвали его один. При общем ключе пришлось бы переподключать всё сразу, включая прод.

Разные права и лимиты. Разработке — низкий предел, проду — рабочий.

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

Что делать при утечке

Порядок действий, где первый шаг важнее всех остальных.

  1. Отозвать ключ. Немедленно, до разбирательства. Пока ключ жив, им пользуются.
  2. Выпустить новый и подставить в окружение. Не в код.
  3. Посмотреть расход за период. Так вы поймёте масштаб и время начала.
  4. Найти, откуда утёк. Репозиторий, логи, скриншот в переписке, сторонний сервис.
  5. Убрать из истории, если попал в репозиторий. Удаления строки недостаточно.

Типичная ошибка — переставить первый пункт на последнее место: сначала расследовать, потом отзывать. Расследование занимает часы, за которые расход продолжается.

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

Что ещё стоит закрыть

Три вещи рядом с ключами, о которых вспоминают позже, чем нужно.

Логи. Заголовок с ключом попадает в лог запроса, лог уезжает в систему сбора, доступ к ней шире, чем к серверу. Заголовок авторизации надо вырезать при записи.

Сообщения об ошибках. Многие библиотеки печатают запрос целиком вместе с заголовками. В журнал ошибок это попадает автоматически.

Данные пользователей в запросах. Отдельная от ключей тема, но живёт там же: что именно вы отправляете в модель и что из этого хранится. Мы описываем свою сторону в llms.txt — и рекомендуем читать такой же документ у любого провайдера, прежде чем отправлять чувствительные данные.

Как заметить, что ключ утёк

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

Три сигнала, которые видно раньше.

Расход не совпадает с нагрузкой. Ваш сервис обработал столько же запросов, что и вчера, а токенов ушло вдвое больше. Это первый и самый надёжный признак — при условии, что вы вообще сравниваете эти две величины.

Запросы идут в нерабочее время. У большинства сервисов нагрузка имеет форму: днём больше, ночью меньше. Ровный поток круглые сутки — повод посмотреть внимательнее.

Появились модели, которых вы не вызываете. Если в расходе видны позиции, которых нет в вашем коде, вопрос закрыт: ключом пользуется кто-то ещё.

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

Заведите себе ожидаемое значение: «наш помощник в поддержке тратит примерно столько-то в неделю». Пока такого ожидания нет, любая цифра выглядит нормальной, и утечка обнаруживается только когда кончаются деньги.

Отдельно стоит настроить предупреждение по расходу — порог, при превышении которого вам приходит уведомление. Это не защита, но это сокращает срок между утечкой и реакцией с недель до часов.

Чего мы не утверждаем

Не даём инструкцию под конкретное хранилище. Их много, и настройка различается; принцип одинаковый — ключ читается из окружения, а не из кода.

Не советуем по требованиям к персональным данным. Они зависят от вашего регламента и юрисдикции.

Не обещаем, что промежуточный сервер решает всё. Он переносит контроль на вашу сторону; пользоваться этим контролем всё равно придётся вам.

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

Что держать в голове

Ключ — платёжное средство. Три места, где его теряют: код, история коммитов и браузер. Правильных мест два: переменные окружения и хранилище секретов, и оба работают, только пока ключ не покидает сервер.

Если приложению в браузере нужна модель — между ними ставится ваш сервер. Это не усложнение, а единственное место, где вообще возможны лимиты, логи и контроль расхода.

Как мы проверяем факты и что храним из запросов — на странице о проекте. Завести отдельные ключи под сценарии можно в кабинете: keydealer.ru/login.

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

Где хранить API-ключ?

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

Почему нельзя держать ключ во фронтенде?

Потому что всё, что попадает в браузер, доступно пользователю. Ключ в JavaScript-коде или в запросе со страницы виден любому, кто откроет инструменты разработчика.

Можно ли положить ключ в приватный репозиторий?

Не стоит. Приватность репозитория меняется, доступ получают новые люди, а история коммитов хранит ключ даже после удаления строки.

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

Немедленно отозвать его и выпустить новый. Пытаться «отследить, кто воспользовался» до отзыва — потеря времени: пока ключ жив, им продолжают пользоваться.

Зачем нужны отдельные ключи под разные задачи?

Чтобы видеть расход по каждой раздельно и чтобы утечка одного не требовала переподключать всё сразу.

Источники