Кальтер / Блог

API-ключи в ИИ-проекте: как хранить и не выложить в браузер

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

Материал подготовлен с помощью ИИ; источники и расчёты проверены редакцией ZASLON.

Ключ хранится на серверной стороне за границей доступа; браузер вызывает сервер, который обращается к внешнему API.
Редакционная схема ZASLON. Условная иллюстрация процесса, не скриншот сервиса.

Если браузеру нужен секретный credential для прямого вызова приватного API, этот credential станет доступен посетителям через JavaScript или сетевой запрос. Некоторые сервисы выдают публичные идентификаторы или ключи с ограничениями; они не заменяют секрет и не должны автоматически называться секретами. Проверь назначение значения в документации. Секрет провайдера храни на сервере, а backend пусть проверяет пользователя и допустимую операцию.

OpenAI рекомендует защищённое хранение ключей, ограничения доступа, мониторинг и ротацию; отдельно запрещает делиться одним ключом между участниками и советует отдельные ключи и срок действия (OpenAI API key safety). GitHub secret scanning помогает находить секреты в истории репозитория, но сканирование — дополнительный слой, а не место для хранения ключей (GitHub docs).

Что можно публиковать, а что нет

Публичная конфигурация — например, адрес backend, публичный идентификатор аналитики или флаг интерфейса. Она может быть видна в собранном JS.

Секрет — API key, bearer token, приватный signing key, пароль, webhook secret или credential, который позволяет совершить действие от имени владельца. Его нельзя класть в frontend-переменные и считать скрытым из-за минификации.

В Vite переменные с префиксом VITE_ доступны клиентскому коду; Next.js встраивает используемые NEXT_PUBLIC_ значения в браузерную сборку. Название вроде VITE_SECRET не делает значение секретным. Правила конкретного сборщика нужно проверять по документации и итоговому артефакту. Переменные Vite, переменные Next.js.

Минимальная схема

Браузер
  └─ POST /api/summary { text: ... }
        └─ Сервер проверяет пользователя, размер и лимит
              └─ Сервер читает API_KEY из secret storage
                    └─ Вызов провайдера

На сервере добавь аутентификацию, лимит размера, rate limit, проверку допустимой операции и управление расходами. Не возвращай клиенту ключ в ответе или ошибке. В логах не сохраняй заголовки авторизации, полный prompt с персональными данными или секретные конфигурации.

Проверь результат сборки

  1. Собери production-версию.
  2. Найди в dist/, сжатых JS-файлах и source maps названия и фрагменты секретных значений.
  3. Проверь сетевые запросы в браузере: нет ли ключа в URL, заголовке клиента или теле ответа.
  4. Проверь репозиторий и историю коммитов secret scanning инструментом.
  5. Убедись, что frontend вызывает только твой ограниченный endpoint.

Это контрольный список, не аудит конкретного проекта. Не вставляй настоящий секрет в тестовый пример.

Если ключ уже попал в клиент или Git

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

Раскрытый credential может остаться в source maps, истории Git, CI-логах, crash-reporting, скриншотах или URL. Удали доступные копии и не публикуй полный лог хостинга. Пользовательский токен для client-side auth отличается от секрета провайдера: он должен быть короткоживущим и иметь минимальный scope.

Секретное хранилище не решает проблему, если сервер принимает любой запрос анонимно и проксирует его с твоим ключом. Проверь пользовательскую авторизацию, лимит расходов, rate limit, допустимые модели/операции и ограничения на длину ввода. Иначе backend превратится в публичный прокси за твой счёт.

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

Разделяй среды и владельцев

Не используй один credential для локальной разработки, preview и production. Отдельные ключи помогают ограничить последствия утечки и понять, откуда пришёл запрос. Выдай каждому серверному компоненту только те права, которые ему нужны, и отключай неиспользуемые credentials. OpenAI рекомендует уникальные ключи для участников и управляемую ротацию; это особенно важно, когда проект перестаёт быть личным экспериментом.

Клиентский токен пользователя и секрет провайдера — разные вещи. Пользователь может авторизоваться в приложении, а backend — хранить отдельный server-to-server credential. Проверяй, что backend связывает пользователя с разрешённым объектом, а не просто добавляет провайдерский ключ к любому запросу. Интеграцию по токенам и scopes отдельно рассматривает безопасное подключение MCP.

Храни журнал ротации без значений ключей: дата отзыва, обновлённые среды и проверенный deployment.

Для любого оставшегося credential выбери минимальный scope, лимит расходов и понятный способ отзыва. После изменения сборки снова просканируй клиентский артефакт. Для разработки backend и интеграций смотри MK Ultra.