Правило про секреты формулируется в одну строку: их не должно быть там, откуда модель их прочитает. Ключи не вставляют в запрос, не пишут в правила и навыки, не оставляют в исходниках и не показывают на скриншотах. Взамен есть нормальные каналы - системное хранилище учётных данных, переменные окружения с ограниченной областью и отдельное хранилище секретов облачных агентов. Разница между этими двумя списками и есть вся практическая безопасность работы с секретами. Первый список объединяет места, откуда значение уходит в переписку и остаётся там навсегда. Второй - места, где значение живёт ровно столько, сколько работает процесс, которому оно нужно.
Наивная защита - положить путь к файлу с ключами в исключения и считать вопрос решённым. Она даёт ложное спокойствие: исключения ограничивают функции редактора, но терминал и внешние инструменты работают в пределах собственных прав. Команда, запущенная агентом, читает файл ровно так же, как её прочитал бы любой процесс с вашими правами. Внешний сервер, которому дали учётные данные, тем более не спрашивает у редактора разрешения. Значит, секрет защищён не тем, что он скрыт от подсказок, а тем, что его вообще нет в доступной области.
Полезно один раз свести типы секретов и правильные каналы в таблицу. Ниже такая карта: локальный ключ, ключ для командной строки, секрет облачного окружения, учётные данные внешнего сервера, секрет сборки. У каждого - свой правильный канал и своя типичная ошибка: закоммиченный профиль оболочки, история команд с открытым значением, снимок окружения вместе с локальным файлом переменных, адрес с логином и паролем внутри, секрет, оставшийся в слое образа или в логе. Ошибки в третьей колонке объединяет одно свойство: во всех случаях значение переживает тот момент, ради которого его достали.
| Секрет | Правильный канал | Чего избегать |
|---|---|---|
| Локальный ключ API | Системное хранилище или окружение одного процесса | Закоммиченный профиль оболочки и вставка в запрос |
| Ключ для командной строки | Переменная на один процесс или секрет CI | История команд с открытым значением |
| Секрет облачного окружения | Хранилище секретов облачных агентов | Снимок окружения вместе с локальным файлом переменных |
| Учётные данные внешнего сервера |
| OAuth, переменные окружения, защищённые заголовки |
| Логин и пароль в адресе, закоммиченный JSON |
| Секрет сборки | Секреты окружения сборки | Лог выполнения и слой образа |
|---|
Как это выглядит в реальной работе, видно на простом сценарии. Человек просит агента проверить, почему сервис не подключается к внешнему API. Агент печатает переменные окружения, чтобы посмотреть, что там задано, и ключ оказывается в выводе команды. Дальше вывод попадает в контекст разговора, оттуда - в сохранённую запись сессии, а если задача выполнялась в облаке, ещё и в её журнал. Одно диагностическое действие превратило секрет одного процесса в текст, который лежит в нескольких местах и переживёт и задачу, и разговор. Никакого взлома здесь не было, была обычная отладка.
Запись разговора можно ещё и раздать по ссылке. Расшифровку открывают либо для команды, где её видят только вошедшие участники и она попадает в общий список, либо публично, и тогда доступ есть у любого, кто получил адрес, без всякого входа. На такие ссылки действует суточный лимит в пятьдесят штук. Известные форматы ключей, токенов и паролей при публикации вычищаются, но вычистка заявлена как посильная: значение без узнаваемого префикса или собственного формата она пропустит. С режимом без хранения данных общие ссылки не работают вовсе. Порядок отсюда простой: сначала перечитать, что именно уходит наружу, потом открывать доступ.
Отсюда следует, что делать при подозрении на утечку. Удалить строку недостаточно, потому что значение уже могло разойтись. Правильная последовательность: отозвать учётные данные, затем проверить историю репозитория, логи, транскрипты сессий, артефакты сборки и внешние действия, которые могли быть совершены. Отзыв - первое действие, а не последнее: пока ключ жив, всё остальное расследование не мешает им пользоваться. И считать надо не по факту, а по возможности: если значение могло попасть в место, которое вы не контролируете, оно скомпрометировано.
Командная работа добавляет неочевидный риск. Если разрешить продолжение чужой облачной задачи, другой человек может направлять процесс, внутри которого живут секреты и права создателя. Официальная документация сравнивает это с общими ключами доступа - сравнение точное. Для общих агентов правильнее заводить служебную учётную запись с минимальными правами, а не делиться своей. Разница в том, что после инцидента служебный доступ отзывают одной операцией, не трогая рабочий день человека.
Из этого следует общий принцип разделения. У агента должны быть свои учётные данные с узкой областью действия, а не ваши личные. Тогда компрометация ограничена тем, что этому агенту действительно нужно, и отзыв ничего не ломает вокруг. Это то же правило наименьших полномочий, что и для людей, просто применённое к процессу, который читает недоверенный текст и действует по нему. Личный ключ разработчика обычно шире, чем задача агента, ровно на то, что этому агенту в руки давать не собирались.
У файла исключений при этом остаётся своя настоящая роль, и её не надо путать с защитой. Он полезен как гигиена контекста: не даёт затащить в запрос гигантские артефакты сборки, локальные дампы и чужие каталоги зависимостей, из-за которых нужное теряется в шуме. Границу проводят так: исключения решают, что модель увидит по умолчанию, а песочница и разрешения решают, что процесс сможет сделать. Первое влияет на качество ответа, второе - на последствия. Ждать от первого второго - и есть та ошибка, с которой начинается большинство инцидентов.
Инженерный вывод простой: секреты не защищают исключениями и правилами - их держат вне досягаемости. Всё остальное - подстраховка. Правила полезны как направление, исключения полезны как чистота контекста, но ни то, ни другое не мешает процессу с правами прочитать файл на диске. Проверяется это одним вопросом к каждому секрету: если агент прямо сейчас запустит команду, печатающую всё окружение, попадёт ли это значение в вывод. Если да, механизм выбран не тот.
Типичные провалы предсказуемы. Считать исключения защитой от терминала и внешних инструментов. Вставить ключ в запрос ради быстрой проверки и оставить его в записи сессии. Разрешить продолжение своей облачной задачи коллегам, не заведя служебный аккаунт. При утечке начать с уборки в репозитории вместо отзыва ключа. И отлаживать конфигурацию печатью всего окружения там, где хватило бы проверки одного имени.