У командной строки два разных сценария входа, и путать их дорого. Интерактивный - обычная команда входа, которая открывает браузер и сохраняет сессию: это для человека за терминалом. Программный - ключ, переданный флагом или переменной окружения: это для скриптов и непрерывной интеграции. Отдельно документирован токен для протокола взаимодействия с редакторами, а в окружении без графики помогает переменная, запрещающая открывать браузер, - иначе команда входа будет ждать браузер, которого там нет.
Наивная практика - задать ключ один раз на всё окружение и забыть. Она удобна ровно до того момента, когда в том же задании выполняется что-то ещё. А выполняется почти всегда: установка зависимостей, сборка, скрипты жизненного цикла пакетов. Всё это - код, управляемый репозиторием, и он читает окружение так же свободно, как ваш агент. Ключ, заданный на всё задание, доступен каждому шагу, включая тот, который добавили в зависимости на прошлой неделе и никто не читал.
Правильная форма - передавать ключ ровно тому процессу, которому он нужен. Ниже показано, как это выглядит: переменная задаётся перед конкретным вызовом и не живёт дольше него. Разница выглядит косметической, а на деле уменьшает радиус поражения при компрометации любого другого шага: секрет не лежит в общем окружении, где его может прочитать чужой скрипт.
Есть места, где ключ становится видимым помимо вашей воли, и их стоит знать поимённо. Значение, переданное флагом, попадает в строку запуска процесса, а список процессов на машине обычно доступен любому пользователю и часто пишется в логи сборки. Тот же флаг остаётся в истории команд оболочки, а история переживает сессию и уезжает в резервные копии. Поэтому переменную окружения перед вызовом предпочитают флагу, а сам ключ берут из хранилища секретов, а не подставляют руками. Это не про паранойю: перечисленные места существуют независимо от того, помните вы о них или нет.
Отдельный вопрос - где хранятся учётные данные при интерактивном входе. Командная строка умеет держать их в системном хранилище или в файле, и есть переменная, которая принудительно выбирает файловый вариант. На своей машине системное хранилище предпочтительнее: файл проще случайно скопировать вместе с домашним каталогом, унести в резервную копию или показать на экране. Файловый режим оставляют для сред, где системного хранилища просто нет, - например, для контейнера, у которого нет ни сессии пользователя, ни графики.
Есть неочевидная деталь, из-за которой утекают ключи даже у аккуратных людей: примеры из документации. В них стоят заглушки, и их иногда подставляют буквально, а потом коммитят. Реального значения не должно быть ни в исходниках, ни в истории команд, ни в списке процессов, ни в транскрипте сессии, ни в артефактах сборки. Каждое из этих мест переживает вашу задачу и просматривается людьми, которым ключ не предназначался.
Для командной работы правильный ответ - служебные учётные записи. У них своя область прав, их не жалко отозвать, и по логам видно, что действие совершил автоматический процесс, а не человек. Личный ключ в общей автоматизации создаёт обратную картину: любое действие выглядит как ваше, а отзыв ломает вам работу вместе с пайплайном. Разбор инцидента в такой схеме превращается в выяснение, кто в тот вечер запускал пайплайн, вместо чтения журнала.
У каждого ключа должен быть готовый ответ на два вопроса: как его отозвать и что при этом перестанет работать. Если ответа нет, ключ уже используется шире, чем задумано, и отзыв превратится в аварию. Отсюда и правило действия при подозрении: ключ, засветившийся в транскрипте, логе или артефакте, считают скомпрометированным и меняют, а не выясняют, видел ли его кто-нибудь на самом деле. Замена стоит десяти минут, разбирательство после утечки - несравнимо больше.
Инженерный вывод простой: аутентификация - это часть архитектуры автоматизации, а не строка в начале скрипта. Кто именно действует, каким ключом, с какой областью прав и как этот ключ отзывается - вопросы, на которые отвечают до первого прогона. Ответы записывают там же, где живёт сам процесс.
Типичные провалы предсказуемы. Задать ключ на всё задание и открыть его скриптам репозитория. Оставить учётные данные в файле там, где доступно системное хранилище. Подставить заглушку из примера буквально и закоммитить. И использовать личный ключ в командной автоматизации вместо служебной учётной записи.
agent login
agent status
# Один процесс автоматизации: ключ не живёт дольше вызова
CURSOR_API_KEY="$CURSOR_JOB_API_KEY" agent -p "Проанализируй текущий diff"
# ключ на всё задание прочитают и скрипты, управляемые репозиторием