# Идентичность агента и учетных данных: перестаньте скрывать действующих субъектов

AI-агент, у которого есть производственные учетные данные, создает сразу две проблемы с идентичностью. Агент может действовать от имени учетной записи, а позже команде приходится делать вид, будто имя учетной записи объясняет, кто именно действовал. Это не так. API-запрос, прошедший аутентификацию как `deploy-bot`, показывает, что у кого-то были учетные данные `deploy-bot`. Но он не говорит, был ли вызывающим одобренный агент для программирования, скопированный shell-скрипт, вредоносная зависимость или процесс, который остался работать после теста.

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

## Учетные данные показывают полномочия, но не вызывающий процесс

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

Представьте запрос на удаление объекта в облачном хранилище. Получающий API может увидеть bearer-токен и сопоставить его с сервисной учетной записью `release-publisher`. Эта учетная запись отвечает на вопрос авторизации: может ли она удалить объект? Но она не отвечает на вопрос атрибуции: какой локальный процесс использовал токен, кто его запустил, какой код он выполнил и одобрил ли кто-то этот запуск?

В спокойное время эти вопросы кажутся похожими, потому что ожидаемый процесс и учетная запись часто принадлежат одному владельцу. Под давлением они расходятся. Разработчик копирует токен в локальное окружение. Агент для программирования наследует это окружение. Плагин, запущенный агентом, читает токен. Все запросы по-прежнему выглядят как запросы `release-publisher`. Слой авторизации работает ровно по настройкам, но расследование теряет сведения о действующем субъекте.

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

У этого различия есть практическое следствие:

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

Если одно поле должно выполнять все четыре задачи, оно не справится ни с одной из них. Имя учетной записи `agent-prod-42` не решает проблему. Более удачная метка не превращает bearer-токен в доказательство того, кто им воспользовался.

## В одном запросе агента участвуют четыре идентичности

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

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

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

В-третьих, есть идентичность учетных данных. Это сервисная учетная запись, SSH-субъект, OAuth-клиент или запись API-токена, которую принимает целевая система. Она показывает, что разрешит целевая система.

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

Понятная запись может выглядеть так:

```json
{
  "event": "http.request",
  "session_id": "ses_7zQ2...",
  "process": {
    "pid": 8421,
    "signing_authority": "Example Developer ID",
    "parent_pid": 8190,
    "launch": "interactive-terminal"
  },
  "credential_ref": "cred_release_publisher",
  "credential_subject": "release-publisher",
  "target": "api.example.internal",
  "operation": "POST /releases",
  "decision": "approved",
  "result": 201
}
```

`credential_ref` указывает на запись в хранилище, а не на секрет. Блок `process` дает сведения, которых не может дать субъект учетных данных. Теперь проверяющий видит, что одна одобренная сессия использовала `release-publisher` для одной цели, и может отличить этот запуск от другого процесса, который позже воспользовался той же учетной записью.

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

## Bearer-токены стирают самые важные сведения

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

RFC 6750 прямо описывает использование bearer-токенов: любая сторона, владеющая токеном, может его использовать. Благодаря этому bearer-токены легко внедрять, но API не может отличить одобренного агента от скопированного процесса, если не добавить отдельный механизм контроля. Распространенный ответ, «мы положили токен в менеджер секретов», решает проблему хранения в состоянии покоя. Он не объясняет, что происходит после того, как процесс получил значение.

OAuth 2.0 Security Best Current Practice, RFC 9700, движется в правильную сторону: там рекомендуется, где уместно, ограничивать токены конкретным отправителем, а также использовать короткий срок действия и ограничение аудитории. Ограничение отправителя снижает риск повторного использования токена, поскольку ресурсный сервер проверяет доказательство от предполагаемого клиента. Но оно не отменяет необходимости определить процесс, который контролирует это доказательство. Если процесс агента может использовать закрытый материал доказательства, атакующий, захвативший процесс, все равно сможет действовать от имени клиента.

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

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

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

## Идентичность процесса должна опираться на сведения, сохраняющиеся после подсказки

Идентичность процесса должна основываться на фактах, которые может сообщить операционная система или среда выполнения, а не на строке, которую агент передает в запросе к инструменту. Заявление агента `name: trusted-release-agent` является утверждением, а не доказательством идентичности.

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

На сервере ищите аналогичные сведения. Идентичность рабочей нагрузки, выданная средой выполнения, проверенный дайджест образа, взаимно аутентифицированный канал или аттестация супервизора процессов могут связать действие с экземпляром рабочей нагрузки. SPIFFE описывает эту модель с помощью SPIFFE Verifiable Identity Document и X.509 SVID: рабочая нагрузка получает идентичность от своей среды, а затем предъявляет ее другой рабочей нагрузке. Такая модель надежнее общей переменной окружения, поскольку идентичность привязана к рабочей нагрузке.

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

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

```text
session_id: ses_7zQ2...
executable: /Applications/Agent.app/Contents/MacOS/agent
signing_authority: Example Developer ID
parent_process: /Applications/Terminal.app
started_at: 2025-03-08T14:03:19Z
owner: developer@example.invalid
```

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

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

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

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

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

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

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

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

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

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

## Подстановка учетных данных не дает секретам попасть к агенту

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

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

Безопасный запрос действия содержит достаточно сведений для авторизации и выполнения вызова, но не содержит сами учетные данные:

```json
{
  "session_id": "ses_7zQ2...",
  "credential_ref": "cred_release_publisher",
  "channel": "http",
  "request": {
    "method": "POST",
    "url": "https://api.example.internal/releases",
    "headers": {"Content-Type": "application/json"},
    "body": {"version": "1.4.2"}
  }
}
```

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

Тот же принцип действует для SSH. Агент может запросить `git fetch` или удаленную команду через SSH-канал, но не должен получать закрытые учетные данные в виде PEM-блока или сокета агента, которым любой дочерний процесс сможет воспользоваться без контроля. Исполнитель может использовать закрытые учетные данные для соединения и связать событие с сессией агента.

Sallyport поддерживает подстановку HTTP-учетных данных и SSH-действия через встроенный помощник, поэтому агент с поддержкой MCP может запросить работу, не получая API- или SSH-секрет. Такая архитектура не делает каждое действие безопасным. Она сохраняет разделение, необходимое для решения, следует ли разрешить процессу этот запрос.

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

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

Представьте команду, которая хранит `PUBLISH_TOKEN` в хранилище секретов CI. Задание развертывания читает его, и это ожидаемо. Разработчик тоже экспортирует его локально, чтобы воспроизвести проблему с выпуском. Позже он запускает агента для программирования в том же shell, потому что агенту нужно изучить скрипты развертывания. Теперь агент может вызывать API выпуска через окружение.

Скрытая в файле репозитория инструкция просит агента выполнить команду, загружающую черновик выпуска. Команда завершается успешно. В журнале аудита API написано: `release-publisher created release 1.4.2`. В журнале CI нет соответствующего задания. Разработчик говорит, что просил агента только изучить файлы. Все могут согласиться с этими фактами, но ни один из них не показывает, какой процесс сделал вызов.

Команда часто отвечает ротацией `PUBLISH_TOKEN`. Это останавливает повторное использование при утечке токена, но одновременно ломает легитимное задание CI и вынуждает срочно все восстанавливать. Затем команда может обвинить разработчика, агента или инструкцию в репозитории, не имея доказательств. Имя учетной записи не позволяет отличить запуск CI от локального действия агента.

Теперь изменим архитектуру. Учетные данные выпуска остаются в хранилище под управлением исполнителя действий. Агент запускает сессию, в которой фиксируются его полномочия подписи и родительский терминал. Через эту сессию он запрашивает загрузку. Исполнитель записывает решение об одобрении, ссылку на учетные данные, целевой API, путь запроса и статус ответа.

Когда происходит подозрительная загрузка, запись инцидента сразу отвечает на первые полезные вопросы: действие исходило от локальной сессии агента, сессия началась из определенного терминала, авторизация была выдана в конкретное время, а учетные данные выпуска использовались для одного POST-запроса. Оператор отзывает эту сессию. CI продолжает работать, поскольку его отдельно идентифицированный процесс не отозван. Если инструкция в репозитории могла раскрыть более широкие секреты, команда все равно может ротировать учетные данные, но делает это на основании фактов, а не из-за отсутствия других средств контроля.

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

## Журналы аудита должны связывать идентичности, не сохраняя секреты

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

Создавайте одну запись для решения по сессии и одну запись для каждого действия. Используйте один и тот же `session_id` в обеих записях, а каждому действию присваивайте собственный идентификатор события. Сохраняйте ссылку на учетные данные и субъект учетных данных, если его безопасно хранить, но никогда не записывайте bearer-токен, закрытые учетные данные или исходный заголовок авторизации.

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

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

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

```text
$ audit verify events.log
verified: 184 events
chain: valid
first_event: evt_01J...
last_event: evt_01K...
```

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

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

Команды могут внедрить эту модель без перестройки всех интеграций с API. Начните с учетных данных, которые меняют производственное состояние, публикуют артефакты, открывают доступ к данным клиентов или подключаются по SSH. Именно такие учетные записи создают самые тяжелые инциденты, когда их наследует процесс агента.

Сделайте первый проход конкретным:

1. Перечислите каждый субъект учетных данных, доступные ему сервисы и все процессы, которые сейчас получают его секрет.
2. Уберите многоразовые секреты из подсказок, конфигурации и окружения процессов агентов.
3. Определите, какие сведения о процессе вы можете собрать в момент авторизации действий.
4. Выдавайте идентификатор сессии каждому запуску агента и записывайте его рядом со ссылкой на учетные данные для каждого действия.
5. Проверьте сдерживание: отзовите одну сессию, пока другая одобренная рабочая нагрузка продолжает пользоваться той же учетной записью.

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

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

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