ATHANOR
EN GitHub ↗

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 из проекта и прямых ответов по умолчанию, но команда с секретом остаётся доверенной стороной.