1 / 8
Проблема и концепция
Начнём не с кода, а с того, ради чего он написан.
Две плохие опции
Вот обычная ситуация: агенту нужно прогнать тест с паролем от тестовой базы, зайти по SSH на сервер или открыть headless-браузером страницу за логином. В любом случае — нужен секрет. Способов дать его агенту ровно два, и оба на первый взгляд удобные, а на деле так себе.
Первый — разрешить агенту читать .env.
агент ──Read(.env)──▶ файл с секретами ──▶ значение попадает
в контекст модели
Стоит прочитать секрет — и он уже стал текстом в разговоре с моделью. Дальше
он осядет в логах API-запросов, в истории транскрипта, а если модель
облачная — ещё и в чужой инфраструктуре. Забрать его оттуда обратно нельзя.
Агенты по умолчанию и не читают .env именно по этой причине — вопрос лишь
в том, как им тогда вообще пользоваться секретами.
Второй — вставить секрет в чат руками.
человек ──вставляет пароль в промпт──▶ история чата ──▶ логи, кэш, бэкапы
Проблема та же самая, просто источник другой: не файл, а сам человек. Единственная разница — это осознанное действие, а не случайность.
Обе схемы сходятся в одном: значение секрета превращается в токены внутри контекста модели. Вот чего нужно избежать — не спрятать секрет от агента совсем (тогда он просто не сможет работать), а не дать значению попасть именно в текст, который модель видит и запоминает.
Стержневая идея
Агенту почти никогда не нужно видеть значение секрета — ему нужно, чтобы
команда, которую он запускает, имела к нему доступ. curl с токеном в
заголовке, ssh с ключом, psql с паролем — секрет здесь нужен процессу, а
не тексту диалога.
Значит, правильный канал — не "прочитать и вставить в промпт", а "запустить процесс с секретом в его окружении и показать модели только результат":
агент просит выполнить команду
│
▼
Athanor подставляет секрет в окружение ДОЧЕРНЕГО процесса
│
▼
модель видит stdout/stderr команды; если команда печатает секрет,
он окажется в этом выводе
Это поведение по умолчанию. Отдельные секреты, для которых текстовое значение не страшно (скажем, тестовый публичный API-ключ), можно явно пометить "можно отдавать текстом" — как осознанное исключение, а не как режим по умолчанию. Устройство этого механизма — в главе 3.
Почему age+sops, а не Vault/1Password/Infisical
Готовые менеджеры секретов — Vault, 1Password, Infisical — решают ту же
задачу, но рассчитаны на команду и инфраструктуру: сервер, аккаунты,
синхронизацию. Для соло-разработчика с простыми проектами это избыточно —
целая инфраструктура ради задачи "один человек, пара VPS". age
(шифрование парой ключей) и sops (декларативный редактор зашифрованных
YAML-файлов поверх age) дают ту же гарантию — секрет на диске всегда
зашифрован — без сервера, аккаунта и сети: два небольших бинарника и один
файл с ключом.
Откуда название
Athanor — алхимическая печь для непрерывной трансмутации: сырое вещество
закладывается один раз, а дальше печь сама поддерживает нужное состояние и
отдаёт "энергию" по запросу. Секрет точно так же закладывается один раз
(athanor add), а дальше система передаёт его доверенному процессу без
прямой выдачи значения инструментом. Сам процесс уже находится внутри
границы доверия и способен вывести полученное значение.
Контрольный вопрос. Почему "не давать агенту читать .env" — неполная
формулировка задачи, и что на самом деле нужно предотвратить?
Ответ: дело не в файле как таковом, а в сокращении ненужных каналов
раскрытия. Можно запретить чтение .env и всё равно вставить пароль в чат
или попросить команду напечатать переменную. Athanor убирает plaintext из
проекта и прямых ответов по умолчанию, но команда с секретом остаётся
доверенной стороной.