# Порядок действий агента для надежного восстановления хода инцидента

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

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

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

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

У каждого вызова есть несколько важных моментов. Агент решает вызвать инструмент. Шлюз принимает запрос и проверяет его разрешение. Затем он передает работу HTTP-сервису или SSH-помощнику. Удаленная сторона может принять запрос. Возвращается ответ. Шлюз передает результат агенту. Если считать все это одним событием, исчезают доказательства, нужные для объяснения сбоя.

Храните локальный монотонно возрастающий `call_sequence` в каждой сессии. Назначайте его, когда шлюз принимает вызов, до начала сетевой работы. Это число отвечает на ограниченный, но полезный вопрос: «В каком порядке этот шлюз принял вызовы от данного процесса агента?» Оно не утверждает, что описывает порядок выполнения на удаленной стороне. Четко обозначенная область действия лучше широкого заявления, которое невозможно защитить доказательствами.

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

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

RFC 3339 задает удобный совместимый формат для календарных временных меток, включая явное смещение UTC. Для экспорта и просмотра человеком используйте его форму UTC, например `2025-03-08T21:14:03.482Z`. RFC 3339 не обещает причинно-следственный порядок. За него отвечает ваш сборщик, а для этого нужны поля последовательности и четкие границы событий.

## Сессия должна идентифицировать действующий процесс, а не расплывчатую задачу

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

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

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

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

```json
{
  "event_id": "01JNRQ2Q9Y9J0R3E5P8F7K2X4M",
  "journal_sequence": 8124,
  "event_type": "session.opened",
  "occurred_at": "2025-03-08T21:14:02.901Z",
  "session_id": "sess_7f31c4",
  "process": {
    "pid": 48102,
    "code_signing_authority": "observed signing authority",
    "local_user": "developer account"
  }
}
```

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

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

## Для одного вызова нужен жизненный цикл, а не одна строка о завершении

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

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

Для разрешенного HTTP-действия зафиксируйте отдельные границы событий:

1. `call.accepted` записывает упорядоченный запрос на шлюзе.
2. `call.authorized` или `call.denied` записывает решение контроля.
3. `call.dispatched` показывает, что шлюз передал запрос сетевому клиенту.
4. `call.result_received` показывает транспортный итог или ответ удаленной стороны.
5. `call.result_returned` показывает результат, возвращенный агенту.

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

Сохраняйте `request_started_at`, `dispatched_at`, `result_received_at` и `result_returned_at`, когда соответствующие моменты происходят. Для не наступившего момента используйте null. Не придумывайте время завершения, если процесс аварийно завершился. Запишите более позднее событие восстановления, указывающее, что сборщик обнаружил незавершенный вызов.

Пример показывает структуру завершенного запроса без токена bearer и полного тела ответа:

```json
{
  "event_id": "01JNRQ3M8W7P0Q4R6S9T1V2X3Y",
  "journal_sequence": 8131,
  "event_type": "call.result_received",
  "occurred_at": "2025-03-08T21:14:05.841Z",
  "session_id": "sess_7f31c4",
  "call_id": "call_00017",
  "call_sequence": 17,
  "channel": "http",
  "target": "api.internal.example/v1/releases",
  "method": "POST",
  "dispatch_event_id": "01JNRQ3G2A...",
  "outcome": {
    "transport": "response",
    "http_status": 201,
    "response_digest": "sha256:..."
  }
}
```

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

## Повторы и тайм-ауты создают самую сложную неопределенность

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

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

Каждой сетевой попытке нужны собственные `call_id` и `call_sequence`. Добавляйте `retry_of`, когда попытка напрямую следует за предыдущей. Сохраняйте причину повтора, которую видел агент: тайм-аут, сброс соединения или полученный статус, допускающий повтор. Такая связь позволяет проследить цепочку и не сворачивать ее в одну запись.

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

```text
sequence 41  accepted         21:14:11.024Z  create release, request r_8d2
sequence 41  dispatched       21:14:11.027Z
sequence 41  result_received  21:14:41.031Z timeout
sequence 41  result_returned  21:14:41.034Z timeout returned to agent
sequence 42  accepted         21:14:42.112Z  retry_of call_00041, request r_8d2
sequence 42  dispatched       21:14:42.115Z
sequence 42  result_received  21:14:42.490Z HTTP 201
sequence 42  result_returned  21:14:42.493Z HTTP 201 returned to agent
```

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

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

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

## Записывайте два вида времени и объясняйте их ограничения

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

Для каждого события журнала записывайте RFC 3339 UTC `occurred_at`. Для событий внутри активной сессии также записывайте `monotonic_ns`, измеренное от выбранного процессом начала монотонных часов. Не сравнивайте монотонные показания разных машин, если специально не установили общую точку отсчета. Это локальные измерения.

Коррекция часов может создать такую путаницу:

```text
journal 901  wall 21:19:07.900Z  monotonic 5562019921  call accepted
journal 902  wall 21:18:58.104Z  monotonic 5562026310  call dispatched
```

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

NIST Special Publication 800-92, Guide to Computer Security Log Management, рекомендует синхронизировать часы и определить требования к данным журнала до инцидента. Это правильная рекомендация, но синхронизация часов сама по себе не устанавливает порядок внутри запуска агента. Она улучшает сопоставление с удаленным API, CI-сервисом или журналами хоста. Локальная последовательность по-прежнему устанавливает порядок сборщика.

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

Для полей длительности тоже нужно точное определение. `gateway_duration_ms` может означать время от принятия до возврата результата. `network_duration_ms` может означать время от отправки до получения результата. Запишите определение рядом со схемой. Иначе отчет о том, что вызов длился 30 секунд, не объяснит, произошла ли задержка до отправки, на удаленном сервисе или после возврата ответа.

## Сборщик аудита должен выбрать порядок до публикации результатов

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

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

Типичная проблема выглядит так. Рабочий процесс A принимает вызов 17 и начинает медленный запрос. Рабочий процесс B принимает вызов 18 и быстро завершается. Если процессы добавляют только записи о завершении, журнал начинается с успеха вызова 18. Расследование не может понять, выполнялся ли вызов 17, не был ли отправлен или был пропущен. События принятия и отправки вызова 17 закрывают этот пробел.

Цепочка хешей добавляет доказательство неизменности к зафиксированной последовательности. Каждая запись содержит дайджест предыдущей зафиксированной записи и дайджест собственных канонических данных. Канонизация важна: одинаковые данные должны давать одинаковые байты до хеширования. Укажите порядок полей, кодировку UTF-8, представление времени, обработку null и форматы чисел. Фраза «мы хешируем JSON» не является спецификацией, потому что обычный порядок объектов JSON не относится к свойствам безопасности.

Концептуальная запись может содержать такие поля:

```json
{
  "journal_sequence": 8131,
  "event_id": "01JNRQ3M8W7P0Q4R6S9T1V2X3Y",
  "previous_hash": "sha256:9c7d...",
  "record_hash": "sha256:04b1...",
  "payload": {"event_type": "call.result_received"}
}
```

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

Sallyport строит журналы Sessions и Activity из одного зашифрованного журнала аудита с цепочкой хешей. Команда `sp audit verify` проверяет цепочку офлайн поверх шифротекста, без ключа vault. Благодаря этому представление сессии и представление отдельных вызовов связаны с одним упорядоченным источником, а расследованию не приходится сопоставлять два разных журнала.

## Создавайте хронологию, сохраняющую неопределенность

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

Предположим, процесс агента получил одобрение в 09:00:00. В 09:03:14 он отправил SSH-команду. В 09:03:16 клиент потерял соединение. В 09:03:18 агент через HTTP запросил состояние целевой системы и обнаружил измененную конфигурацию. Эти данные допускают несколько объяснений: SSH-команда завершилась, состояние изменил другой участник или сработала ранее поставленная в очередь задача. В хронологии нужно указать, какой вывод подтверждают доказательства, а какой нет.

Используйте в заметках расследования такой формат:

| Порядок | Время | Доказательство | Подтверждаемое утверждение |
|---|---|---|---|
| 444 | 09:03:14.120Z | `call.dispatched` | Шлюз передал SSH-команду помощнику. |
| 445 | 09:03:16.202Z | разрыв транспорта | Шлюз не получил код возврата. |
| 446 | 09:03:18.810Z | ответ на HTTP-запрос | В этот момент запрошенная конфигурация отличалась. |
| 447 | 09:03:19.001Z | `call.result_returned` | Агент получил результат запроса. |

Не пишите «SSH-команда изменила конфигурацию», если у вас нет прямого доказательства, связывающего команду с удаленным эффектом. Такое связывание могут дать удаленные записи аудита, уникальный идентификатор операции или ответ с постоянным идентификатором запроса на сервере. Близкая временная метка сама по себе этого не доказывает.

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

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

## Работа с секретами должна выдержать расследование

Журналы аудита часто подводят именно в момент, когда становятся особенно полезными: кто-то хочет записать все заголовки, переменные окружения оболочки и тела ответов «только для этого расследования». Такое решение может превратить локальный инцидент с агентом в утечку учетных данных.

Записывайте идентичность действия без секретных данных. Для HTTP сохраняйте метод, нормализованные хост и путь, ссылку на учетные данные или метку ключа, безопасные имена заголовков, дайджест запроса, статус ответа, идентификатор запроса провайдера, если он есть, и тщательно выбранную сводку ответа. Никогда не записывайте заголовок авторизации, исходный ключ API, закрытый ключ или полный дамп окружения.\n
Для SSH записывайте ссылку на хост, ссылку на учетную запись, если это разрешено политикой, нормализованное представление команды, дайджест команды, состояние соединения и код возврата, если он получен. Сами команды могут содержать секреты. Если рабочий процесс допускает произвольный текст оболочки, используйте защищенное хранилище доказательств для строго разрешенного просмотра или сохраняйте только редактированную форму вместе с дайджестом. Не считайте журнал команд безопасным только потому, что в нем нет паролей.

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

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

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

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

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

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

Попросите его ответить по одному только экспорту на пять вопросов:

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

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

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

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