По данным GitGuardian (State of Secrets Sprawl), только за 2024 год в публичный GitHub утекло около 23,7 млн секретов — ключей API, токенов, паролей. Подавляющее большинство — не результат взлома, а обычные коммиты, логи и клиентские бандлы. Свежий ключ, попавший в публичный репозиторий, боты начинают пробовать меньше чем через минуту.
Вывод, к которому мы пришли после N-го инцидента: бороться за то, чтобы ключ не утёк — проигранная война. Выигрышная — сделать утечку бесполезной.
Почему ключи утекают всегда
Один ключ OpenAI в среднем проекте живёт в пяти местах одновременно:
в
.envна ноутбуках разработчиков;в секретах CI (и иногда в логах CI —
printenvв отладочном шаге);в конфиге продакшена;
в скриптах «на минутку» (
test_gpt.pyс ключом в строке);и с недавних пор — в контексте ИИ-агента, который «пишет код сам».
Каждая копия — независимая точка отказа. Классические меры работают, но у каждой есть предел:
Мера | Что закрывает | Что не закрывает |
|---|---|---|
| случайный коммит | логи, CI, агентов, «скрипт на минутку» |
Сканеры (gitleaks, trufflehog) | утечку в git до пуша | все остальные каналы |
Секрет-менеджеры (Vault, ASM) | хранение и доставку | ключ всё равно в runtime-окружении приложения |
Скоуп-ключи провайдера | радиус поражения | есть не у всех провайдеров; отзыв всё равно ломает всех потребителей ключа |
Общая черта: во всех схемах настоящий ключ в какой-то момент оказывается у потребителя.
А значит, утекает вместе с ним
Схема: credential proxy
Идея не новая (так работают, например, платёжные токенизаторы), но для API-ключей она почему-то почти не применяется. Разрываем связь «приложение — ключ»:
приложение ──(vlt_проходка)──▶ прокси ──(настоящий ключ)──▶ провайдер │ статус / IP / лимиты → лог
Секрет — настоящий ключ. Вводится один раз, шифруется, наружу не возвращается никогда и никаким API.
Проходка (pass) — виртуальный токен, привязанный к секрету. Своя привязка к IP, свои лимиты rpm/rpd, свой срок жизни, свой лог. Именно её получают приложения, скрипты и агенты.
Прокси — проверяет проходку, расшифровывает секрет в памяти на время одного запроса, подставляет и стримит ответ обратно.
Следствия:
Зона, где может утечь оригинал, сжимается до одного сервера.
Утечка проходки — не инцидент: с чужого IP — 403, сверх лимита — 429, отзыв — один клик, оригинал не тронут.
Побочный бонус: видно, кто и сколько ходит по каждому потребителю, потому что у каждого — своя проходка.
Для кода переход — две строки: api.openai.com → <прокси>/p/openai, sk-… → vlt_…. Путь, тело, заголовки и SSE-стриминг проходят как есть.

Как это устроено внутри (наша реализация)
Мы собрали такую схему как сервис — proxykey (Node 22 + Fastify 5 + PostgreSQL + Redis). Технические решения, которые могут быть интересны независимо от продукта:
Конвертное шифрование: на каждый секрет — свой DEK (AES-256-GCM, AAD = id секрета), DEK завёрнут KEK’ом из окружения. Расшифровка — в памяти на время одного запроса, плейнтекст нигде не кэшируется и не логируется, DEK зануляется после использования.
Токены не хранятся: в базе только SHA-256-хэши проходок; горячий путь валидирует по Redis-кэшу (TTL 300 с) с фолбэком в Postgres.
Деградация по-разному в разные стороны: упал Postgres — работаем с кэша (fail-closed для некэшированных токенов); упал Redis — валидируем по Postgres, но рейт-лимиты открываются (fail-open), а валидация — нет.
SSRF-guard: кастомные base URL резолвятся и проверяются на приватные/метаданные-диапазоны, иначе прокси с пользовательскими апстримами — готовый SSRF-вектор.
Логи запросов: партиционированный по месяцам Postgres, метаданные без авторизационных заголовков и значений ключей.
Telegram-боты — отдельная боль: токен там живёт в пути URL, а aiogram/grammY валидируют его формат до первого запроса. Прокси принимает
bot<digits>:vlt_…и игнорирует цифры — формат проходит валидацию клиентских библиотек, настоящий токен подставляется на сервере.

ИИ-агенты: главный новый потребитель ключей
Отдельная причина, по которой мы вообще в это ввязались. Агенты (Claude Code, Cursor и т.д.) разворачивают сервисы и настраивают ботов — им постоянно нужны ключи. Но всё, что попало в контекст модели, надо считать опубликованным: контекст логируется, трейсится и выманивается промпт-инъекцией.
Решение — дать агенту не секрет, а инструмент: MCP-сервер хранилища. Агент подключается по URL с токеном mcp_… и умеет выписывать, ротировать, отзывать проходки и читать логи. Операции «прочитать настоящий ключ» в наборе инструментов просто нет — это свойство протокола доступа, а не обещание.
Любимый сценарий — «отложенный секрет»: агент разворачивает Telegram-бота, токена которого ещё не существует. Агент создаёт pending-проходку, прописывает её в конфиг, отдаёт человеку ссылку; человек вводит настоящий токен в панели — проходка активируется, бот оживает. Агент закончил работу, ни разу не увидев секрета.

Честные trade-offs
Прокси — критичная точка. Если он лежит, лежат все ваши вызовы. Лечится репликами и кэшем валидации, но это надо понимать.
Прокси видит трафик. Вопрос не «видит ли», а «что логирует». В нашей реализации — только метаданные; превью тел — опционально и по-проходочно. Если модель угроз не допускает третью сторону — разворачивайте такую схему у себя, паттерн воспроизводим.
Латентность. Один хоп + проверка по кэшу — единицы миллисекунд на фоне сотен миллисекунд генерации LLM. Для низколатентных не-LLM API считайте сами.
Диклеймер
Мы — авторы proxykey (proxykey.org). Сервис бесплатный, без карты; панель, прокси и MCP-сервер описаны в статье честно, включая ограничения. Схема credential proxy воспроизводима и без нас — если соберёте свою, главное не забудьте SSRF-guard и fail-closed на валидации.
