Як дати агенту кодування секрет, не вставляючи його в промпт

Агенту потрібен ключ 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 і казати користувачеві, яку змінну налаштувати. Ми публікуємо невелику навичку, яка каже рівно це, і в ній п'ять рядків.

Якщо ключ уже пройшов через розмову

По порядку, і перший крок не є необов'язковим.

  1. Змініть його. Надісланий промпт не відкликається. Вважайте значення скомпрометованим і випустіть нове.
  2. Знайдіть транскрипти. CLI тримають файли розмов у вашій домашній теці. Пошукайте в них старе значення і видаліть або обріжте те, що збіглося.
  3. Перевірте місця, які копіюють текст. Історія менеджера буфера обміну, тікет або issue, де агента інструктували, будь-яке резюме, яке агент записав у репозиторій, і git log -p, якщо агент комітив, поки тримав ключ.
  4. І тільки тепер будуйте двері. Нове значення за змінною середовища, посилання за іменем і оновлені інструкції агента, щоб наступний не питав.

Крок 4 останнім, бо чистий механізм навколо витеклого ключа це лише акуратніше прибраний витік.

Питання, які люди ставлять насправді

Чому ключ API ніколи не можна вставляти в промпт для AI-агента?

Бо промпт зберігається і пересилається. Claude Code, Codex та інші CLI записують розмову у файл транскрипта на вашому диску, а саме повідомлення йде до провайдера моделі. Далі ключ опиняється ще й в історії вашого буфера обміну, у будь-якому журналі, який дублює повідомлення, та в повторних спробах і субагентах, що копіюють розмову. Маскування за шаблоном ловить те, що схоже на sk- або glpat-, але не пароль до бази, який виглядає як звичайне слово.

Як агент кодування користується секретом, якого не може прочитати?

Через змінну середовища. Ви кладете значення у змінну середовища того процесу, в якому працює агент, і агент пише в команді $STRIPE_SECRET_KEY замість значення. Оболонка підставляє його в момент виконання команди. Агент ніколи не бачить самих символів, тож вони не потрапляють ні в його розмову, ні в його транскрипт.

Чи можна натомість передати секрет аргументом командного рядка?

Можна, але не варто. Командний рядок видно кожному процесу на машині через ps, він потрапляє в історію оболонки, у журнали процесів і в будь-який діагностичний інструмент, який перечитує запущену команду. Змінну середовища одного процесу читає цей процес і користувач root, а це значно менша поверхня.

Де AgentsRoom зберігає секрети, якими користуються агенти?

В одному файлі у вашій домашній теці, у режимі 0600, зашифрованому ключовим сховищем операційної системи, і лише на цій машині. Нічого не синхронізується на сервер, і немає кнопки, яка показала б значення ще раз. Застосунок показує імена, агент бачить імена, а значення розшифровується тільки в головному процесі, у момент старту термінала або команди.

Що буде, якщо вставити посилання на секрет у повідомлення агенту?

Нічого не підставиться, і це зроблено навмисне. Повідомлення, опис тікета, навичка і системний промпт це буквальний текст, тож написане там посилання приходить до агента як текст. Підставляється лише значення змінної середовища. Ми дізналися це на власному досвіді: посилання, процитоване в описі тікета, раніше виходило підставленим у першому повідомленні запущеного звідти агента.

Ключ уже пройшов через розмову. Що робити?

Спершу змінити його, перед усім іншим, бо надіслане не відкликається: ключ уже на боці провайдера і у файлі транскрипта на вашому диску. Потім видалити або обрізати транскрипти, які його містять, перевірити менеджер буфера обміну і пошукати його в репозиторії на випадок, якщо агент записав його у файл. І тільки після цього класти нове значення за змінну середовища.

Якщо коротко

Агенту не потрібно читати ваш ключ. Йому потрібен процес, у середовищі якого ключ уже лежить.

Збережіть значення один раз, поза репозиторієм, зашифрованим системою. Посилайтеся на нього за іменем там, де збирається середовище. Нехай агент пише $NAME. Тримайте всі інші канали буквальними, зокрема ті, де підстановка була б зручною, бо зручні канали це рівно ті, які зберігаються і пересилаються.

А коли ви все-таки помилитеся, а це станеться, спершу змініть ключ і тільки потім прибирайте.

Завантажити AgentsRoom

Запускайте всіх своїх AI-агентів на всіх своїх проєктах з одного вікна.

БезкоштовноЗавантажити AgentsRoom

Додаток-компаньйон: контролюйте своїх агентів на ходу

Використовуйте свого: Claude, Codex, Antigravity CLI або іншого AI-провайдера.

Отримати розширення
Chrome Web Store

Надсилайте баги та запити прямо у свій публічний беклог.

Кілька проектів
Багато постачальників
Кілька агентів
Статус в реальному часі
Різниця файлів і коміт
Мобільний компаньйон
Попередній перегляд в реальному часі
Команди агентів
Автоматизація браузера
Розробка, орієнтована на беклог
Бібліотека підказок
Бібліотека навичок
Переглянути всі функції

Читати далі