Ваш аудитор требует доказательства того, что ваши агенты ИИ делали в прошлом квартале. Вы передаете им CSV-экспорт журнала аудита. Они задают один вопрос: «Можете ли вы доказать, что это не было отредактировано задним числом?» Большинство команд не могут. Журнал хранится в изменяемой базе данных, экспортируется той же системой, которая его создала. Аудитор должен принимать всю цепочку на веру, что как раз и делает её не доказательством.
Этот пост объясняет, как платформа формирует аудиторские доказательства, которые имеют силу без веры. Предыдущий пост освещал, почему важно иметь идентификацию каждого агента и журнал с обнаружением изменений для развертываний Claude Code и MCP. Этот пост погружается глубже в три свойства, которые делают доказательства независимо проверяемыми: хэш-цепочку, криптографическую подпись каждого события и экспорт в машинно-читаемый формат соответствия, который уже понимают инструменты аудитора.
Хэш-цепочка: каждое событие фиксирует все предыдущие события
Главная реестр является только для добавления и имеет хэш-цепочку для каждого арендатора. Каждое событие содержит хэш SHA-256 предыдущего события в своём поле prev_hash. Хэш цепочки события N вычисляется на основе канонического бинарного преобразования, которое включает: собственные поля события N (арендатор, номер последовательности, временная метка, актер, действие, объект, дайджест метаданных, хэш полезной нагрузки), соединённые с prev_hash из события N-1. При последовательности 1 prev_hash состоит из всех нулей — генезис-якорь.
Критическое свойство: изменение, вставка или удаление любого события в середине цепочки изменяет его хэш, что нарушает связь prev_hash следующего события, что нарушает следующее, и так далее до конца. Любое редактирование можно обнаружить, пройдя по цепочке и пересчитав каждый хэш.
Предобраз является фиксированным двоичным кодированием с префиксом длины и разделителем домена с версией — не JSON. Это осознанный выбор для защиты от трех векторов атак, которые оставил бы хеш на основе JSON:
- Порядок ключей: порядок ключей в JSON-объекте не гарантируется большинством сериализаторов. Разный порядок ключей приводит к разному хешу даже для семантически одинаковых данных, что создаёт ложные разрывы цепочки — или, хуже того, позволяет злоумышленнику изменить порядок ключей, чтобы подделать соответствующий хеш.
- Пробелы и форматирование чисел:
{"seq": 1}и{"seq":1}и{"seq": 1.0}семантически эквивалентны в JSON, но дают разные SHA-256 дайджесты. - Фальсификация конкатенации: без префиксов длины два соседних коротких поля могут быть объединены для создания третьего длинного поля с тем же хешем. Использование префикса длины для каждого поля с указанием количества байт (4-байтовый big-endian) решает эту проблему.
Реализация в core/internal/store/canon/canon.go является единственным источником истины. И Append (запись), и Verify (чтение обратно) вызывают одну и ту же функцию EventHash. Второй реализации нет, чтобы происходило расхождение:
func EventHash(e Event) []byte {
var buf []byte
buf = lps(buf, domainEvent) // "olivares.audit.v1"
buf = lps(buf, e.TenantID)
var seq [8]byte
binary.BigEndian.PutUint64(seq[:], uint64(e.Seq))
buf = append(buf, seq[:]...)
buf = lps(buf, e.OccurredAt)
buf = lps(buf, e.Actor)
buf = lps(buf, e.ActorKind)
buf = lps(buf, e.Action)
buf = lps(buf, e.TargetKind)
buf = lps(buf, e.TargetID)
buf = append(buf, fixed(e.MetaDigest)...)
buf = append(buf, fixed(e.PayloadHash)...)
buf = append(buf, fixed(e.PrevHash)...)
sum := sha256.Sum256(buf)
return sum[:]
}
Разделитель домена "olivares.audit.v1" связывает хеш с его назначением и версией. Хеш из другого домена (контрольная точка, полезная нагрузка, дайджест метаданных) никогда не сможет совпасть с хешем события, даже если исходные байты случайно совпадают.
Подпись Ed25519: каждое событие является своей собственной якорной точкой
Хэш-цепочка доказывает внутреннюю согласованность, но не подлинность. Злоумышленник с прямым доступом к записи в базе данных может пересчитать всю цепочку с нуля с изменёнными событиями и создать действительную цепочку — отличную от оригинала, но внутренне согласованную. Хэш-цепочки обнаруживают подделку; они не доказывают происхождение.
Подписи Ed25519 по каждому событию закрывают эту gap. Каждое событие, добавляемое в реестр, подписывается во время записи. Подпись охватывает разделённый по доменам предварительный образ арендатора, порядковый номер и хэш цепочки события:
domain ("olivares.audit.event.v1") || tenant || seq (8 bytes, big-endian) || hash
Подпись хранится вместе с событием, но по замыслу исключена из предварительного изображения цепного хэша. Это не случайно: если бы подпись была включена в хэш, подписание события изменяло бы хэш, который она должна подтверждать. Подпись подтверждает хэш, не изменяя его.
Внешний проверяющий, имеющий только открытый ключ, может проверять каждое событие отдельно: пересчитать цепной хэш из полей события, восстановить предварительное изображение и проверить подпись Ed25519. Если какое-либо событие было изменено после подписания, проверка подписи не пройдет для этого конкретного события — проверяющему не нужно доверять системе, которая создала доказательство.
Платформа также поддерживает ротацию ключей. Цепочка, у которой ключ подписи изменился в середине жизненного цикла, проверяется полностью, закрепляя текущий ключ и публичные ключи предыдущих поколений. Функция verify принимает набор кандидатов на ключи и считает событие действительным, если любой из кандидатов подтверждает его.
Помимо подписей для каждого события, периодические контрольные точки нотариально подтверждают конец цепочки в рамках отдельного домена подписи (olivares.audit.checkpoint.v1). Для организаций, которым необходимо защищаться от компрометации на уровне хоста — а не только базы данных — контрольные точки могут подписываться внешним KMS/HSM ключом (AWS KMS, GCP Cloud KMS, Azure Key Vault), где приватный ключ никогда не находится на хосте. Подписи для каждого события справляются с атакующим, нацеленным только на базу данных; внешние контрольные точки защищают от атакующего, компрометирующего хост. Эти две модели угроз различаются; ни одна подпись по отдельности не покрывает обе.
Контракт с реестром: закреплено в одной и той же транзакции
Распространенной проблемой в системах аудита является отложенная согласованность между изменением состояния и записью аудита. Состояние изменяется, запись аудита ставится в очередь или обрабатывается пакетно, и если запись аудита не удается, изменение состояния уже зафиксировано. Результат: неизученные изменения, которые существуют в системе, но отсутствуют в доказательствах.
Платформа обеспечивает более строгий контракт. И изменение состояния, и заверение в реестре происходят в одной и той же транзакции базы данных. Если заверение не удается, вся транзакция откатывается — изменение состояния никогда не фиксируется. Это не на основе максимально возможных усилий; это закрытое по отказу.
Журнал выполнения сессии иллюстрирует это. Когда сессия Claude Code меняет состояние (создана, запущена, останавливается, остановлена, с ошибкой), appendRunEvent фиксирует переход в двух местах атомарно:
- Глобальный аудиторский журнал с цепочкой хешей через
sc.Audit().Append— защищенная от подделок цепочка, заякореннаяPayloadHash. - Журнал, доступный для запросов по каждой сессии — проекция только для добавления, связанная с глобальной цепочкой через
audit_seq.
Оба записи происходят внутри транзакции Mutate вызывающего. Комментарий в коде runtime_ledger.go прямо указывает на замысел дизайна: «Журнал является системой учета, поэтому если печать не проходит, весь переход откатывается — это НЕ попытка сделать по возможности».
Сам PayloadHash фиксирует только канонические, не содержащие конфиденциальной информации факты переходов — ссылку на выполнение, последовательность, тип события, переход состояния и отметку времени. Он никогда не включает содержимое транскрипта, подсказки, значения окружения или секреты. Реестр доказывает что произошло; он не хранит что было сказано.
Аналогичная схема применяется к мутациям файлов рабочего пространства в workspace_ledger.go. Запись файла, создание каталога, перемещение или удаление фиксируются до выполнения операции с файловой системой. Если доказательство не может быть добавлено, мутация не выполняется. Печать содержит тип операции, путь и SHA-256 записанного содержимого — никогда сами байты содержимого.
Экспорт OSCAL: машиночитаемые доказательства, которые обрабатываются инструментами вашего аудитора
Журнал с защитой от подделки необходим, но недостаточен для аудитора. Если доказательства находятся в проприетарном формате, аудитор все равно зависит от ваших инструментов для их интерпретации. OSCAL — Open Security Controls Assessment Language, поддерживаемый NIST — это формат, который закрывает этот gap.
Платформа экспортирует запечатанные пакеты доказательств в виде связки OSCAL, содержащей три модели:
- Определение компонента: возможности плоскости управления, выраженные как реализованные требования к соответствию стандартам (NIST SP 800-53, ISO 27001, EU AI Act и другие). Каждое реализованное требование содержит идентификатор контрола, ключи возможностей, которые это подтверждают, и фактический статус в виде пользовательского свойства.
- Результаты оценки: результаты контроля с состоянием, соответствующим OSCAL. Цель каждой проверки имеет
satisfiedилиnot-satisfiedс точным статусом продукта, сохранённым в поле причины. - Сопоставление контрольных мер: перекрёстная таблица от контрольных мер фреймворка к модели ссылочных возможностей платформы с использованием модели сопоставления контроля OSCAL версии 1.2.0. Связь всегда
intersects-with— возможности покрывают часть контроля. Это никогда не утверждает соответствие; такое утверждение существует только в результатах оценки и основано на актуальных эксплуатационных данных.
Ограничение честности в экспорте OSCAL стоит указать явно. Перечисление статусов находки OSCAL имеет ровно два значения: satisfied и not-satisfied. Нет значений «partial» или «по замыслу». Контроль, который частично реализован, учтён по замыслу, имеет пробелы или unmapped, отображается как OSCAL not-satisfied, при этом реальный статус продукта находится в status.reason, а пользовательское свойство — в пространстве имён самой платформы (https://olivares.ai/ns/oscal). Экспорт никогда не превращает частично выполненный контроль в satisfied. Только контролям, подкреплённым актуальными оперативными доказательствами на момент закрепления, присваивается OSCAL satisfied.
Каждый документ OSCAL имеет свойства привязки к реестру: хэш манифеста, номер последовательности реестра на момент запечатывания, хэш реестра и результат проверки целостности. Это является связующим звеном между документом OSCAL, который аудитор просматривает в своем инструменте GRC, и базовой защищенной цепочкой, которую он может проверить независимо.
Что конкретно означает оффлайн-проверка
“Оффлайн-проверка” — это не маркетинговый термин. Она описывает конкретную техническую процедуру: проверяющий берет экспортированные данные, запускает инструмент проверки на отключенном от сети устройстве и подтверждает целостность данных без какого-либо сетевого доступа к системе, которая их создала.
Архивный экспорт платформы записывает дерево каталогов из сегментов JSONL (по одной строке на событие, канонический JSON) плюс манифест для каждого сегмента. Манифест фиксирует диапазон последовательности сегмента, количество событий, первые и последние хэши цепочки, SHA-256 файла событий и последний хэш предыдущего сегмента для сквозной непрерывности между сегментами.
Оффлайн-верификатор (VerifyArchiveDir) затем выполняет следующее, полностью в константной памяти без сетевых запросов:
- Загрузите манифесты и сопоставьте их с файлами событий. Отдельный файл событий (без манифеста) или манифест, чей файл событий отсутствует, считается ошибкой. Единицей доказательства является пара.
- Поточно обрабатывайте каждую строку файла событий. Для каждого события повторно вычисляйте хэш цепочки из архивных полей, используя ту же функцию
EventHash, что и живая система. Сравните его с сохранённым хэшем. Проверьте привязкуprev_hashи отсутствие разрывов в последовательности. - Проверьте каноничность. Снова маршализуйте каждую разобранную строку и убедитесь, что она даёт идентичный байтовый вывод с данными на диске. Это предотвращает скрытую передачу данных через неизвестные поля или атаки с дублированием ключей, которые могут пройти проверку хэша, но содержать скрытые данные.
- Проверьте подписи Ed25519 для каждого события. Для каждого события, не являющегося контрольной точкой, восстановите предобраз подписи и проверьте его относительно закреплённых публичных ключей.
- Проверьте подписи контрольных точек. Для каждого события контрольной точки проверьте подпись в домене контрольной точки по закреплённым ключам контрольной точки. Если закреплены только ключи событий (нет ключа контрольной точки), событие контрольной точки помечается как «непроверяемое» — закрытое отказом, нельзя пропускать.
- Проверьте непрерывность между сегментами. Убедитесь, что первая последовательность каждого сегмента следует за последней последовательностью предыдущего сегмента плюс один, и что последний хеш предыдущего сегмента совпадает с
prev_segment_last_hashтекущего сегмента. - Проверьте дайджест файла событий. SHA-256 файла событий, вычисленный во время потоковой передачи, должен совпадать с
events_sha256из манифеста.
Проверяющее устройство сообщает о первой обнаруженной несоответствии с указанием конкретного номера последовательности и машины, читаемой причины: hash-mismatch, prev-mismatch, seq-gap, event-sig-invalid, event-sig-missing, checkpoint-sig-invalid, count-mismatch, events-sha256-mismatch или segment-link-mismatch.
Честное ограничение: офлайн-проверяющий подтверждает именно тот диапазон, который он проверил, и ничего за его пределами. Удалённый префикс или хвост невозможно обнаружить офлайн — каталог не указывает, где началась или закончилась цепочка. Отчёт проверки включает поле Ranges для каждого арендатора с флагом StartsMidChain, чтобы аудитору было точно известно, что именно подтверждено. Подписанные контрольные точки в цепочке работающей системы покрывают хвост; офлайн-экспорт охватывает архивный диапазон. Вместе они формируют полное подтверждение.
| Слой | Что это доказывает | Что это не доказывает |
|---|---|---|
| Хеш-цепочка | Внутренняя согласованность; любое изменение разрушает цепочку | Происхождение (кто записал события) |
| По каждому событию Ed25519 | Происхождение; каждое событие было подписано владельцем ключа | Защита от компрометации на уровне хоста |
| Контрольная точка вне устройства | Сопротивление компрометации хоста (ключ KMS/HSM никогда не находится на хосте) | Гранулярность по каждому событию (охватывает только контрольные точки) |
| Экспорт OSCAL | Машиночитаемые доказательства соблюдения стандартов, сопоставленные с фреймворком | Что каждый контроль полностью выполнен (считается только актуальное доказательство) |
| Проверка архива | Офлайн повторное выведение всего вышеупомянутого | События до или после экспортированного диапазона |
Путь кода: от изменения состояния до заверенного доказательства
Последовательность от изменения состояния сессии Claude Code до проверяемого доказательства охватывает три уровня. В runtime_ledger.go функция appendRunEvent создает PayloadHash через SHA-256 хеширование канонических полей перехода с префиксом длины:
func runEventPayloadHash(runRef string, seq int64, event, from, to, detail, atTS string) [32]byte {
h := sha256.New()
for _, part := range []string{
runRef, strconv.FormatInt(seq, 10), event, from, to, detail, atTS,
} {
_, _ = h.Write([]byte(strconv.Itoa(len(part))))
_, _ = h.Write([]byte{':'})
_, _ = h.Write([]byte(part))
}
var sum [32]byte
copy(sum[:], h.Sum(nil))
return sum
}
Этот хеш затем передается в sc.Audit().Append, который назначает следующий последовательный номер для каждого арендатора, связывает его с хешем предыдущего события, вычисляет цепной хеш этого события через EventHash, подписывает его с помощью Ed25519 и вставляет заверенное событие — все это в рамках транзакции вызывающего.
Результат: к моменту фиксации транзакции изменение состояния сеанса и его защищённая от подделки аудиторская запись либо оба сохраняются, либо оба откатываются. Нет промежутка, когда состояние изменилось, но доказательство не зафиксировано.
Ссылки
- Продукт: Аудит — журнал аудита и экспорт доказательств
- Продукт: Соответствие — оценка фреймворка и OSCAL
- Модель безопасности — модель доверия, лежащая в основе реестра