Как дать кодинг-агенту секрет, не вставляя его в промпт
Агенту нужен ключ Stripe или пароль от базы, чтобы запустить свою команду. Вставьте его в чат, и он окажется в транскрипте на вашем диске, у провайдера модели, а иногда и в коммите. Правило, к которому мы пришли после трёх собственных ошибок: секрет попадает к агенту через переменную окружения и никак иначе.
Ситуация всегда одна и та же. Агент на середине задачи, ему нужно обратиться к вашему тестовому API Stripe, прогнать миграцию на staging-базе или запушить в приватный реестр. Он просит ключ. Ключ лежит у вас в менеджере паролей, агент ждёт, и самое быстрое, что можно сделать на свете, это вставить его в чат.
Эта вставка и есть момент, когда ключ уходит из-под вашего контроля. Не потому, что модель злонамеренна, а потому, что промпт это объект, который сохраняется и пересылается: он уходит в файл транскрипта на вашем диске, он уходит к провайдеру модели, и его копирует каждая повторная попытка и каждый субагент, которому достаётся эта переписка.
Мы делаем AgentsRoom, десктопное приложение, которое запускает кодинг-агентов, так что нам пришлось решить это для себя. Мы ошиблись три раза в сентябре 2026 года. Вот правило, к которому мы в итоге пришли, три ошибки, которые его породили, и как применить его независимо от того, пользуетесь вы нашим приложением или нет.
Короткий ответ
- Секрет попадает к агенту через переменную окружения и никак иначе. Ни через сообщение, ни через тикет, ни через навык, ни через флаг командной строки.
- Агент пишет
$NAME, а не значение. Оболочка подставит его, когда команда начнёт выполняться. - Сохраните значение один раз, вне репозитория, зашифрованным связкой ключей вашей ОС, и везде дальше ссылайтесь на него по имени.
- Секрет, прошедший через переписку, сгорел. Смените его, а потом прибирайтесь.
- Маскирование это страховочная сетка, а не план. Оно ловит то, что выглядит как ключ, а не пароль от базы, который читается как обычное слово.
Почему эта вставка хуже, чем кажется
Происходят три вещи сразу, и очевидна только первая.
Ключ уходит к провайдеру модели. Эту часть видят все. Он теперь лежит в теле запроса на чужой инфраструктуре, внутри переписки, которую вы не можете отредактировать.
Ключ уходит на ваш диск, открытым текстом. Claude Code, Codex, Antigravity CLI и остальные держат транскрипт переписки, чтобы её можно было продолжить. Этот файл лежит в вашей домашней папке, незашифрованным, ровно столько, сколько CLI хранит свою историю. Поищите один раз строку sk- в своих транскриптах и посмотрите, что найдётся.
Ключ расползается вбок. Переписка это память агента, поэтому всё, что копирует переписку, копирует и ключ: возобновлённая сессия, форкнутый агент, субагент, запущенный с контекстом родителя, резюме, записанное в файл проекта. Мы видели, как ключ путешествовал из сообщения в чате в описание тикета, а оттуда в первое сообщение второго агента, и никто при этом не сделал ничего плохого.
И привычная страховочная сетка не держит. Маскирование вывода работает по шаблону: sk-…, glpat-…, форма JWT. Оно не может знать, что thursday-harbour-71 это пароль от вашей staging-базы. В собственном приложении мы маскируем распознанные значения в выводе терминала и всё равно считаем это сеткой под трапецией, а не самой трапецией.
Правило одной двери
Вот оно, в одну строку: учётные данные попадают к агенту через переменную окружения того процесса, в котором он работает, и ни по какому другому каналу.
Всё остальное это буквальный текст. Сообщение, которое вы набираете, описание тикета, файл навыка, системный промпт, флаг командной строки: всё это читает человек или модель, поэтому секрет, записанный там, это опубликованный секрет.
Командная строка заслуживает отдельной фразы, потому что кажется безопаснее сообщения в чате, а на деле не безопаснее. Значение в --api-key=… видно каждому процессу на машине через ps, оно попадает в историю вашей оболочки, его записывает всё, что логирует запущенную команду, и оно всплывает в диагностических инструментах, которые показывают вам строку процесса. Переменная окружения одного процесса читается этим процессом и пользователем root. Поверхность меньше, удобство то же.
Агенту, к слову, значение не нужно никогда. Ему нужно написать вот это:
curl -H "Authorization: Bearer $STRIPE_SECRET_KEY" https://api.stripe.com/v1/charges
Оболочка подставляет $STRIPE_SECRET_KEY при выполнении. Агент написал семнадцать символов и знак доллара. В его транскрипте лежат семнадцать символов и знак доллара.
Как это устроено в нашем приложении
Механизм важен меньше, чем форма, но форму проще скопировать, когда видишь хотя бы одну реализацию.
Одно хранилище, один файл. AgentsRoom держит секреты в одном файле в вашей домашней папке, в режиме 0600, зашифрованном связкой ключей операционной системы и подтверждаемом при каждой записи. Ничего не синхронизируется на сервер и ничего не уходит на другой компьютер: секрет принадлежит машине, которая его хранит. Если связка ключей недоступна, сохранение завершается ошибкой, а не откатывается к открытому тексту.
Вы сохраняете значение один раз. Команды и подключения > Секреты, в любом проекте. Имя (буквы, цифры, точка, дефис, подчёркивание, 64 символа, чтобы его можно было использовать как имя переменной окружения) и значение, набранное в поле пароля. Появившаяся строка показывает имя и его ссылку, {{secret:STRIPE_SECRET_KEY}}, и кнопку, которая копирует ссылку. Кнопки «показать» нет, кнопки «скопировать значение» нет, потому что нет ни одного пути в коде, который вернул бы значение в интерфейс.
Вы ссылаетесь на него там, где собирается окружение. Изменить агента > Дополнительно > Переменные окружения, или настройки проекта, или настройки провайдера, или командная строка dev-терминала:
STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}}
При запуске основной процесс подставляет значение в окружение терминала, который он собирается создать. Интерфейс его не видит, агент его не видит, телефон его не видит. Страница «Менеджер секретов» показывает эти экраны.
Агент узнаёт имена, а не значения. Агент, подключённый к нашим инструментам MCP, может вызвать secrets_list и получить список имён, которые есть на этой машине. Этого достаточно, чтобы он приносил пользу: он может сказать «у вас сохранён STRIPE_SECRET_KEY, добавьте STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}} в окружение этого агента, и я буду использовать $STRIPE_SECRET_KEY». Прочитать значение он не может, даже выполняя команду, которая этим значением пользуется.
Три ошибки, которые мы выпустили
Записанное правило выглядит очевидным. На самом деле это рубец от трёх багов, все в сентябре 2026 года, все найдены пользователями.
Ссылка, процитированная в тикете, выходила подставленной
19 сентября. Robert задокументировал соответствие в описании тикета, совершенно разумное STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}}, чтобы тот, кто возьмёт тикет, знал, что нужно настроить. Прочитанный через API, тикет возвращал ссылку нетронутой. Но агент, запущенный из этого тикета, получал в первом сообщении значение.
Причиной была деталь внутренней сантехники с вполне реальным последствием. Бриф тикета становится первым промптом агента, а тот путешествовал внутри переменной окружения команды запуска. Наш резолвер подставлял значения во все переменные окружения, поэтому подставил и промпт. Ключ ушёл на диск и к провайдеру модели. Смена ключа, обойти это было нечем.
Исправление состояло в том, чтобы сделать команду запуска агента буквальной: когда окружение несёт идентификатор агента, переменная с первым промптом остаётся нетронутой. Dev-команда с той же переменной не имеет идентификатора агента и подставляется как раньше. Один побочный эффект мы приняли сознательно: ссылка, набранная в опциях CLI агента, тоже больше не подставляется, и это правильно, ведь командная строка всё равно попадает в ps и в журналы.
Урок: дверь должна быть узкой намеренно. «Подставлять везде, где это может пригодиться» это ровно тот путь, которым секрет оказывается там, где его быть не должно.
Ссылка, вставленная в сообщение, которая никогда не подставляется
22 сентября. Cs040618 сообщил о противоположной проблеме: «я вставляю ссылку в переписку, а агент не может понять, что это за ключ».
Это не баг. Сообщение по замыслу является буквальным текстом, и это ровно то исправление, которое мы сделали тремя днями раньше. Но ничто в приложении об этом не говорило в момент самого действия, а агент, получив {{secret:…}} в сообщении, понятия не имел, что отвечать.
Три исправления, и все о том, чтобы показать, где находится дверь: подсказка, которая срабатывает, когда вы вставляете ссылку в поле ввода, и указывает на переменные окружения; пояснение над списком секретов, которое называет три поля, где ссылка подставляется, и говорит «никогда в сообщении»; и строка в собственных инструкциях агента, которая говорит ему, что отвечать, когда ссылка приходит в сообщении.
«Настройки > Секреты» не существовало
28 сентября. Агент Mohammad завёл отчёт: пользователь говорит, что добавил OTPIQ_API_KEY в Настройки > Секреты, сохранял его несколько раз за полчаса, а secrets_list по-прежнему возвращает те же три старых секрета.
Путь сохранения работал нормально. Секрет просто никогда не доходил до хранилища, потому что страницы «Секреты» в настройках нет. Шесть справочных текстов в десктопном приложении, страница на этом сайте и собственные инструкции агента отправляли людей на экран, которого не существует. В настройках действительно есть блок переменных окружения, поэтому именно туда он и вписал токен, в фигурных скобках, что ссылкой не является: никогда не подставляется, уходит к агенту как есть и синхронизируется вместе с проектом, так что токен покинул машину.
Мы поправили эти тексты, чтобы они называли настоящий путь, научили инструменты для агентов говорить, где добавляется секрет, и добавили проверку блока окружения, которая отмечает учётные данные, похожие на сырое значение, имя в фигурных скобках, которое не является ссылкой, и ссылку на секрет, которого на этой машине нет.
Урок здесь вообще не о секретах: неверный путь в справочном тексте это баг безопасности. Он уводит пользователя к ближайшему полю, которое выглядит подходящим.
Если вы не пользуетесь AgentsRoom
Правило переносится. Возьмите тот вариант, который подходит вашей установке; все они заканчиваются в одном и том же месте, в переменной окружения процесса, в котором работает агент.
- CLI менеджера паролей.
op run -- claudeс 1Password или эквивалент в Bitwarden и Doppler: инструмент читает ссылки из шаблона, вставляет настоящие значения в окружение дочернего процесса, и ничего не пишется на диск. direnvс.envrcв gitignore. Просто, локально, по папкам. Добавьте.envrcи.env*в.gitignoreдо первого значения, а не после.- Файл
.env, который агент никогда не читает. Держите его вне репозитория и загружайте в оболочке, которая запускает агента. Если файл лежит внутри репозитория, агент рано или поздно его откроет, процитирует в сообщении, и вы вернётесь туда, откуда начали. - Облачные учётные данные: берите короткоживущие. GitHub OIDC в CI вместо долгоживущих ключей AWS и рабочая нагрузка с собственной идентичностью вместо файла ключа сервисного аккаунта.
Что бы вы ни выбрали, добавьте это правило в инструкции агента, чтобы он перестал спрашивать. Хватит одного абзаца в CLAUDE.md или AGENTS.md: никогда не спрашивать значение учётных данных, никогда не записывать его в файл, использовать $NAME и говорить пользователю, какую переменную настроить. Мы публикуем небольшой навык, который говорит ровно это, и в нём пять строк.
Если ключ уже прошёл через переписку
По порядку, и первый шаг не опционален.
- Смените его. Отправленный промпт не отзывается. Считайте значение скомпрометированным и выпустите новое.
- Найдите транскрипты. CLI хранят файлы переписок в вашей домашней папке. Поищите в них старое значение и удалите или обрежьте то, что совпало.
- Проверьте места, которые копируют текст. История менеджера буфера обмена, тикет или issue, где агента инструктировали, любое резюме, которое агент записал в репозиторий, и
git log -p, если агент коммитил, пока держал ключ. - И только теперь стройте дверь. Новое значение за переменной окружения, ссылка по имени и обновлённые инструкции агента, чтобы следующий не спрашивал.
Шаг 4 последним, потому что чистый механизм вокруг утёкшего ключа это всего лишь аккуратнее прибранная утечка.
Вопросы, которые люди задают на самом деле
Почему нельзя вставлять ключ API в промпт для ИИ-агента?
Потому что промпт сохраняется и пересылается. Claude Code, Codex и остальные CLI пишут переписку в файл транскрипта на вашем диске, а само сообщение уходит к провайдеру модели. Дальше ключ оказывается ещё и в истории вашего буфера обмена, в любом журнале, который дублирует сообщение, и в повторных попытках и субагентах, копирующих переписку. Маскирование по шаблону ловит то, что похоже на sk- или glpat-, но не пароль от базы, который выглядит как обычное слово.
Как кодинг-агент пользуется секретом, который он не может прочитать?
Через переменную окружения. Вы кладёте значение в переменную окружения того процесса, в котором работает агент, и агент пишет в команде $STRIPE_SECRET_KEY вместо значения. Оболочка подставляет его в момент выполнения команды. Агент никогда не видит сами символы, поэтому они не попадают ни в его переписку, ни в его транскрипт.
Можно ли вместо этого передать секрет аргументом командной строки?
Можно, но не нужно. Командная строка видна каждому процессу на машине через ps, она попадает в историю оболочки, в журналы процессов и в любой диагностический инструмент, который перечитывает запущенную команду. Переменная окружения одного процесса читается этим процессом и пользователем root, а это куда меньшая поверхность.
Где AgentsRoom хранит секреты, которыми пользуются агенты?
В одном файле в вашей домашней папке, в режиме 0600, зашифрованном связкой ключей операционной системы, и только на этой машине. Ничего не синхронизируется на сервер, и нет кнопки, которая показала бы значение ещё раз. Приложение показывает имена, агент видит имена, а значение расшифровывается только в основном процессе, в момент старта терминала или команды.
Что будет, если вставить ссылку на секрет в сообщение агенту?
Ничего не подставится, и это сделано намеренно. Сообщение, описание тикета, навык и системный промпт это буквальный текст, поэтому написанная там ссылка приходит к агенту как текст. Подставляется только значение переменной окружения. Мы узнали это на своём опыте: ссылка, процитированная в описании тикета, раньше выходила подставленной в первом сообщении запущенного оттуда агента.
Ключ уже прошёл через переписку. Что делать?
Сначала сменить его, прежде чем что-либо ещё, потому что отправленное не отзывается: ключ уже на стороне провайдера и в файле транскрипта на вашем диске. Потом удалить или обрезать транскрипты, которые его содержат, проверить менеджер буфера обмена и поискать его в репозитории на случай, если агент записал его в файл. И только после этого класть новое значение за переменную окружения.
Если коротко
Агенту не нужно читать ваш ключ. Ему нужен процесс, в окружении которого ключ уже лежит.
Сохраните значение один раз, вне репозитория, зашифрованным системой. Ссылайтесь на него по имени там, где собирается окружение. Пусть агент пишет $NAME. Держите все остальные каналы буквальными, включая те, где подстановка была бы удобна, потому что удобные каналы это ровно те, которые сохраняются и пересылаются.
А когда вы всё-таки ошибётесь, а это случится, сначала смените ключ и только потом прибирайтесь.
Скачать AgentsRoom
Запускай всех своих ИИ-агентов во всех проектах из одного окна.
Приложение-компаньон: следите за агентами на ходу
Используйте Claude, Codex, Antigravity CLI или другого поставщика AI.
Отправляйте баги и запросы прямо в ваш публичный бэклог.
Читать далее
Routines в Claude Code: что работает в облаке, что остаётся на вашей машине и как выбрать
Routines позволяют Claude Code запускать сохранённый промпт без вашего участия: по расписанию, по вызову API или по событию GitHub, в облачной сессии на свежем клоне вашего репозитория. Они находятся в research preview. У Claude Code есть и два локальных способа планировать работу, запланированные задачи десктопного приложения и /loop, и все три ведут себя по-разному: минимальный интервал, доступ к вашим локальным файлам, запросы разрешений, что происходит, когда ноутбук спит. Это руководство раскладывает все три варианта с ограничениями из документации, а затем объясняет, почему наши собственные семь ночных агентов работают на локальной машине.
Читать статьюМой ИИ-тренер по бегу: Git-репозиторий и агент Claude
Я заканчиваю пробежку, часы синхронизируются, и через три минуты анализ уже записан в мой репозиторий, неделя пересобрана, а тренер оставил комментарий под активностью в Strava. Никакого написанного приложения, никакого сервера, никакого счёта за токены: подписка Claude, AgentsRoom и файлы Markdown. Вот вся сборка целиком, воспроизводимая.
Читать статьюОдин аккаунт Claude на двух компьютерах или на двоих: что работает
В один аккаунт Claude можно войти на всех твоих устройствах, и все они расходуют одни и те же лимиты на 5 часов и на неделю, Claude и Claude Code вместе. Пользоваться одним аккаунтом вдвоём запрещают условия Anthropic, а семейного тарифа нет. Что мы проверили 25 октября 2026 года и как увидеть, какой компьютер сколько потратил.
Читать статью