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

Действия агентов требуют записей, которые описывают полномочия, а не только активность. Строка вроде POST /deploy returned 200 не скажет расследующему, тот ли процесс отправил запрос, разрешил ли его человек, какая система его получила и остановил ли что-нибудь последующий отзыв.
Практическая единица здесь, это неизменяемая запись действия, связанная с событиями сессии, одобрения и отзыва. Запись должна быть достаточно небольшой, чтобы ее можно было искать и проверять, но достаточно подробной, чтобы уставший инженер мог восстановить спорный вызов без догадок о пропущенных полях.
Аудитная запись должна описывать попытку с полномочиями
Аудитная запись действия агента фиксирует одну попытку повлиять на внешнюю систему и контекст полномочий, который существовал в этот момент. Действие может завершиться до выхода в сеть, прерваться во время передачи, получить отказ от цели или закончиться успешно. Все четыре случая должны попасть в аудитный след.
Команды часто записывают одну из двух неподходящих вещей. Они сохраняют расшифровку чата, которая описывает намерение, но не выполнение. Или записывают необработанный сетевой трафик, получая слишком много секретов и все равно не фиксируя решение человека, разрешившего вызов. Ни одна из этих записей не отвечает на вопрос, имел ли конкретный участник право выполнить конкретную операцию.
Считайте это разными объектами:
- Сессия определяет один запущенный процесс агента и период его работы.
- Одобрение фиксирует решение человека с заданной областью действия.
- Действие описывает одну запрошенную внешнюю операцию и ее результат.
- Отзыв прекращает сессию или авторизацию в точно указанное время.
Во время инцидента это различие имеет практическое значение. Если человек отозвал сессию в 14:03, нужно увидеть все действия до 14:03, все попытки после 14:03 и локальное решение, которое отклонило эти поздние попытки. Одна изменяемая строка session approved: false уничтожает эту историю.
Не связывайте модель записи с успешностью действия. Сам запрос может свидетельствовать о злонамеренном намерении, ошибке конфигурации или неправильном понимании задачи агентом. Неудачные вызовы часто дают первые полезные сведения.
Участник, это процесс, а не дружелюбная метка агента
Записывайте процесс, который фактически запросил действие. Имя модели, рабочего пространства или псевдоним агента могут помочь человеку быстро просмотреть отчет, но ни один из них не определяет однозначно исполняемый файл, удерживавший соединение в тот момент.
Полезный объект участника содержит внутренний идентификатор сессии, ID процесса, путь к исполняемому файлу, время запуска и полномочия подписи кода, если операционная система их предоставляет. Когда это можно надежно собрать, добавьте ID родительского процесса и его исполняемый файл. Связь с родителем часто показывает, запустил ли агента терминал, породило ли его расширение редактора или это сделал неожиданный вспомогательный процесс.
Используйте снимок идентичности процесса, а не пытайтесь определить ее позже. ID процессов переиспользуются. Пути могут измениться. Поздний поиск не восстановит отсутствующее наблюдение.
{
"actor": {
"session_id": "ses_01J8Q1F9K9Z3",
"pid": 48217,
"started_at": "2025-02-18T21:14:06Z",
"executable": "/usr/local/bin/agent-runner",
"signing_authority": "Developer ID Application: Example Developer",
"parent_pid": 48091,
"parent_executable": "/Applications/Terminal.app"
}
}
Значение signing_authority должно поступать из проверки подписи платформы, а не из строки, которую передал агент. Агент может назвать себя claude-code, deploy-helper или trusted-agent. Метка остается лишь оформлением, если операционная система не связывает ее с идентичностью исполняемого файла.
Не превращайте идентичность процесса в ложное утверждение об авторстве. Подписанный бинарный файл может действовать плохо после внедрения в запрос, компрометации плагина или неосторожной инструкции. Это поле показывает, какие полномочия кода инициировали запрос. Оно не подтверждает разумность запроса.
RFC 5424, спецификация протокола syslog, отделяет поля заголовка, такие как имя приложения и ID процесса, от структурированных данных. Для записей агентов это разделение по-прежнему полезно. Стабильные идентификаторы и время помещайте в отдельные поля, по которым могут выполнять запросы машины. Переменный контекст, например репозиторий или ссылка на задачу, храните в объекте с пространством имен. Если складывать все в одну строку сообщения, любое последующее расследование превращается в разбор текста.
Цели нужны и адрес, и смысл
Записывайте, куда направилось действие и на какой ресурс или команду оно хотело повлиять. Это связанные факты, но они не взаимозаменяемы.
Для HTTP сетевым назначением может быть api.example.internal, а осмысленной операцией, POST /v1/releases/{release_id}/promote. Сохраняйте хост, порт, протокол, HTTP-метод и шаблон маршрута. Добавьте идентификатор целевой системы, которым управляет ваша команда, например release-service-prod. Идентификатор переживет миграцию хоста, а имя хоста поможет понять, какой запрос действительно покинул машину.
Для SSH сохраняйте псевдоним или имя хоста, порт, отпечаток ключа хоста или ссылку на запись known-hosts и метку удаленной учетной записи, если ее безопасно хранить. В цели также должен быть указан класс запрошенной команды. restart-worker сообщает больше, чем ssh succeeded, но раскрывает меньше, чем полная команда оболочки с путями клиентов и переменными окружения.
Удаляйте секреты до сохранения, а не после того, как аналитик откроет журнал. Строки запроса URL, заголовки запросов, аргументы оболочки и тела JSON регулярно содержат токены. Библиотека журналирования, которая сохраняет их «для отладки», однажды обязательно создаст файл инцидента, полный учетных данных.
Такая форма записи сохраняет сведения о цели доступными для запросов, не копируя запрос целиком:
{
"target": {
"kind": "http",
"system_id": "release-service-prod",
"endpoint": {
"scheme": "https",
"host": "api.example.internal",
"port": 443,
"method": "POST",
"route_template": "/v1/releases/{release_id}/promote"
}
},
"operation": {
"name": "promote_release",
"request_fingerprint": "sha256:8e8c...",
"request_bytes": 286,
"redacted_parameters": {
"environment": "production",
"release_id": "rel_7b2"
}
}
}
Отпечаток полезен только при определении каноникализации. Сортируйте поля объектов, удаляйте поля, исключенные политикой редактирования, нормализуйте кодировку текста, а затем хешируйте получившиеся байты. Записывайте версию каноникализации. Иначе два равнозначных запроса могут получить несвязанные дайджесты, а изменение схемы в будущем сделает старые записи подозрительными.
Дайджест не заменяет сохраненные доказательства, если регулятор, договор или процедура расследования требуют исходного запроса. В таком случае зашифруйте доказательства отдельно, ограничьте доступ и сохраните их дайджест содержимого в записи действия. Не помещайте исходное тело в обычный поисковый журнал только потому, что хранилище дешево.
Запрошенная операция и наблюдаемый результат требуют разных полей
Запрос агента выражает намерение. Результат показывает, что шлюз увидел после попытки выполнить это намерение. Не объединяйте их в расплывчатый статус вроде completed.
Для сетевого вызова записывайте этап передачи, на котором остановилось выполнение, протокольный статус, если он есть, ограниченную сводку ответа и затраченное время. Для SSH фиксируйте результат соединения, результат аутентификации, код выхода удаленной команды и ограниченные сводки стандартного вывода и ошибок. Цель может принять запрос и выполнить асинхронную задачу позже, поэтому ответ 202 не означает, что внешнее изменение произошло.
Используйте словарь результатов, который указывает место сбоя. Например:
denied_vault_lockedозначает, что локальная граница секретов отклонила вызов.denied_approvalозначает, что необходимое решение человека не разрешило действие.network_errorозначает, что шлюз не смог установить или сохранить соединение.target_rejectedозначает, что удаленная служба вернула отказ.target_acceptedозначает, что удаленная служба приняла запрос.
Не помещайте необработанную ошибку передачи в основное поле статуса. Рядом с коротким диагностическим сообщением без секретов указывайте нормализованный код, например dns_lookup_failed или tls_validation_failed. Инженерам нужна агрегация, а тем, кто расследует инцидент, достаточно локального контекста, чтобы отличить просроченный сертификат от заблокированного хоста.
Полный объект результата может выглядеть так:
{
"result": {
"outcome": "target_accepted",
"started_at": "2025-02-18T21:19:42.184Z",
"finished_at": "2025-02-18T21:19:43.021Z",
"duration_ms": 837,
"http_status": 202,
"response_fingerprint": "sha256:2a64...",
"response_summary": "promotion job accepted",
"evidence_ref": null
}
}
Не называйте target_accepted успехом в схеме. Позже это создаст проблемы. Если служба принимает задачу, а сама задача завершается ошибкой, шлюз сделал все правильно, но бизнес-операция не завершилась. На этот отдельный вопрос может ответить более позднее событие о завершении на удаленной стороне, связанное с ID задачи.
В записях об одобрении нужно указывать, что именно одобрил человек
Запись об одобрении должна содержать решение, область действия, объект и временные метки. Фраза «Пользователь одобрил агента» говорит слишком мало. Проверяющим остается гадать, разрешил ли человек один вызов API, конкретные учетные данные, сессию или все будущие сессии похожего процесса.
Одобрение сессии обычно разрешает вызовы одного определенного процесса до его завершения или отзыва одобрения. Одобрение отдельного вызова относится к одному использованию одних учетных данных или одной операции. Записывайте область действия напрямую: эти два механизма создают совершенно разный риск.
{
"approval": {
"approval_id": "apr_01J8Q1P4Y5D6",
"decision": "approved",
"scope": "session",
"subject_session_id": "ses_01J8Q1F9K9Z3",
"approved_at": "2025-02-18T21:14:11Z",
"expires_at": "2025-02-18T22:02:53Z",
"approver_presence": "local_user_confirmation"
}
}
Не заявляйте большей уверенности, чем позволяет ваш интерфейс. Если приложение получило событие локального подтверждения, зафиксируйте этот факт. Не записывайте имя человека, учетную запись поставщика идентификации или биометрический метод, если система действительно не проверяет личность и не хранит эту связь в рамках документированной политики. Выдуманные сведения о личности создают ложную уверенность и обязанности по защите персональных данных.
Сохраняйте идентификатор одобрения, которое управляло действием, в самой записи действия. Также записывайте решение об авторизации, принятое непосредственно перед выполнением. Это кажется избыточным, пока не приходится расследовать пограничный случай по времени: старое одобрение сессии может существовать, но требование одобрения отдельного вызова могло отклонить действие. Записи нужны оба факта.
Усталость от подтверждений, это дефект дизайна, а не причина отказываться от доказательств одобрения. Если человеку приходится подтверждать каждый безобидный запрос, он начнет нажимать автоматически. Запрашивайте подтверждение для каждого вызова только при работе с учетными данными или операциями, неправильное использование которых требует непосредственного внимания, и указывайте в записи, почему шлюз запросил подтверждение.
Отзыв, это событие с моментом отсечения
Отзыв прекращает полномочия на будущее. Он не удаляет сессию, не отменяет уже переданный запрос и не меняет решение об одобрении, существовавшее пять минут назад.
Записывайте объект отзыва, тип инициатора, время наблюдения и результат применения. Если шлюз может завершить или заблокировать активную сессию, фиксируйте, сделал ли он это. Если действие уже пересекло сетевую границу, прямо укажите, что отзыв не может его отменить. Во время реагирования инженерам нужна эта неприятная правда, а не успокаивающая, но ложная метка revoked.
Рассмотрим такую последовательность сбоя:
- В 09:00 процесс агента получает одобрение сессии и в 09:17 отправляет изменение в рабочую среду.
- Оператор замечает неожиданную цель и отзывает сессию в 09:18:04.
- Цель отвечает на запрос от 09:17 в 09:18:07, поскольку уже поставила работу в очередь.
- В 09:18:09 агент пытается выполнить еще один вызов, и шлюз его отклоняет.
Хороший журнал сохраняет все четыре события. Действие в 09:17 было разрешено в момент начала. Ответ после отзыва относится к более раннему действию. Отклоненная попытка доказывает, что отзыв вступил в силу для последующей работы. Если пометить каждое прежнее действие как revoked, исчезнет последовательность, объясняющая реальный масштаб воздействия.
Используйте монотонный порядковый номер вместе с календарным временем. Часы могут расходиться, пользователи могут менять местное время, а несколько событий могут иметь одинаковую точность временной метки. Порядковый номер устанавливает порядок, в котором журнал принял записи. Если система работает на нескольких хостах, сохраняйте локальную последовательность каждого хоста и используйте ID корреляции, а не делайте вид, будто настенные часы устанавливают идеальный глобальный порядок.
Защита от незаметного изменения требует режима добавления без перезаписи
Цепочка хешей затрудняет незаметное редактирование, добавляя дайджест предыдущей записи в каждую новую. Она не превращает обычный файл журнала в доказательство существования каждого ожидаемого события. Тот, кто контролирует записывающий процесс и сохраненную вершину цепочки, может удалить хвост, начать новую цепочку или не дать записям попасть в надежное хранилище.
Это ограничение не делает цепочки хешей бесполезными. Они отвечают на более узкий, но важный вопрос: изменились ли записи, исчезли ли записи из середины или пришли ли они в другом порядке после записи системой? Поля цепочки должны быть явными.
{
"journal": {
"sequence": 1842,
"recorded_at": "2025-02-18T21:19:43.024Z",
"previous_hash": "sha256:68b1...",
"record_hash": "sha256:93f4...",
"hash_format": "canonical-json-v1"
}
}
Хешируйте всю каноническую запись, исключая только сам record_hash. Не хешируйте красиво отформатированное представление, в котором пробелы, порядок полей или формат временных меток меняются между версиями. Версионируйте канонический формат и сохраняйте средство проверки для каждого выпущенного формата.
NIST Special Publication 800-92, Guide to Computer Security Log Management, рассматривает создание, хранение, анализ и хранение в течение установленного срока журналов как разные обязанности. Это помогает избежать распространенной ошибки: команды добавляют хеш к записям и считают работу законченной. Вам по-прежнему нужны надежное хранилище, контроль доступа к записывающему процессу, решение о сроках хранения, регулярная проверка и процедура расследования неудачной проверки.
Sallyport строит журналы Sessions и Activity из одного зашифрованного аудитного журнала, недоступного для изменения со стороны читателя и защищенного цепочкой хешей. Команда sp audit verify проверяет цепочку офлайн по шифротексту, не требуя ключа хранилища. Такой дизайн отделяет возможность читателя проверить целостность журнала от возможности использовать учетные данные.
Один ID корреляции делает запись полезной в стрессовой ситуации
Присваивайте каждому запрошенному действию ID до проверки авторизации шлюзом. Передавайте его через локальное решение, сетевую попытку, обработку ответа и любой последующий callback о завершении на удаленной стороне. Когда инженер видит спорное развертывание, он должен найти один идентификатор и получить всю хронологию.
Используйте связанные ID для разных отношений. Действие ссылается на сессию, одобрение, ссылку на учетные данные, цель и порядковый номер журнала. Ссылка на задачу или разговор может вести от действия обратно к контексту агента, но не делайте расшифровку разговора источником истины. Текст запроса меняется, может содержать личные сведения и часто неточно описывает фактический запрос.
Компактная полная запись может иметь такую форму:
{
"schema_version": "1.0",
"action_id": "act_01J8Q2ABR8M7",
"event_type": "action.completed",
"actor": {"session_id": "ses_01J8Q1F9K9Z3", "pid": 48217},
"target": {"kind": "http", "system_id": "release-service-prod"},
"operation": {"name": "promote_release", "request_fingerprint": "sha256:8e8c..."},
"authorization": {"vault": "unlocked", "approval_id": "apr_01J8Q1P4Y5D6", "decision": "approved"},
"result": {"outcome": "target_accepted", "http_status": 202},
"journal": {"sequence": 1842, "previous_hash": "sha256:68b1..."}
}
Не используйте ID корреляции как токен авторизации. Создавайте его независимо, делайте непредсказуемым, если его могут увидеть внешние вызывающие стороны, и никогда не считайте владение этим ID разрешением на чтение действия или выполнение последующего вызова.
Хранение должно сохранять доказательства и не создавать второе хранилище секретов
Аудитные данные накапливают личные и внутренние сведения, даже если вы тщательно удаляете секреты. Имена целей могут раскрыть клиентов, параметры маршрутов, внутренние проекты, сводки ответов, состояние учетных записей, а пути процессов, привычки разработчиков. Определите, кто может искать записи, кто может экспортировать их и как долго остается доступной каждая категория.
Храните доступные для поиска метаданные действий отдельно от любых зашифрованных доказательств запроса или ответа. Для подробных доказательств устанавливайте более короткий срок хранения, когда необходимость расследования исчезает. Сохраняйте хеши и записи связей достаточно долго, чтобы доказать, что оставшиеся сводки по-прежнему соответствуют данным, записанным системой.
Проверьте модель на реальном вопросе из разбора инцидента: «Какой подписанный процесс запросил доступ к этой рабочей цели, в рамках какого одобрения и продолжались ли попытки после отзыва?» Если для ответа аналитику приходится соединять неструктурированные сообщения, изучать отладочные журналы клиента или спрашивать агента, что он помнит, модель неполна.
Фиксированная цепочка решений Sallyport задает четкие границы для этой модели: шлюз хранилища, авторизация сессии и одобрение учетных данных для отдельного вызова создают разные факты авторизации. В собственной схеме аудита также храните их отдельно. Запись должна показывать, где остановились полномочия, а не прятать каждый отказ под общим кодом ошибки.
Создайте схему до того, как агенты получат широкие полномочия. Если после инцидента восстанавливать идентичность участника, область действия одобрения и порядок отзывов, придется собирать полномочия по пробелам. Это медленная работа, которая обычно заканчивается словами «мы думаем», хотя запись должна была точно показать, что произошло.
Вопросы и ответы
Чем журнал сессии агента отличается от журнала аудита действий?
Журнал сессии показывает, что процесс существовал в определенный период. Аудитная запись действия описывает, что именно процесс пытался сделать, какой узел получил запрос, какие ограничения для учетных данных действовали, одобрил ли действие человек и чем оно завершилось. Нужны оба вида записей: отозванная сессия могла выполнить множество вызовов до отзыва.
Нужно ли записывать только успешные действия агента?
Нет. Успешный статус HTTP описывает только ответ, который увидел вызывающий процесс. Записывайте результат передачи, код статуса, краткое описание ответа и любую локально обнаруженную ошибку выполнения. Так расследование сможет отличить отказ, тайм-аут и завершенное удаленное изменение.
Как определить целевую систему, не записывая секреты?
Используйте стабильный внутренний идентификатор целевой системы и описание цели без секретов, например хост API и шаблон маршрута или псевдоним SSH-хоста и класс команды. Не сохраняйте по умолчанию исходные учетные данные, заголовки авторизации, токены в URL или целые тела запросов. Журнал, который копирует секреты, не выполняет свою задачу безопасности.
Зачем нужны полномочия подписи кода в журнале аудита агента?
Записывайте путь к исполняемому файлу, ID процесса, ID родительского процесса, если он доступен, время запуска и полномочия подписи кода. Полномочия подписи отвечают на другой вопрос, не тот, что имя процесса: они помогают отличить ожидаемый бинарный файл от другой программы, использующей знакомую метку.
Как записывать одобрение сессии и одобрение отдельного действия?
Одно одобрение может распространяться на сессию процесса, а другое, отдельное, на одно использование чувствительных учетных данных. В записи должны быть указаны область действия одобрения и его решение. Без этой области проверяющие часто предполагают, что прежнее одобрение разрешало более поздний важный вызов, хотя это было не так.
Нужно ли менять прежние одобренные действия после отзыва?
Не изменяйте исходные записи действий. Добавьте событие отзыва, в котором указаны сессия или авторизация, действие которых прекращено. Не переписывайте прежние записи так, будто позже они стали несанкционированными. Хронология покажет, какие вызовы произошли до вступления отзыва в силу.
Может ли журнал аудита с цепочкой хешей доказать безопасность действия агента?
Цепочка хешей обнаруживает удаление, вставку или изменение записей, если у вас сохранилось ожидаемое состояние цепочки и вы проверяете ее последовательность. Она не определяет, было ли одобренное действие разумным, и не доказывает, что удаленная система выполнила запрос. Сочетайте ее с точными записями и, при необходимости, журналами на стороне удаленной системы.
Что записывать для HTTP-запросов, которые выполняют AI-агенты?
Начните с отпечатка запроса, а не с исходного тела. Сохраняйте метод, шаблон маршрута, выбранные параметры без секретов, размер тела и криптографический хеш канонического представления после удаления секретов. Храните зашифрованные доказательства отдельно только тогда, когда это оправдано правилами расследования и хранения данных.
Что должно входить в аудитную запись SSH-действия?
Фиксируйте адрес SSH, отпечаток ключа хоста или ссылку на запись known-hosts, метку удаленной учетной записи, если она не является секретом, класс команды, код завершения и ограниченные сводки stdout и stderr. Считайте полную командную строку чувствительными данными: в ней часто встречаются пути, идентификаторы и случайно попавшие токены.
На какие вопросы должен отвечать журнал аудита действий агента?
При расследовании инцидента нужно восстановить, кто запустил процесс, что он запросил, какое одобрение управляло действием, какой узел получил запрос, какой результат вернулся и изменил ли отзыв полномочия процесса. Если запись не отвечает на один из этих вопросов, добавьте поле или связанное событие.