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

AI-агент может составить убедительное объяснение своей работы уже после того, как принял неверное решение. Такое объяснение не служит свидетельством. Если агент видит, выбирает, исправляет или обходит записи, по которым проверяющие оценивают его работу, аудиторский след превращается ещё в одну область вывода, которую агент может оптимизировать.
Журналы аудита с защитой от чтения решают узкую, но важную задачу: процесс, запрашивающий внешнее действие, не должен управлять записью об этом действии. Система действий фиксирует то, что наблюдала, возвращает агенту нужный результат и хранит запись для проверки вне его доступа. Это не делает агента честным. Зато нечестному, растерянному или скомпрометированному агенту становится сложнее подправить историю задним числом.
Особенно важно это в случаях, когда агент может развёртывать код, вызывать платные API, менять настройки облака, отправлять почту или выполнять команды SSH. Здесь переписка в чате служит слабым свидетельством. Она фиксирует заявления и выбранный вывод инструментов. Запись, созданная в точке принудительного выполнения, может сохранить запрос, авторизацию, выполнение и результат, даже если агент предпочёл бы промолчать.
Агент не должен управлять и действием, и свидетельством
У агента должно быть меньше возможностей влиять на свидетельства, чем на саму задачу. Если дать ему файл audit.jsonl и попросить дописывать записи, получится дневник активности, а не журнал аудита. Агент может забыть строку, скрыть неудобный аргумент, записать успех до завершения вызова или создать вторую копию, которую проверяющий никогда не увидит.
Проблема возникает даже с агентами, которые следуют инструкциям. Агент часто решает, какие инструменты вызвать, какую ошибку перехватить и какой контекст сохранить. Инъекция в запрос может велеть ему не записывать запрос. Ошибка может отправить запрос через библиотеку без инструментирования. Вредоносная зависимость может писать напрямую в сеть. Если один и тот же процесс владеет действием и записью, любой из этих сбоев может оставить аккуратный, но неполный след.
Разделите возможности явно:
- Агент запрашивает действие и получает ограниченный результат.
- Исполнитель хранит учётные данные, отправляет запрос и создаёт запись события.
- Журнал принимает записи через путь, который агент не может читать или изменять.
- Проверяющий читает и проверяет записи через отдельный интерфейс.
Это граница возможностей, а не обещание правильного поведения. У агента не должно быть доступа к файлам журнала, API для запроса его записей, учётных данных, позволяющих переписывать журнал, или возможности задавать временную метку события. Если агенту нужно объяснить ход рассуждений, сохраняйте объяснение в отдельном артефакте запуска и помечайте его как материал, предоставленный агентом. Не смешивайте его с записью выполнения.
Команды часто смешивают намерение и свидетельство. Намерение, это то, что агент говорит о своей цели: «перезапустить сервис staging». Свидетельство, это то, что наблюдал компонент выполнения: агент запросил конкретную команду в определённое время, решение о разрешении было принято, исполнитель подключился к указанной ссылке на узел, а команда завершилась с определённым статусом. Оба вида данных полезны. Они отвечают на разные вопросы и требуют разного уровня доверия.
Защита от чтения означает разделение возможностей, а не задержку просмотра
Защита от чтения означает, что участник может создать запись, но не получает доступа к сохранённой записи или будущему контексту проверки. Это не значит, что агент не получает обратную связь. Задаче SSH нужны stdout, stderr и код завершения. HTTP-задаче нужны статус ответа и тело ответа или ограниченная выборка из него. Если убрать результаты, агент станет бесполезным, но свидетельства от этого не станут сильнее.
У этой границы есть четыре практические части. Во-первых, агент использует интерфейс запросов, а не учётные данные. Во-вторых, исполнитель создаёт запись на основе собственных наблюдений, а не доверяет JSON-объекту от агента. В-третьих, агент не может перечислять, получать, менять или удалять записи. В-четвёртых, проверяющий позже может изучить запись, не полагаясь на память агента или историю чата.
Схема с отложенным чтением не соответствует этому стандарту. Допустим, агент пишет события в базу данных и получает обещание, что не сможет запрашивать таблицу до конца сеанса. Он всё равно знает схему. Он может подбирать строки аргументов, выбирать пропуски, провоцировать сбои транзакций и по различиям во времени ошибок узнавать, попала ли запись в журнал. У него также могут оставаться учётные данные базы, которыми воспользуется другой путь выполнения.
Защита от чтения сильнее, если путь событий не возвращает ответа, раскрывающего состояние журнала. Исполнитель может подтвердить запрошенное действие, но не должен возвращать идентификаторы событий, номера последовательности, пометки проверяющих, метки риска или подтверждение того, что конкретная запись стала постоянной. Такие сведения дают участнику материал для оптимизации.
Это не требует считать каждого агента враждебным. Агент может стать небезопасным по вполне обычным причинам: из-за внедрённой инструкции, бесконечного цикла, ошибки инструмента, устаревшей зависимости или оператора, выдавшего слишком широкие полномочия. Конструкция должна выдерживать больше, чем добрые намерения.
Одного журнала только для добавления недостаточно
Хранилище только для добавления обнаруживает некоторые виды подделки. Оно не доказывает, что регистратор увидел каждое важное действие, и не мешает агенту заранее подготовить вводящие в заблуждение записи. Команды часто покупают неизменяемое хранилище, а затем оставляют прямые сетевые учётные данные в окружении агента. Главный пробел остаётся на месте.
Рассмотрим два пути развёртывания. В первом агент читает API-токен из переменной окружения и напрямую вызывает сервис развёртывания. Отдельная библиотека пытается записать запрос. Агент может обойти библиотеку, запустить другой процесс или использовать другой HTTP-клиент. Журнал может оставаться идеально доступным только для добавления, а развёртывание так и не появится в нём.
Во втором случае агент просит исполнителя выполнить развёртывание, но сам не получает токен. Исполнитель владеет единственным маршрутом, который может добавить учётные данные. Он записывает запрос до отправки и обновляет событие после ответа. Журнал описывает только действия, прошедшие через исполнителя, зато у агента нет разрешённого пути в обход.
RFC 6962, спецификация Certificate Transparency, описывает дерево хешей Меркла, которое позволяет эффективно доказывать принадлежность листа дереву и то, что новое дерево расширяет старое. Это полезный шаблон для обеспечения целостности. Но он не утверждает, что дерево доказывает полный захват событий реального мира. Запрос на развёртывание, который никогда не попал в дерево, не появится в доказательстве включения.
То же ограничение относится к линейной цепочке хешей. Запись может содержать дайджест предыдущей записи. Если кто-то изменит, удалит или переставит сохранённые записи, проверка завершится ошибкой в соответствующей точке. Но чистая цепочка всё равно может охватывать лишь тщательно выбранную часть действий. Сначала поместите регистратор в путь действий, и только потом празднуйте появление цепочки.
Есть и второе ограничение: администраторы тоже могут угрожать следу. Если оператор способен изменить базу журнала и сбросить корень цепочки без внешнего свидетеля, конструкция в основном защищает от случайных изменений. Автономная проверка, защищённые контрольные точки и разделение доступа дают проверяющим более надёжную основу. Ни одна из этих мер не превращает журнал в полный отчёт обо всём, что произошло на машине.
Поместите регистратор в путь выполнения
Регистратор должен наблюдать действие там, где добавляются учётные данные и внешний запрос покидает контролируемую систему. Логирование на уровне фреймворка агента происходит слишком рано. Логирование только в удалённом сервисе часто происходит слишком поздно и может не содержать контекст авторизации. Шлюз выполнения видит и принятый запрос, и полученный результат.
Для HTTP такой шлюз должен принять ограниченный запрос, выбрать сохранённые учётные данные, сам добавить их, отправить запрос и вернуть разрешённую часть ответа. Агент не должен передавать необработанные заголовки Authorization. Вместо этого он должен ссылаться на учётные данные по внутреннему имени, которое разрешает шлюз.
Формат запроса можно сделать достаточно небольшим, чтобы его было легко проверить и записать:
{
"action": "http.request",
"credential_ref": "deploy-api",
"method": "POST",
"url": "https://deploy.example.internal/releases",
"headers": {"content-type": "application/json"},
"body": {"revision": "a18f3c"}
}
Исполнитель должен отклонять заголовки с учётными данными, переданные вызывающей стороной для этого маршрута. Так агент не сможет внедрить другой токен, оставив в журнале вводящую в заблуждение ссылку credential_ref. URL также нужно нормализовать до записи, поскольку необработанный URL может содержать данные пользователя, хитрости с кодированным путём или случайно попавший в параметр запроса секрет.
Для SSH действует то же правило. Агент просит выполнить команду для ссылки на узел. Исполнитель выбирает настроенную идентификацию, способ проверки узла и метод подключения. Если агент может прочитать закрытый ключ и сам запустить SSH-клиент, система аудита уже не может утверждать, что охватывает его удалённые действия.
Не путайте шлюз с универсальным прокси. Прокси пытается наблюдать широкий поток трафика. Шлюз действий предлагает определённый набор действий и владеет полномочиями для их выполнения. Благодаря более узкой форме проще точно описать смысл записи: этот исполнитель принял это действие с такой авторизацией и получил такой результат.
Записывайте событие до того, как внешний мир успеет ответить
Надёжной записи нужно начальное событие до отправки действия исполнителем и событие завершения после получения результата. Если писать запись только после успеха, тайм-аут, сбой, завершение процесса или обрыв сети могут стереть серьёзный запрос из истории.
Используйте один идентификатор действия, сгенерированный исполнителем, а не агентом. Начальная запись должна содержать идентификатор сеанса, идентификатор процесса-участника, тип действия, нормализованную цель, безопасный дайджест запроса, решение об авторизации и временную метку, созданную регистратором. Укажите состояние started.
После получения ответа или ошибки исполнитель создаёт запись завершения со ссылкой на тот же идентификатор действия. В ней должна быть категория результата, например succeeded, rejected, timed_out, transport_error или executor_error, а также дайджест ответа и ограниченные метаданные. Тогда проверяющий сможет отличить «шлюз отклонил запрос» от «удалённая система могла его получить, но ответ не вернулся».
Последнее различие имеет практические последствия. Представьте, что агент отправляет POST /releases и теряет соединение после того, как удалённый сервис принял запрос. Слепой повтор может создать два релиза. Неверно считать первую попытку неудачной только потому, что клиент не увидел ответа. В журнале нужно показать, что исполнитель попытался выполнить вызов, передача началась, транспорт отказал, а конечное состояние удалённой системы остаётся неизвестным, пока его не подтвердит другой источник.
Минимальная пара записей может выглядеть так:
{"seq":481,"action_id":"01HV...","state":"started","session":"run-72","type":"http.request","target":"deploy.example.internal/releases","request_sha256":"...","authorized":true,"previous_sha256":"..."}
{"seq":482,"action_id":"01HV...","state":"completed","result":"transport_error","sent":true,"response_sha256":null,"previous_sha256":"..."}
Поля выше показывают структуру, а не универсальную схему. Значения последовательности и дайджесты должен контролировать регистратор. Агент может передать метку задачи, но её нужно пометить как недоверенный ввод и ограничить её длину. Иначе агент сможет поместить в официально выглядящее поле секреты, инструкции для проверяющих или вводящий в заблуждение текст.
Если между двумя записями произошёл сбой, остаётся незавершённое действие. Сохраните его. Незавершённая запись требует расследования, а не очистки во время восстановления. После перезапуска систему можно пометить её как нерешённую, если для этого есть надёжный способ, но нельзя переписывать историю в аккуратный успех или неудачу.
Схема должна сохранять намерение и результат
Журнал, в котором есть только «команда выполнена», почти не помогает во время инцидента. Журнал, сохраняющий каждый байт каждого запроса и ответа, превращается в хранилище конфиденциальных данных. Полезный компромисс, это структурированный контекст, достаточный для идентификации действия и оценки авторизации, при одновременном сокращении секретов и неконтролируемых полезных нагрузок.
Записывайте идентификатор вызывающего процесса в форме, которую может установить операционная система. В macOS полномочия подписи кода дают более надёжную привязку сеанса, чем имя агента, объявленное им самим. Идентификаторы процессов переиспользуются. Отображаемое имя может лгать. Сохраняйте достаточно контекста сеанса, чтобы проверяющий мог понять, запустился ли новый процесс и распространялось ли одобрение на этот процесс.
Для HTTP-действия сохраняйте метод, нормализованные полномочия и путь, выбранную ссылку на учётные данные, разрешённые заголовки запроса, дайджест тела запроса, результат решения и статус ответа, если он доступен. Для SSH-действия сохраняйте ссылку на узел, ссылку на удалённую учётную запись, если она применима, команду или её дайджест с учётом чувствительности, ссылку на выбранную идентификацию, результат проверки узла, код завершения и дайджест вывода.
Не храните необработанные учётные данные. Не позволяйте токену bearer попасть в URL, пользовательский заголовок, аргументы команды или перехваченный вывод. Маскирование после сохранения слабее запрета на сбор, поскольку секрет уже мог попасть в резервные копии, реплики или выгрузки для проверяющих.
С дайджестами нужна осторожность. Дайджест доказывает равенство только с материалом, который уже есть у проверяющего. Он не объясняет человеку, что произошло. Для полезной нагрузки развёртывания хорошо может работать сохранённый дайджест вместе с ревизией репозитория. Для разрушающей команды базы данных может потребоваться нормализованное представление команды, потому что проверяющим нужна точная инструкция как свидетельство.
Храните отдельное поле для решения политики или одобрения, разрешившего действие. Позже проверяющий должен иметь возможность ответить: исполнитель разрешил действие потому, что у сеанса было одобрение, оператор одобрил конкретное использование или хранилище было открыто? Не сводите эти варианты к расплывчатому allowed: true, если от различия зависит человеческая модель контроля.
Для проверки нужны отдельный доступ и независимая верификация
Проверяющим нужна не просто таблица с поиском. Они должны установить, согласуется ли последовательность записей сама с собой и сохранила ли система свои границы. Человек, расследующий запуск, не должен сначала просить агента пересказать собственное поведение.
Цепочка хешей даёт конкретную проверку. Каждая запись содержит дайджест предыдущей сохранённой записи, а регистратор вычисляет дайджест текущей канонической записи. Проверяющий читает последовательность по порядку, пересчитывает дайджесты и проверяет соответствие каждой ссылки на предшественника. Изменение старой цели, удаление неудобной ошибки или перестановка двух записей нарушает последующую цепочку.
Канонизация важна. Если один компонент хеширует текст JSON, а другой сначала разбирает и сериализует его заново, безобидные пробелы или порядок полей вызовут ошибку проверки. Определите точный порядок полей, кодировку, обработку null, точность временных меток и алгоритм дайджеста. Затем версионируйте формат записей. Проверяющий должен понимать, может ли он проверить конкретную версию, а не угадывать.
Проверка должна работать отдельно от процесса, создавшего записи. Если для проверки целостности нужен тот же работающий сервис, скомпрометированный сервис может скрыть повреждение цепочки. Скопируйте зашифрованный поток записей на компьютер проверяющего или в защищённый архив, а затем запустите проверку для этой копии.
Sallyport создаёт журналы Sessions и Activity из одного зашифрованного журнала аудита, связанного хешами, а sp audit verify может автономно проверить эту цепочку поверх шифротекста без ключа хранилища. Это правильное направление: проверка свидетельств не должна требовать раскрытия секретов, которые обеспечили выполнение действия.
Проверка целостности отвечает на ограниченный вопрос: образуют ли эти записи последовательность, которую ожидает проверяющий? Она не устанавливает, были ли правдивыми все ответы сервера, не существовал ли у целевой системы другой путь доступа и не заменил ли администратор весь журнал старой корректной копией. Если откат важен в вашей среде, храните защищённые контрольные точки или отправляйте подписанные корни во внешнюю систему хранения.
Ошибки, которые незаметно портят след
Самые опасные конструкции обычно выглядят разумно на демонстрации. Они ломаются при повторной попытке, сбое или неожиданном использовании агентом тех же полномочий.
Первая ошибка, логирование из SDK агента. Такой подход популярен, потому что требует нескольких строк кода и даёт разработчикам привычное представление трассировки. Он не создаёт запись выполнения, если агент получает учётные данные, запускает другую программу или вызывает адрес, который SDK не оборачивает. Считайте трассировки SDK отладочными данными.
Вторая ошибка, одна запись об успехе. Запись, создаваемая только после чистого ответа, стирает неоднозначность. Сетевые операции имеют неоднозначный исход, а удалённые команды могут изменить состояние до закрытия соединения. Записывайте начало и завершение отдельно и сохраняйте незавершённые начальные записи.
Третья ошибка, секреты в полезной нагрузке аудита. Иногда команды объясняют это тем, что проверяющим нужна полная воспроизводимость. Для понимания действия им редко нужен токен bearer, а скопированные секреты превращают журнал в ценную цель. Храните ссылки на учётные данные и дайджесты запросов.
Четвёртая ошибка, изменяемый поисковый индекс в роли источника истины. Индексы теряют поля, удаляют документы по сроку хранения и позволяют вносить исправления. Держите долговечный поток событий главным источником. Стройте из него поисковое представление, а при подозрении возвращайтесь к исходной записи.
Пятая ошибка, широкий путь обхода. Команда вроде «запусти произвольную оболочку с окружением production» сводит на нет аккуратную работу с учётными данными и моделью событий. Иногда оператору нужен исключительный доступ. Оформите его как отдельно одобренное действие с явной записью, а не прячьте внутри обычного маршрута агента.
Одобрение человеком и защита от чтения решают разные задачи
Одобрение может остановить небезопасное действие до его выполнения. Защита от чтения сохраняет свидетельства после появления запроса на действие. Когда агент работает с важными полномочиями, нужны обе меры.
Одобрение сеанса подходит, когда человек доверяет конкретному процессу агента и ограниченному периоду работы. Одобрение каждого действия подходит для учётных данных или операции, которые требуют внимания человека при каждом использовании. Ни один вариант одобрения не заменяет запись выполнения. Даже одобренный сеанс может случайно вызвать систему, а агент может неверно описать сделанное.
На экране одобрения нужно показывать вызывающий процесс в форме, доступной для человеческой оценки. Самостоятельно заявленная метка вроде «помощник релиза» слаба. Установленные полномочия подписи кода дают оператору более надёжный сигнал о том, какая программа запросила доступ. Решение об одобрении должно стать частью контекста события, но агент не должен управлять описанием своей идентичности.
Отделяйте блокировку хранилища от одобрения. Заблокированное хранилище должно запрещать действия, даже если ранее было одобрено одобрение сеанса. Это создаёт простой аварийный стоп-кран: заблокируйте полномочия, и исполнитель перестанет использовать сохранённые учётные данные. Отказы тоже нужно записывать. Последовательность отклонённых запросов может показать, что агент зациклился, проверяет границы или продолжает работу после отзыва доступа оператором.
Не превращайте это в огромный язык правил без реальной необходимости. Правила, которые никто не может проверить во время инцидента, создают ложное чувство безопасности. Небольшой набор понятных средств контроля, ограниченный интерфейс действий и записи, которыми агент не может управлять, часто лучше множества условных исключений.
Стройте границу вокруг действия, которое может причинить вред
Начните с внешнего действия, неправильное использование которого приведёт к самому неприятному расследованию. Это может быть команда SSH в рабочей среде, вызов API релиза, изменение учётной записи или платный запрос. Уберите учётные данные из окружения агента, пропустите это действие через исполнителя и проверьте сценарии отказа до подключения новых инструментов.
Проведите такой короткий тест:
- Запросите действие, а затем завершите процесс агента, пока запрос выполняется. Убедитесь, что начальное событие осталось в журнале.
- Заставьте удалённую цель вернуть ошибку и убедитесь, что запись завершения различает отказ, ошибку на удалённой стороне и транспортную ошибку.
- Попробуйте выполнить то же действие с переданным вызывающей стороной заголовком учётных данных или закрытым ключом. Убедитесь, что исполнитель отклоняет запрос.
- Дайте агенту обычные интерфейсы и попробуйте перечислить, изменить или удалить записи аудита. Убедитесь, что у него нет такого пути.
- Скопируйте сохранённый журнал в другое место и проверьте его цепочку, не полагаясь на текущее состояние исполнителя.
Эти тесты находят пробелы, которые скрывает демонстрация успешного сценария. Они также заставляют точно ответить на неудобный вопрос: какие действия агент всё ещё может выполнять вне записываемого шлюза? Если среди них есть полномочия для рабочей среды, запись по замыслу неполна. Прямо скажите об этом, закройте путь или сузьте заявления о свойствах аудиторского следа.
Полезная запись аудита начинается до выхода запроса, заканчивается наилучшим наблюдаемым результатом и остаётся вне контроля агента. Постройте эту границу до того, как первый инцидент заставит вас довериться версии событий от агента.
Вопросы и ответы
Что такое журнал аудита с защитой от чтения?
Журнал аудита с защитой от чтения позволяет системе действий записывать события, но не даёт исполнителю читать, редактировать, удалять или избирательно формировать сохранённую запись. Агент получает нужный результат действия, а отдельный проверяющий позже изучает цепочку свидетельств.
Достаточно ли журнала только для добавления при аудите AI-агентов?
Нет. Режим добавления помогает обнаружить удаление или изменение записей задним числом, но агент всё ещё может пропускать события, выбирать вводящие в заблуждение поля или действовать через путь, который не видит регистратор. Защита от чтения ограничивает то, что исполняющий процесс может узнать и изменить во время работы.
Что должен содержать журнал аудита AI-агента?
Записывайте идентификатор запроса, решение об авторизации, тип действия, идентификатор цели, ссылку на учётные данные, временную метку, категорию результата, дайджест ответа и ошибки выполнения. Не добавляйте в журнал необработанные секреты или полные конфиденциальные тела ответов без особой необходимости.
Как не дать агенту обойти журнал аудита?
Лучше всего, когда шлюз действий владеет учётными данными и сам выполняет исходящий запрос или удалённую команду. Если агент получает токен и вызывает систему напрямую, он может действовать вне записи шлюза.
Что доказывает цепочка хешей в журнале аудита?
Цепочка хешей позволяет обнаружить удаление, перестановку и изменение записей, когда проверяющий анализирует последовательность. Она не доказывает, что система записала каждое действие, поэтому для каждого разрешённого пути действий всё равно нужен доверенный регистратор.
Мешает ли защита от чтения агенту видеть результаты команд?
Нет. Агенту нужен результат запрошенного действия, например код состояния, вывод команды или ошибка. Ему не нужен журнал, по которому проверяющие позже будут оценивать его поведение.
Может ли журнал аудита создать проблему с конфиденциальностью?
Записывайте достаточно данных, чтобы определить цель и восстановить решение, но не собирайте больше персональной или деловой информации, чем необходимо. Предпочитайте идентификаторы, хеши и ограниченные сводки целым файлам, строкам базы данных или телам ответов.
Как проверяющему исследовать запуск AI-агента?
Сначала проверяющий должен подтвердить целостность записей, затем сопоставить запись сеанса с отдельными действиями и проверить, не вышел ли какой-либо путь выполнения за пределы шлюза. Чистая цепочка доказывает только согласованность того, что получил регистратор.
Что делает журналы аудита агентов ненадёжными?
Журналы легче изменить, когда агент выбирает их содержимое, тот же процесс выполняет действия и хранит свидетельства или операторы могут незаметно редактировать записи. Отдельный путь приёма записей только на добавление и автономная проверка целостности закрывают разные части этого риска.
С чего начать ведение журналов с защитой от чтения?
Начните с действия, которое несёт самые серьёзные внешние последствия, например с доступа по SSH к рабочей среде или API развёртывания. Поместите его учётные данные за исполнителя, который записывает намерение и завершение до возврата результата, а затем проверьте, что агент не может обратиться к цели напрямую.