# Какой настоящий процесс агента запрашивает подтверждение?

Запрос на подтверждение с надписью «Terminal» или «zsh» обычно слишком расплывчат, чтобы принять обоснованное решение. Эти программы могут быть частью пути запуска, но чаще это общая инфраструктура. Рецензенту нужно знать, какой запуск агента запросил действие, что его запустило и есть ли в этой цепочке что-то, что дает запросу устойчивую идентичность.

Сложность в том, что дерево процессов одновременно отвечает на несколько разных вопросов. Оно показывает, кто кого создал. Может обнаружить исполнитель задач, которого никто не ожидал. Может показать, что графическая IDE запустила терминал. Но само по себе дерево не доказывает, что подписанный родитель одобрил каждую строку кода, которую выполнит дочерний процесс. Хорошая система подтверждений использует дерево как доказательство, а затем формулирует только тот узкий вывод, который оно действительно поддерживает.

## Подтверждать нужно актера, а не терминал

Актер, которого одобряет рецензент, это запуск процесса, запросивший действие, с учетом цепочки его запуска. Окно терминала лишь задает место, где работают процессы. Оно не становится ответственным приложением автоматически.

Разница кажется мелочной, пока у команды не появляется несколько агентов в одной оболочке. Разработчик открывает терминал в iTerm2, запускает интерактивного агента для работы с кодом в одной вкладке, начинает задачу репозитория в другой и оставляет фоновый наблюдатель в третьей. У всех трех потомков где-то выше может находиться одно и то же приложение терминала. Назвать каждый запрос «iTerm2» почти ничего не скажет рецензенту.

Не менее распространена обратная ошибка. Рецензент видит `node`, `python` или `zsh` и считает, что ближайший к запросу бинарный файл и есть вся идентичность. Но этот бинарный файл может быть интерпретатором пакета агента, обработчиком менеджера пакетов, временным скриптом расширения IDE или оболочкой командного инструмента команды. Технически название верно, но для работы оно бесполезно.

Внутри системы используйте три метки, даже если на карточке показываются только две:

- **Инициатор**: процесс, который действительно запросил доступ к защищенному каналу.
- **Цепочка выполнения**: важные родительские процессы, объясняющие, как инициатор дошел до этого вызова.
- **Узнаваемая сторона**: ближайший предок, чью идентичность рецензент может разумно распознать и чью подпись кода можно проверить.

Эти метки устраняют путаницу, которую часто создают системы подтверждений. Инициатор отвечает на вопрос «какой процесс сделал этот вызов?». Узнаваемая сторона отвечает на вопрос «через кого или какую организацию прошел этот запуск?». Эти понятия связаны, но не взаимозаменяемы.

Например, запрос может выглядеть так:

```text
Code Helper (signed application)
  └─ zsh -l
      └─ npm run agent
          └─ node ./tools/start-agent.mjs
              └─ agent-cli --workspace /Users/maya/src/payments
```

Если HTTP-запрос делает `agent-cli`, он и есть инициатор. Оболочка и npm объясняют путь запуска. Подписанный процесс Code Helper может быть лучшей узнаваемой стороной, если он действительно находится в текущей цепочке предков, а не просто открыт на рабочем столе. Карточка «agent-cli, запущен из Code Helper» сообщает правду. Карточка «Code Helper запрашивает доступ» выбрасывает сведения, по которым можно отличить этот запуск от другого расширения или задачи.

## Дерево процессов это доказательство, а не заявление о намерениях

Родительский процесс доказывает, что он создал дочерний процесс или установил с ним связь. Но это не доказывает, что родитель понимал последующие аргументы дочернего процесса, его запрос, инструкции репозитория или удаленный ответ.

Это ограничение важно, когда люди небрежно говорят о подписях. Apple описывает designated requirement как механизм, с помощью которого macOS определяет, остается ли код тем же кодом в разных версиях. Обычно он включает идентификатор и подписывающую сторону. Apple также четко обозначает границу: неподписанный код не имеет устойчивого designated requirement, а локальные ad hoc-подписи не дают стабильной идентичности между версиями.

Для подтверждения это полезное доказательство, но не одобрение действия. Подписанная IDE может запустить неподписанный скрипт репозитория. Подписанный терминал может выполнить загруженный бинарный файл. Подписанный исполнитель задач может передать в обычный интерпретатор опасные переменные среды. Подпись помогает рецензенту узнать издателя кода, но не делает безопасной остальную цепочку.

Различайте следующие утверждения:

| Утверждение | Что его подтверждает | Чего оно не подтверждает |
|---|---|---|
| «Этот запрос пришел от PID 81234». | Локальная запись о процессе | Кто написал код и почему он сделал вызов |
| «Этот дочерний процесс запустил этот родитель». | PID родителя и текущая цепочка предков | Что родитель одобрил нынешнее поведение дочернего процесса |
| «Этот исполняемый файл подписан этой стороной». | Проверка подписи и designated requirement | Что файл безопасен или действует в соответствии с намерением |
| «Этот запуск начался из этой IDE или терминала». | Непрерывная цепочка живых предков | Что видимое окно запустило каждого потомка |

Практический вывод прост. Не сводите все четыре утверждения к значку приложения и одной дружелюбной фразе. Рецензент должен видеть, какое именно утверждение он принимает.

Если цепочка неоднозначна, скажите об этом. «Запущено неподписанным скриптом из сеанса терминала» лучше, чем приписывать запрос приложению терминала, будто именно оно написало код. Ложная точность приучает людей игнорировать карточку.

## Сначала изучите живую цепочку, затем проектируйте карточку

Самый быстрый способ найти нужного актера, изучить реальный запуск, пока он еще работает. Делайте это на безвредной операции агента, потому что после перехода от запуска к исполнителю задач или дочернему помощнику цепочка может измениться.

В macOS начните с PID инициатора, если шлюз его записывает. Команда выводит процесс, PID родителя, время запуска, имя исполняемого файла и аргументы:

```sh
ps -p "$PID" -o pid=,ppid=,lstart=,user=,comm=,args=
```

Типичный результат выглядит так:

```text
81234 80991 Tue Jul 21 14:08:31 2026 maya /usr/local/bin/agent-cli agent-cli --workspace /Users/maya/src/payments
```

Затем поднимайтесь вверх. `ps` не делает это рекурсивно, поэтому небольшая функция оболочки помогает повторять проверку одинаково:

```sh
ancestry() {
  local pid="$1"
  while [ "$pid" -gt 1 ] 2>/dev/null; do
    ps -p "$pid" -o pid=,ppid=,user=,comm=,args=
    pid=$(ps -p "$pid" -o ppid= | tr -d ' ')
  done
}

ancestry "$PID"
```

Вывод намеренно простой. Ищите смену типа: исполняемый файл агента превращается в интерпретатор, интерпретатор в менеджер пакетов, менеджер пакетов в оболочку, оболочка в терминал или помощник IDE. Не останавливайтесь на первом знакомом имени. Продолжайте, пока не дойдете до процесса, который дает рецензенту содержательное происхождение, или пока локальная цепочка не закончится.

Для общего снимка, когда есть несколько потомков, пригодится такой вид:

```sh
ps -ax -o pid=,ppid=,user=,lstart=,comm=,args= | sort -n
```

Используйте его для расследования, а не для карточки подтверждения. Полный список процессов содержит слишком много лишних сведений и слишком мало структуры. Реализация должна собирать его при необходимости, а затем сокращать до инициатора, непосредственного родителя, узнаваемой стороны и промежуточных переходов, объясняющих неожиданный запуск.

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

## VS Code меняет путь запуска, но не становится инициатором

Интегрированный терминал VS Code может добавлять аргументы или переменные среды при запуске поддерживаемых оболочек для интеграции с оболочкой. В документации также сказано, что автоматическая подстановка работает не во всех конфигурациях, включая некоторые дочерние оболочки, SSH и сложные сценарии. Это хорошо показывает, что метаданные интерфейса терминала и родство Unix-процессов являются разными источниками доказательств.

В простом случае цепочка выглядит так:

```text
Visual Studio Code
  └─ terminal helper
      └─ login shell
          └─ agent-cli
```

Помощник терминала нужен IDE для управления псевдотерминалом и дочерней оболочкой. Оболочка входа может загрузить профиль, который изменит `PATH`, активирует среду языка, установит хуки оболочки или запустит скрипт проекта. Эти детали объясняют, почему одна и та же команда ведет себя по-разному в обычном терминале и в терминале IDE.

Решение о подтверждении не должно делать вид, что этих деталей нет. Покажите исполняемый файл агента как инициатор. Указывайте VS Code как источник запуска только тогда, когда он находится в текущей цепочке предков. Если оболочка и команда задачи действительно отличают этот запуск от других, включите их в раскрываемый путь.

Типичный сбой выглядит так:

```text
Visual Studio Code
  └─ zsh
      └─ make test-agent
          └─ sh -c ./scripts/bootstrap-agent
              └─ python3 tools/agent.py
```

Наивная реализация видит `zsh` и подписывает запрос «терминал VS Code». Другая видит `python3` и называет его «Python». Ни один вариант не помогает понять, относится ли запрос к известной задаче репозитория или к произвольному процессу из той же оболочки.

Полезная формулировка ближе к такой:

```text
Requester: python3 tools/agent.py
Run path: make test-agent > scripts/bootstrap-agent
Launched from: Visual Studio Code integrated terminal
```

У такой метки есть цена: она показывает, что запрос пришел из кода, контролируемого репозиторием. Так и должно быть. Если рецензент доверяет подписанному редактору, но не доверяет текущей копии репозитория, карточка должна оставить место для такого решения.

Не делайте вывод о принадлежности VS Code по управляющим последовательностям терминала, переменным среды или одному заголовку окна. Их можно унаследовать, скопировать или получить из устаревшего состояния. Живая цепочка предков дает более сильное доказательство. Контекст, который предоставляет IDE, может дополнить карточку после проверки этой цепочки, но не заменить ее.

## Отдельный терминал дает контекст, но не безусловные полномочия

iTerm2, Terminal и похожие приложения дают человеку осознанную точку входа. Если разработчик открывает чистый терминал, вводит команду агента и сразу получает запрос на подтверждение, указать приложение терминала полезно. Это сообщает, где начался запуск.

Но это не дает оснований автоматически подтверждать любой дочерний процесс, который случайно использует тот же сеанс. Оболочки созданы для запуска потомков, а долгоживущие вкладки часто переживают причину, по которой их открыли. Команда, запущенная в 9:00 в терминальной вкладке, может оставить рабочий процесс до обеда, после `cd`, смены ветки, изменения среды и трех несвязанных команд.

Сделайте разницу видимой:

```text
Requester: agent-cli --workspace /Users/maya/src/payments
Parent: zsh -l
Interactive origin: signed terminal application
Session started: Tue Jul 21 14:08:31 2026
```

Интерактивный источник это дополнительный контекст. Сеанс принадлежит конкретному запуску процесса. Если процесс завершился, отзовите подтверждение сеанса, даже если вкладка терминала все еще открыта. Новый процесс агента в той же вкладке это новый запуск с новыми аргументами и, возможно, другим исполняемым файлом в `PATH`.

Здесь особенно заметна сложность с узнаваемой для рецензента идентичностью. Приложение терминала может быть подписано известным издателем, а `agent-cli` может быть неподписанным исполняемым файлом в каталоге проекта. Честная карточка должна назвать оба факта. Не переносите подпись терминала на неподписанный дочерний процесс, будто терминал за него поручился.

Та же проблема возникает с функциями и псевдонимами оболочки. Команда, похожая на `agent`, может раскрыться в функцию, которая сменит каталог, загрузит файл среды, запустит скрипт пакета и наконец стартует другой бинарный файл. Сборщик процессов увидит только получившихся потомков. Если нужно показывать введенную команду как дополнительный контекст, получайте ее через интеграцию с оболочкой или ее хук и помечайте как команду из истории ввода пользователя, а не как идентичность процесса.

## Скрипты и исполнители задач создают самые обманчивые цепочки

Исполнители задач популярны, потому что превращают сложную локальную настройку в запоминающуюся команду. Из-за этого их цепочки процессов часто выглядят надежнее, чем есть на самом деле.

Рассмотрим обычный запуск:

```text
zsh -l
  └─ just agent
      └─ /bin/sh -cu 'npm run agent -- --mode write'
          └─ npm run agent -- --mode write
              └─ sh -c node ./scripts/launch-agent.js --mode write
                  └─ node ./scripts/launch-agent.js --mode write
                      └─ agent-cli --mode write
```

У каждого промежуточного процесса есть законная задача. `just` выбирает рецепт. `/bin/sh` интерпретирует его тело. npm выбирает скрипт пакета и запускает оболочку. Node выполняет скрипт запуска. Но ни одно из этих имен не говорит рецензенту, пришел ли защищенный запрос от известного запуска агента или от другой команды, которая случайно использовала тот же интерпретатор.

Не удаляйте промежуточные уровни из аудиторской записи. Именно там расследование обнаружит измененный скрипт пакета, неожиданный хук жизненного цикла `pre` или `post`, измененный `PATH` или скрипт репозитория, подменивший исполняемый файл. Сокращайте их на карточке только после того, как сохраните в неизменяемой записи события.

Разумное правило отображения выглядит так:

1. Сначала укажите исполняемый файл инициатора и его аргументы.
2. Покажите непосредственный запускатель, если он меняет смысл, например `npm run deploy-agent` или `make migration-bot`.
3. Назовите первого узнаваемого подписанного предка как источник запуска.
4. Полную наблюдавшуюся цепочку оставьте доступной в подробностях события.

Это правило предотвращает две плохие привычки. Первая, скрывать все оболочки, из-за чего изменение задачи становится невидимым. Вторая, показывать семистрочное дерево до того, как рецензент успеет понять запрос. Когда карточки невозможно прочитать, люди подтверждают их форму, а не действие.

Я не согласен с рекомендацией подтверждать исполнителя задач вместо агента, потому что имя задачи понятнее человеку. Имена задач часто являются обычным текстом репозитория. Тот, кто может изменить копию репозитория, возможно, может изменить и поведение задачи. Используйте имя задачи как контекст, но привязывайте сеанс подтверждения к наблюдавшемуся запуску процесса агента и проводите новую проверку при каждом новом запуске.

## Внимательно выбирайте границу подписи

Ближайший подписанный предок обычно является лучшей узнаваемой стороной, но слово «ближайший» требует осторожности. Системная оболочка или интерпретатор с подписью может находиться непосредственно над неподписанным скриптом. Если назвать оболочку полномочной стороной, получится технически подписанная метка, которая ничего не говорит о том, кто предоставил код.

Рекомендации Apple по подписи кода различают идентификатор исполняемого файла и идентичность подписи, а designated requirement используют для установления идентичности кода. Инструмент `codesign` может показать это требование. Используйте результат, чтобы определить подписанное приложение или помощник, но не считайте одну строку идентификатора доказательством связи с издателем.

Для приложения в комплекте проверьте исполняемый файл или пакет приложения, который появляется в цепочке:

```sh
codesign --display --verbose=4 --requirements - "/Applications/Example.app" 2>&1 \
  | sed -n '/Identifier=/p;/TeamIdentifier=/p;/Authority=/p;/designated =>/p'
```

Типичный результат содержит поля такого вида:

```text
Identifier=com.example.editor
TeamIdentifier=AB12CDE345
Authority=Developer ID Application: Example Software (AB12CDE345)
designated => anchor apple generic and identifier "com.example.editor" and certificate leaf[subject.OU] = "AB12CDE345"
```

Точный набор полей зависит от подписи и способа распространения. Важно, чтобы система подтверждений сохраняла устойчивую идентичность кода, когда macOS может ее предоставить, а затем показывала понятную рецензенту версию, не скрывая исходные доказательства.

При выборе отображаемой стороны придерживайтесь следующих правил:

- Предпочитайте подписанное графическое приложение или отдельного подписанного помощника, который действительно находится в цепочке инициатора.
- Не используйте обычный подписанный интерпретатор вместо неподписанного скрипта, который он выполнил.
- Не делайте вывод о полномочиях по каталогу исполняемого файла, имени репозитория или приглашению оболочки.
- Явно помечайте неподписанные и ad hoc-подписанные этапы, а не прячьте их за узнаваемым родителем.
- Если узнаваемой подписанной стороны нет, скажите, что у запуска нет проверенной локальной идентичности издателя.

У процесса может быть несколько подписей в концептуальном смысле: подпись пакета приложения, подпись вложенного помощника и подпись интерпретатора. Проверяйте тот исполняемый файл, который действительно присутствует в списке процессов. Подписанное приложение может запустить отдельно подписанного помощника, а помощник может запустить бинарный файл, установленный пользователем. Это разные вопросы идентичности.

## Некоторые цепочки запуска должны снижать доверие

Чистая связь родительского и дочернего процесса доступна не всегда, и такие случаи не являются мелкими исключениями. Именно здесь системы подтверждений чаще всего ошибаются с атрибуцией.

### Удаленное выполнение разрывает локальную цепочку

Если локальный агент вызывает SSH, Mac может определить локальный клиент SSH и его родителей. Удаленная команда имеет отдельное дерево процессов на другом хосте. Не говорите, что удаленный `agent-cli` запустила локальная IDE, если не собрали и не проверили доказательства на удаленной машине.

Локальная карточка все равно может быть полезной. Она может назвать локального инициатора, адрес назначения, удаленную учетную запись, если она известна, и точную команду, переданную SSH. Этого достаточно, чтобы решить, может ли локальный агент начать соединение. Но этого недостаточно для установления идентичности удаленного процесса.

### Отделенная работа требует нового решения об идентичности

Дочерний процесс может вызвать `setsid`, выполнить двойной fork или попросить супервизор продолжить работу после завершения исходного запускателя. После этого подтверждение, привязанное только к терминалу или начальной оболочке, становится выдумкой. Отделенному рабочему процессу нужны собственная запись, собственное время запуска и новый диапазон подтверждения, если только у вас нет специально спроектированной связи с супервизором, которую рецензент может проверить.

### Унаследованная среда может изменить актера, не меняя дерево

Один и тот же путь `node` может загрузить другой пакет агента, если изменились `PATH`, `NODE_OPTIONS`, пути модулей языка, настройки прокси или рабочий каталог. Родословная процессов не показывает все унаследованные значения. Для расследования сохраняйте ограниченный отпечаток контекста: путь к исполняемому файлу, аргументы, текущий каталог, выбранные имена и значения переменных среды после удаления секретов и, если это соответствует вашей модели, дайджест исполняемого файла.

Не выгружайте всю среду в карточку или журнал подтверждения. В ней часто находятся токены, адреса и личные пути. Цель состоит в различении запусков, а не в создании второго хранилища секретов в логах.

## Рецензенту нужны короткий вывод и проверяемые доказательства

У запроса на подтверждение есть всего несколько секунд, чтобы привлечь внимание. Вынесите наверх вывод, важный для решения, а затем дайте рецензенту возможность проверить, почему система пришла к нему.

Практическая карточка может выглядеть так:

```text
Agent process requests an HTTP call

Requester
agent-cli --workspace /Users/maya/src/payments
PID 81234, started 14:08:31

Launched through
npm run agent > node scripts/launch-agent.js
from a signed editor process

Destination
api.example.internal / POST /deployments
```

Эта форма достаточно компактна для чтения и достаточно точна для возражения. Рецензент может отклонить запрос, если рабочий каталог неверен, исполнитель задач неожиданен, адрес назначения вызывает вопросы или запрос пришел с неподписанного этапа. Это реальные причины остановить действие.

Полные доказательства держите за карточкой. Сохраняйте PID и время запуска инициатора, путь к исполняемому файлу, аргументы, цепочку родителей, сведения о подписи выбранной стороны и решение о подтверждении. Если тот же агент позже сделает еще один запрос в одобренном запуске, запись должна ясно показывать, что решение относилось ко времени жизни процесса, а не к имени приложения без связи с конкретным процессом.

Помесячная авторизация Sallyport построена вокруг этой практической границы: первый вызов нового процесса агента требует подтверждения, а оно действует только до завершения процесса. В карточке Sallyport сначала показывает подписывающую сторону процесса. Это дает рецензенту узнаваемую отправную точку, не создавая впечатления, будто подпись объясняет каждую дочернюю команду.

## Отзывайте доступ вместе с процессом и явно оформляйте исключения

Подтверждение сеанса должно следовать времени жизни процесса агента, потому что именно процесс сделал запрос. Повторное использование подтверждения только потому, что терминал все еще открыт, окно IDE видно или задача имеет то же имя, создает область доступа, которую невозможно честно проверить.

Это правило создает неудобства для короткоживущих оболочек. Менеджер пакетов может запускать новый процесс агента для каждой подкоманды. Не исправляйте это, молча распространяя подтверждение на всех будущих потомков npm или на каждый процесс в терминале. Решите, нужен ли рабочему процессу стабильный подписанный хост агента, долгоживущий процесс с одной понятной идентичностью или подтверждение каждого чувствительного вызова.

Подтверждение каждого вызова правильно, когда риск связан с каждым использованием, а не с идентичностью запуска. Токен для развертывания в рабочей среде, закрытый SSH-ключ к важному хосту или административный API с правом записи могут требовать решения человека при каждом использовании, даже если идентичность процесса знакома. Узнаваемость процесса уменьшает путаницу, но не отменяет необходимость суждения.

Проверка реализации проста: завершите одобренный процесс, запустите ту же команду снова и убедитесь, что второй запуск требует отдельного подтверждения. Затем запустите другую команду в той же вкладке терминала и убедитесь, что она не может унаследовать решение первого запуска. Если один из тестов не проходит, система одобрила место или метку, а не актера.

Рецензенту никогда не должно приходиться использовать имя терминала как замену идентичности процесса. Сохраняйте цепочку, определяйте запрашивающий запуск, показывайте самую сильную узнаваемую сторону, которую можете защитить доказательствами, и оставляйте отсутствие данных видимым. Это менее эффектно, чем широкое правило «доверять IDE». Зато такой подход выдерживает ситуацию, когда важной частью цепочки оказывается скрипт задачи, оболочка или удаленный переход.
