Twój audytor prosi o dowody na to, co twoje agenty AI zrobiły w ostatnim kwartale. Wręczasz mu eksport CSV z dziennika audytu. Zadają jedno pytanie: „Czy możesz udowodnić, że to nie zostało zmienione po fakcie?” Większość zespołów nie potrafi. Dziennik znajduje się w zmiennej bazie danych, wyeksportowany przez ten sam system, który go zapisał. Audytor musi zaufać całej ścieżce w całości, co jest dokładnie tą cechą, która sprawia, że nie jest to dowód.
Ten post wyjaśnia, jak platforma tworzy dowody audytu, które są wiarygodne bez potrzeby zaufania. Poprzedni post omawiał, dlaczego tożsamość każdego agenta i niezmienny rejestr mają znaczenie dla wdrożeń Claude Code i MCP. Ten post zagłębia się w trzy właściwości, które sprawiają, że dowody są niezależnie weryfikowalne: łańcuchowanie haszy, kryptograficzne podpisywanie każdego zdarzenia oraz eksport w formacie zgodności czytelnym dla maszyn, który jest już obsługiwany przez narzędzia audytora.
Łańcuch haszy: każde zdarzenie odnosi się do wszystkich wcześniejszych zdarzeń
Księga jest tylko do dopisywania i łańcuchowana haszowo dla każdego najemcy. Każde zdarzenie nosi hash SHA-256 poprzedniego zdarzenia w swoim polu prev_hash. Hash łańcucha zdarzenia N jest obliczany na podstawie kanonicznego binarnego preobrazu, który obejmuje: własne pola zdarzenia N (najemca, numer sekwencji, znacznik czasu, aktor, akcja, cel, skrót metadanych, hash ładunku) połączone z prev_hash ze zdarzenia N-1. W sekwencji 1, prev_hash to same zera — kotwica genezy.
Kluczowa właściwość: modyfikacja, wstawienie lub usunięcie jakiegokolwiek zdarzenia w środku łańcucha zmienia jego hash, co łamie łącze prev_hash następnego zdarzenia, co z kolei łamie następne, i tak dalej aż do końca. Pojedyncza edycja jest wykrywalna poprzez przejście przez łańcuch i ponowne obliczenie każdego hashu.
Preobraz jest stałym kodowaniem binarnym z prefiksem długości i oddzielaczem domeny z wersją — nie jest to JSON. Jest to celowy wybór w celu ochrony przed trzema powierzchniami ataku, które pozostawiałby hash oparty na JSON:
- Kolejność kluczy: Kolejność kluczy w obiekcie JSON nie jest gwarantowana przez większość serializerów. Inna kolejność kluczy daje inny hash nawet dla semantycznie identycznych danych, co tworzy fałszywe przerwy w łańcuchu — lub co gorsza, pozwala atakującemu przestawiać klucze, aby sfałszować pasujący hash.
- Białe znaki i formatowanie liczb:
{"seq": 1}i{"seq":1}i{"seq": 1.0}są semantycznie równoważnymi JSON-ami, ale produkują różne digesty SHA-256. - Fałszerstwo przez konkatenację: bez prefiksów długości, dwa przyległe krótkie pola mogą zostać połączone w celu sfałszowania trzeciego długiego pola o tym samym skrócie. Prefiksowanie każdego pola jego liczbą bajtów (4-bajtowy zapis big-endian) zamyka tę lukę.
Implementacja w core/internal/store/canon/canon.go jest jedynym źródłem prawdy. Zarówno Append (zapis), jak i Verify (odczyt) wywołują tę samą funkcję EventHash. Nie istnieje druga implementacja, która mogłaby się rozjechać:
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[:]
}
Separator domeny "olivares.audit.v1" wiąże skrót z jego przeznaczeniem i wersją. Skrót z innej domeny (punkt kontrolny, payload, skrót metadanych) nigdy nie może kolidować z hashem zdarzenia, nawet jeśli surowe bajty przypadkiem się zgadzały.
Podpisywanie w Ed25519: każde zdarzenie jest własnym zakotwiczeniem
Łańcuch skrótów potwierdza wewnętrzną spójność, ale nie autentyczność. Atakujący mający dostęp do surowego zapisu bazy danych mógłby przeliczyć cały łańcuch od nowa z zmienionymi zdarzeniami i wygenerować prawidłowy łańcuch — inny niż oryginał, ale wewnętrznie spójny. Łańcuchy skrótów wykrywają manipulacje; nie potwierdzają pochodzenia.
Podpisy Ed25519 dla każdego zdarzenia zamykają ten gap. Każde zdarzenie dodane do rejestru jest podpisywane w momencie zapisu. Podpis obejmuje preobraz oddzielony domenowo dla klienta, numer sekwencji oraz skrót łańcucha zdarzenia:
domain ("olivares.audit.event.v1") || tenant || seq (8 bytes, big-endian) || hash
Podpis jest przechowywany w zdarzeniu, ale z założenia jest wykluczony z preobrazu łańcucha-hash. Nie jest to przypadek: gdyby podpis był uwzględniony w hashu, podpisanie zdarzenia zmieniłoby hash, który ma potwierdzać. Podpis poświadcza hash, nie zmieniając go.
Zewnętrzny weryfikator posiadający tylko klucz publiczny może potwierdzić każde zdarzenie indywidualnie: ponownie obliczyć hash łańcucha na podstawie pól zdarzenia, odtworzyć preobraz i zweryfikować podpis Ed25519. Jeśli jakiekolwiek zdarzenie zostało zmienione po podpisaniu, weryfikacja podpisu dla tego konkretnego zdarzenia nie powiedzie się — weryfikator nie musi ufać systemowi, który wygenerował dowód.
Platforma obsługuje również rotację kluczy. Łańcuch, którego klucz podpisujący zmienił się w trakcie używania, jest weryfikowany end-to-end przez przypięcie obecnego klucza oraz kluczy publicznych poprzednich generacji. Funkcja weryfikacji przyjmuje zestaw kandydatów na klucze i uznaje zdarzenie za ważne, jeśli którykolwiek kandydat je zweryfikuje.
Oprócz podpisów przypisanych do poszczególnych zdarzeń, okresowe punkty kontrolne notarialnie potwierdzają wierzchołek łańcucha w oddzielnej domenie podpisu (olivares.audit.checkpoint.v1). Dla organizacji, które muszą bronić się przed kompromitacją na poziomie hosta – a nie tylko bazy danych – punkty kontrolne mogą być podpisywane przez zewnętrzny klucz KMS/HSM (AWS KMS, GCP Cloud KMS, Azure Key Vault), gdzie klucz prywatny nigdy nie znajduje się na hoście. Podpisy przypisane do poszczególnych zdarzeń chronią przed atakującym na poziomie bazy danych; zewnętrzne punkty kontrolne chronią przed atakującym kompromitującym hosta. Te dwa modele zagrożeń są odrębne; żaden podpis pojedynczo nie zabezpiecza obu.
Kontrakt księgi głównej: zapieczętowany w tej samej transakcji
Częstym problemem w systemach audytu jest ostateczna spójność między zmianą stanu a rekordem audytu. Stan się zmienia, zapis audytu jest umieszczany w kolejce lub grupowany, a jeśli zapis audytu zawiedzie, zmiana stanu została już zatwierdzona. Wynik: nieaudytowane zmiany, które istnieją w systemie, ale nie ma na nie dowodu.
Platforma wymusza silniejszy kontrakt. Zarówno zmiana stanu, jak i zapieczętowanie księgi głównej odbywają się w tej samej transakcji bazy danych. Jeśli pieczęć nie powiedzie się, cała transakcja jest wycofywana — zmiana stanu nigdy nie zostaje zatwierdzona. To nie jest działanie na zasadzie najlepszego wysiłku; to jest zamknięcie odmowne.
Rejestr czasów sesji to ilustruje. Gdy Claude Code sesja przechodzi stan (utworzona, uruchomiona, zatrzymana, zatrzymana, nieudana), appendRunEvent rejestruje przejście w dwóch miejscach atomowo:
- Globalna księga audytowa powiązana z łańcuchem hashów przez
sc.Audit().Append— łańcuch z widocznym manipulacją, zakotwiczony przezPayloadHash. - Rejestr zapytania na sesję — projekcja tylko do dołączania powiązana z globalnym łańcuchem przez
audit_seq.
Oba zapisy zachodzą w ramach transakcji Mutate dzwoniącego. Komentarz w runtime_ledger.go wyraźnie określa intencję projektową: “księga jest systemem rejestru, więc jeśli pieczęć zawiodła, całe przejście cofa się – to NIE jest najlepszy wysiłek.”
Sam PayloadHash rejestruje jedynie kanoniczne, niepoufne fakty przejściowe — odniesienie do uruchomienia, sekwencję, typ zdarzenia, przejście stanu i znacznik czasu. Nigdy nie obejmuje treści transkryptu, promptów, wartości środowiskowych ani sekretów. Księga dowodów udowadnia co się wydarzyło; nie przechowuje co zostało powiedziane.
Ten sam wzorzec dotyczy mutacji plików w przestrzeni roboczej w workspace_ledger.go. Zapis pliku, mkdir, przeniesienie lub usunięcie jest zapieczętowane przed wykonaniem operacji systemu plików. Jeśli dowodu nie można dołączyć, mutacja nie zostaje wykonana. Pieczęć zawiera typ operacji, ścieżkę i SHA-256 zapisanej zawartości — nigdy same bajty zawartości.
Eksport OSCAL: dowód w formacie czytelnym dla maszyn, który może przetworzyć narzędzie audytora
Licznik łatwy do wykrycia manipulacji jest niezbędny, ale niewystarczający dla audytora. Jeśli dowody są w formacie własnościowym, audytor nadal zależy od twoich narzędzi, aby je zinterpretować. OSCAL — Open Security Controls Assessment Language, utrzymywany przez NIST — to format, który zamyka ten gap.
Platforma eksportuje zapieczętowane pakiety dowodów jako pakiet OSCAL zawierający trzy modele:
- Definicja komponentu: możliwości płaszczyzny kontrolnej wyrażone jako zaimplementowane wymagania względem ram zgodności (NIST SP 800-53, ISO 27001, EU AI Act i inne). Każde zaimplementowane wymaganie zawiera identyfikator kontrolny, klucze zdolności, które je dokumentują, oraz rzeczywisty status jako właściwość niestandardową.
- Wyniki oceny: wyniki poszczególnych kontroli ze statusem zgodnym z OSCAL. Każdy cel znaleziska zawiera
satisfiedlubnot-satisfied, a dokładny status produktu jest zachowany w polu powodu. - Mapowanie kontroli: mapa odwzorowań od kontroli frameworka do modelu odniesienia zdolności platformy, z wykorzystaniem modelu mapowania kontroli OSCAL 1.2.0. Relacja jest zawsze
intersects-with– zdolności odnoszą się do części kontroli. Nigdy nie stwierdza zgodności; takie stwierdzenie istnieje tylko w wynikach oceny, uzależnione od bieżących dowodów operacyjnych.
Warto wyraźnie zaznaczyć ograniczenie uczciwości w eksporcie OSCAL. Enum statusu wyników OSCAL ma dokładnie dwie wartości: satisfied i not-satisfied. Nie ma „partial” ani „według projektu”. Kontrola, która jest częściowo zaimplementowana, uwzględniona według projektu, ma luki lub unmapped, mapuje się na OSCAL not-satisfied, przy czym rzeczywisty status produktu znajduje się w status.reason oraz w niestandardowej właściwości w własnej przestrzeni nazw platformy (https://olivares.ai/ns/oscal). Eksport nigdy nie przekształca częściowo spełnionej kontroli w satisfied. Tylko kontrole poparte aktualnymi dowodami operacyjnymi w momencie zatwierdzenia otrzymują OSCAL satisfied.
Każdy dokument OSCAL posiada właściwości kotwicy księgi głównej: hash manifestu, numer sekwencji księgi głównej w momencie zapieczętowania, hash księgi głównej oraz wynik weryfikacji integralności. Stanowią one most pomiędzy dokumentem OSCAL, który audytor odczytuje w swoim narzędziu GRC, a leżącym u jego podstaw łańcuchem odpornym na manipulacje, który może zweryfikować niezależnie.
Co konkretnie oznacza weryfikacja offline
„Weryfikacja offline” to nie hasło marketingowe. Opisuje konkretną procedurę techniczną: weryfikator bierze wyeksportowane dowody, uruchamia narzędzie weryfikacyjne na maszynie odłączonej od sieci i potwierdza integralność dowodów bez jakiegokolwiek dostępu sieciowego do systemu, który je wygenerował.
Eksport archiwalny platformy zapisuje drzewo katalogów segmentów JSONL (jedna linia na zdarzenie, kanoniczny JSON) oraz manifest dla każdego segmentu. Manifest rejestruje zakres sekwencji segmentu, liczbę zdarzeń, pierwsze i ostatnie hasze łańcucha, SHA-256 pliku zdarzeń oraz ostatni hasz poprzedniego segmentu dla ciągłości między segmentami.
Następnie offline weryfikator (VerifyArchiveDir) wykonuje następujące czynności, całkowicie w stałej pamięci bez wywołań sieciowych:
- Załaduj manifesty i sparuj je z plikami zdarzeń. Pojedynczy plik zdarzeń (bez manifestu) lub manifest, którego plik zdarzeń brakuje, jest awarią. Jednostką dowodu jest para.
- Przesyłaj każdy plik zdarzeń linia po linii. Dla każdego zdarzenia ponownie wyprowadź hash łańcucha z zarchiwizowanych pól, używając tej samej funkcji
EventHash, której używa system na żywo. Porównaj go z zapisanym hashem. Sprawdź powiązaniaprev_hashi brak luk w sekwencji. - Zweryfikuj kanoniczność. Ponownie zamarszmaluj każdą sparsowaną linię i potwierdź, że generuje identyczne bajty jak te zapisane na dysku. Zapobiega to przemytowi nieznanych pól lub atakom typu duplicate-key, które mogłyby przejść weryfikację hasha, ale przenosiłyby ukryte dane.
- Zweryfikuj podpisy Ed25519 dla każdego zdarzenia. Dla każdego zdarzenia niebędącego punktem kontrolnym odtwórz preobraz podpisu i zweryfikuj go w stosunku do przypisanych kluczy publicznych.
- Zweryfikuj podpisy punktów kontrolnych. Dla każdego zdarzenia punktu kontrolnego zweryfikuj podpis w domenie punktu kontrolnego w stosunku do przypiętego(-ych) klucza(-y) punktu kontrolnego. Jeśli przypięte są tylko klucze zdarzeń (brak klucza punktu kontrolnego), zdarzenie punktu kontrolnego oznacza się jako „niemożliwe do zweryfikowania” — deny-closed, nie do pominięcia.
- Sprawdź ciągłość między segmentami. Potwierdź, że pierwsza sekwencja każdego segmentu następuje po ostatniej sekwencji poprzedniego segmentu plus jeden oraz że ostatni hash poprzedniego segmentu zgadza się z
prev_segment_last_hashbieżącego segmentu. - Zweryfikuj skrót pliku zdarzeń. SHA-256 pliku zdarzeń obliczony podczas przesyłania strumieniowego musi zgadzać się z
events_sha256manifestu.
Weryfikator raportuje pierwszą niespójność, którą znajdzie, ze wskazaniem konkretnego numeru sekwencyjnego oraz powodu w formie czytelnej dla maszyny: hash-mismatch, prev-mismatch, seq-gap, event-sig-invalid, event-sig-missing, checkpoint-sig-invalid, count-mismatch, events-sha256-mismatch lub segment-link-mismatch.
Szczere ograniczenie: weryfikator offline potwierdza dokładnie zakres, który sprawdził, i nic poza nim. Usunięte prefiksy lub końcówki są niewykrywalne offline — katalog nie informuje, gdzie łańcuch się zaczynał ani kończył. Raport weryfikacyjny zawiera pole Ranges dla każdego najemcy z flagą StartsMidChain, dzięki czemu audytor wie dokładnie, co zostało potwierdzone. Podpisane punkty kontrolne w łańcuchu systemu na żywo obejmują końcówkę; eksport offline obejmuje archiwalny zakres. Razem tworzą pełne poświadczenie.
| Warstwa | Co to udowadnia | Czego to nie udowadnia |
|---|---|---|
| Łańcuch skrótów | Spójność wewnętrzna; każda edycja przerywa łańcuch | Pochodzenie (kto napisał wydarzenia) |
| Na wydarzenie Ed25519 | Pochodzenie; każde wydarzenie było podpisane przez posiadacza klucza | Ochrona przed kompromitacją na poziomie hosta |
| Checkpoint poza urządzeniem | Odporność na kompromitację hosta (klucz KMS/HSM nigdy nie znajduje się na hoście) | Szczegółowość na poziomie wydarzeń (dotyczy tylko checkpointów) |
| Eksport OSCAL | Maszynowo czytelne dowody zgodności z odwzorowaniem na framework | Że każda kontrola jest w pełni spełniona (liczą się tylko dowody na żywo) |
| Weryfikacja archiwum | Offline ponowne wyprowadzenie wszystkich powyższych | Wydarzenia przed lub po wyeksportowanym zakresie |
Ścieżka kodu: od mutacji stanu do zapieczętowanych dowodów
Sekwencja od zmiany stanu sesji Claude Code do weryfikowalnych dowodów obejmuje trzy warstwy. W runtime_ledger.go funkcja appendRunEvent konstruuje PayloadHash poprzez SHA-256 haszowanie kanonicznych, prefiksowanych długością pól przejścia:
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
}
Ten hash jest następnie przekazywany do sc.Audit().Append, które przypisuje kolejny numer sekwencji dla najemcy, łączy się z hashem poprzedniego zdarzenia, oblicza hash łańcucha tego zdarzenia za pomocą EventHash, podpisuje go Ed25519 i wstawia zapieczętowane zdarzenie — wszystko w ramach transakcji wywołującego.
Wynik: w momencie zatwierdzenia transakcji zmiana stanu sesji i jej odporny na manipulacje zapis audytu są albo oba zachowywane, albo oba cofane. Nie ma sytuacji, w której stan uległ zmianie, a dowody nie zostały zachowane.
Linki
- Produkt: Audyt — ścieżka audytu i eksport dowodów
- Produkt: Zgodność — oceny w ramach ram oraz OSCAL
- Model bezpieczeństwa — model zaufania leżący u podstaw rejestru