API-ключи и секреты, объяснённые для новичка
Что это
Многие инструменты, к которым подключается агент, требуют доказать, что это вы: GitHub, база данных, Sentry, платёжный сервис. Доказательство — это секрет: строка-пароль, которую сервис выдал именно вам. Виды, которые вы встретите:
- API-ключ / токен — длинная строка вроде
sk_live_...илиghp_.... Кто её знает — тот действует от вашего имени. - Строка подключения к БД (
postgresql://user:пароль@host/db) — внутри спрятан пароль, поэтому вся строка — секрет. - OAuth — вместо ключа вы разово логинитесь в браузере, и сервис сам выдаёт агенту временный доступ (часто безопаснее — постоянный ключ вообще не появляется).
Правило одно и простое: секрет — как ключ от квартиры. Не пишите его на двери. «Дверь» здесь — это ваш код, конфиги, логи, скриншоты и особенно то, что уходит в git.
Зачем так строго
Утёкший ключ = скомпрометированный ключ. Если ключ попал в закоммиченный файл, боты находят его в публичном репозитории за минуты и начинают тратить ваши деньги (инференс, облако) или читать ваши данные. Поэтому в движке есть жёсткий инвариант: «секрет никогда не попадает в файл значением; утёкший секрет считается скомпрометированным — ротируй». И этот принцип не просто написан в правилах — он вшит в то, как компилятор собирает пакет.
Плейсхолдер против значения — главная идея
Плейсхолдер — это имя-заготовка вида ${DATABASE_URI_RO} или ${GITHUB_MCP_PAT}. Оно говорит «сюда на твоей машине подставится настоящее значение», но самого значения не содержит. Движок раскладывает по пакету только плейсхолдеры. Настоящее значение вы кладёте отдельно — в переменные окружения на своём компьютере (обычно файл .env, который добавлен в .gitignore и никуда не уезжает).
Аналогия: пакет движка — это анкета, где написано «сюда впиши свой пароль», а не сам пароль. Анкету можно спокойно показать, переслать, закоммитить — пароля в ней нет.
Worked-пример: подключаем GitHub
Вход. Вы хотите, чтобы агент работал с вашими репозиториями, и включаете MCP-сервер github. Ему нужен персональный токен GitHub.
Что делает движок:
- В спеке появляется запись в списке нужных секретов (
secretsManifest) — только имяGITHUB_MCP_PATи пометка «обязательный», плюс ссылка на доку, где токен создать. - При сборке в
.mcp.jsonпопадает заголовок с плейсхолдером, а не с токеном:
"headers": { "Authorization": "Bearer ${GITHUB_MCP_PAT}" }
- В README и INSTALL движок выводит человекочитаемую строку: «
GITHUB_MCP_PAT(обязательный) — Personal access token GitHub с нужными scopes (repo/read)». Значения — нигде.
Что делаете вы (один раз, у себя):
- Заходите в GitHub → Settings → создаёте токен с минимальными правами (например только
repo:read, если агенту нужно лишь читать — это «принцип наименьших привилегий»). - Кладёте его в своё окружение:
export GITHUB_MCP_PAT=ghp_ваш_токен(или строкой в локальном.env).
Результат. Агент через ${GITHUB_MCP_PAT} подставляет ваш токен из окружения и работает с репозиториями. При этом в файлах, которые вы закоммитите или перешлёте, лежит только ${GITHUB_MCP_PAT} — сам токен физически не попал в артефакт. Если однажды токен всё же засветится — вы идёте в GitHub, отзываете старый, создаёте новый; менять код не нужно, плейсхолдер тот же.
Пять привычек новичка
- Секрет — только в окружение/
.env, не в код. И убедитесь, что.envв.gitignore. - Минимум прав. Нужен только просмотр — не давайте токен на запись/удаление.
- Read-only, где можно. Для базы движок по умолчанию берёт read-only режим и отдельную роль — агент не сможет ничего испортить.
- OAuth лучше вечного ключа. Многие пресеты (Vercel, Atlassian, Notion, Canva) авторизуются в браузере — постоянный ключ вообще не заводится.
- Засветился — сразу ротируй. Отозвать и пересоздать — минутное дело; последствия слитого ключа — нет.
Честная граница
Полного DRM не бывает: тот, кто установил движок, читает свой собственный plaintext-пакет — и это нормально, там нет чужих секретов, только ваши плейсхолдеры. Автоматическую проверку «секрет не утёк в диффе» перед сдачей движок предписывает как правило (secrets-hygiene, режим always); жёстко, кодом возврата, это блокируется пока только в Claude Code через hook, в остальных средах — как строгая инструкция.