Безопасное хранение API-ключей: практики, а не только советы
«Не коммить ключи в git» слышали все. Разбираем, что делать дальше: лимиты на ключ, ограничение моделей и план действий, если ключ всё же утёк.
4 мин · · Обновлено · безопасность
«Не коммить ключи» — совет, который знают все и всё равно иногда нарушают: копипаста в публичный gist для вопроса на форуме, забытый .env в архиве проекта, скриншот терминала с открытым токеном. Разница между утечкой-неприятностью и утечкой-катастрофой определяется не тем, что ключ попал наружу, а тем, что было настроено заранее.
Ключ — это bearer-токен, а не пароль с MFA
Формат fetch_..., заголовок один и тот же для обоих режимов работы:
Authorization: Bearer fetch_твой_ключ
Anthropic-клиенты иногда шлют его как x-api-key — шлюз принимает оба варианта, поэтому нет смысла держать разные ключи под разные заголовки. Важно другое: у bearer-токена нет второго фактора. Кто угодно с этой строкой может делать запросы от твоего имени, пока ты его не отзовёшь. Ключ показывается в кабинете один раз при создании — если не сохранил его в менеджере паролей сразу, второй раз посмотреть нельзя, только выпустить новый.
Лимит на ключ — это не опция для параноиков, а базовая настройка
На странице ключа в /app/api-keys задаются дневной и месячный лимит расхода в долларах и список моделей, которые этому ключу разрешено вызывать. Настраивай оба параметра сразу при создании ключа, а не после инцидента:
- Ключ для дев-окружения — лимит вроде $2/день, доступ только к дешёвым моделям для тестов.
- Ключ для продакшена — лимит по факту ожидаемой нагрузки плюс запас на всплеск, но не «безлимит».
- Ключ для демо стороннему заказчику — жёсткий потолок, который физически не даст утечке вылиться в серьёзный счёт.
Если лимит превышен, запросы с этого ключа возвращают 429 с кодом key_daily_limit_reached или key_monthly_limit_reached — сервис не спишет больше выставленного порога, даже если запросов шлют тысячами. Если ключ вызывает модель не из разрешённого списка, ответ — 403 model_not_allowed_for_key. Это значит, что даже укравший ключ не сможет прогнать через него дорогую модель, если ты заранее не дал такого разрешения.
Где ключ живёт в коде
Переменные окружения, не константы в файле:
# .env — обязательно в .gitignore
FETCH_API_KEY=fetch_твой_ключimport os
api_key = os.environ["FETCH_API_KEY"]Для Claude Code — ANTHROPIC_AUTH_TOKEN (не ANTHROPIC_API_KEY, с ним клиент решит, что нужен логин через официальный аккаунт). Для инструментов на базе OpenAI SDK — OPENAI_API_KEY, независимо от того, какой провайдер реально стоит за адресом.
Перед первым коммитом стоит проверить историю репозитория, а не только текущее состояние файлов — ключ мог засветиться в старом коммите и остаться в истории даже после удаления из рабочей копии:
git log -p --all | grep -i "fetch_"Если что-то нашлось — считай ключ скомпрометированным независимо от того, публичный репозиторий или приватный: доступ мог быть у любого, кто когда-либо клонировал его.
Idempotency-Key защищает от повторного списания, не от кражи
Заголовок Idempotency-Key — это про надёжность, не про безопасность: повтор запроса с тем же значением за 24 часа вернёт тот же ответ без повторного списания. Полезно при сетевых обрывах и ретраях, но он не мешает третьей стороне с украденным ключом слать свои собственные запросы — это разные задачи, и подменять одно другим не стоит.
Мониторинг вместо надежды
Заголовки X-Zukko-Cost-Micros (стоимость запроса в микродолларах) и X-Fetch-Balance-Cents (остаток после списания — несмотря на название, тоже в микродолларах: 1 000 000 = $1) приходят в каждом обычном ответе; у потоковых (stream: true) их нет — там расход смотри в кабинете, /v1/usage или /v1/balance. Логируй их у себя — резкий скачок расхода без соответствующего роста трафика на твоей стороне обычно означает, что ключ используется не только тобой. Историю списаний с пагинацией (общую на весь аккаунт, по всем ключам) можно поднять отдельно:
curl https://api.zukko.pro/v1/usage \
-H "Authorization: Bearer fetch_твой_ключ"Если ключ всё-таки утёк
Порядок действий, а не паника:
- Зайти в
/app/api-keysи отозвать скомпрометированный ключ — доступ по нему обрывается сразу. - Выпустить новый ключ, сразу выставить лимиты и список разрешённых моделей.
- Проверить
/v1/usageза последние сутки на предмет запросов, которые не похожи на твои (история там общая на аккаунт, фильтра по ключу нет). - Обновить переменные окружения везде, где использовался старый ключ, и передеплоить.
Дешевле выпустить новый ключ при малейшем сомнении, чем гадать, сохранился ли старый где-то ещё в правильном виде.
Разделяй ключи по назначению
Один ключ на весь стек — самый частый способ превратить маленькую утечку в большую. Отдельный ключ на каждый проект или окружение с собственным лимитом ограничивает ущерб рамками одного проекта, а не всего аккаунта. Если что-то идёт не так, отозвать один ключ проще, чем разбираться, какие сервисы сломает отзыв общего.
Логи приложения — не менее частый источник утечки, чем git
Ключ редко утекает эффектно — чаще он просто оседает там, где его не ждали. Если middleware логирует заголовки входящих запросов для отладки, Authorization: Bearer fetch_... или x-api-key может целиком уйти в систему сбора логов вроде Sentry или Datadog, доступ к которой обычно шире, чем к самому репозиторию: туда заглядывают саппорт, аналитики, иногда подрядчики. Правило простое — маскировать значения этих заголовков до записи в лог, а не после:
SENSITIVE_HEADERS = {"authorization", "x-api-key"}
def sanitize_headers(headers: dict) -> dict:
return {
k: ("***" if k.lower() in SENSITIVE_HEADERS else v)
for k, v in headers.items()
}Та же логика касается трассировок ошибок: если исключение выводит тело запроса целиком, а тело содержит ключ в заголовках объекта запроса, стектрейс в системе мониторинга ошибок оказывается таким же местом утечки, как случайный коммит.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Агенты opencode: как настроить роли, модели и инструментыСвой агент в opencode — это роль, модель и список разрешений. Показываем конфиг и пример, где рутину делает дешёвая модель, а код правит сильная.
- Ключи OpenAI API в открытом доступе: почему ими нельзя пользоватьсяОткуда берутся «бесплатные» ключи на GitHub, почему пользоваться ими опасно и для вас, и для владельца, и как защитить собственный ключ от такой же утечки.
- Ошибки GLM API: почему не работает запрос и как это исправитьНеверный id модели, переполненный контекст 128K, ключ, баланс, лимиты: типичные причины ошибок GLM API и быстрый способ найти свою.
- Plan mode в Claude Code: сначала план, потом правки в кодеВ plan mode Claude Code читает код и предлагает план, но ничего не правит, пока вы не согласитесь. Как включить, использовать и во сколько это обходится.