Ваш аудитор просить докази того, що зробили ваші AI-агенти минулого кварталу. Ви передаєте їм експорт журналу аудиту у форматі 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-байтний великий ендін) вирішує цю проблему.
Реалізація в 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. Якщо будь-яка подія була змінена після підписання, перевірка підпису не вдасться для цієї конкретної події — перевіряючий не потребує довіряти системі, яка створила докази.
Платформа також підтримує ротацію ключів. Ланцюжок, у якого змінювався ключ підпису у процесі використання, перевіряється від кінця до кінця шляхом фіксації поточного ключа та публічних ключів попередніх поколінь. Функція перевірки приймає набір кандидатів ключів і вважає подію дійсною, якщо будь-який кандидат її підтверджує.
Крім підписів для кожної події, періодичні контрольні точки нотаріально фіксують вершину ланцюжка під окремим доменом підпису (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 та інші). Кожна реалізована вимога містить ID контролю, ключі можливостей, що підтверджують її, та реальний статус як власну властивість.
- Результати оцінки: результати по контролю з відповідним статусом, що відповідає 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
- Модель безпеки — модель довіри, що лежить в основі реєстру