Przejdź do treści

Tłumaczenie maszynowe. Wersja angielska jest wiążąca; weryfikacja przez native speakera jeszcze nie nastąpiła.

audit

Dowody audytu, które weryfikator może sprawdzić offline

Autor Olivares AI 11 min do przeczytania

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:

  1. Globalna księga audytowa powiązana z łańcuchem hashów przez sc.Audit().Append — łańcuch z widocznym manipulacją, zakotwiczony przez PayloadHash.
  2. 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 satisfied lub not-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.

Aktywność w celu udokumentowania potoku

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:

  1. 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.
  2. 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ązania prev_hash i brak luk w sekwencji.
  3. 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.
  4. 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.
  5. 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.
  6. 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_hash bieżącego segmentu.
  7. Zweryfikuj skrót pliku zdarzeń. SHA-256 pliku zdarzeń obliczony podczas przesyłania strumieniowego musi zgadzać się z events_sha256 manifestu.

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.

WarstwaCo to udowadniaCzego to nie udowadnia
Łańcuch skrótówSpójność wewnętrzna; każda edycja przerywa łańcuchPochodzenie (kto napisał wydarzenia)
Na wydarzenie Ed25519Pochodzenie; każde wydarzenie było podpisane przez posiadacza kluczaOchrona przed kompromitacją na poziomie hosta
Checkpoint poza urządzeniemOdporność na kompromitację hosta (klucz KMS/HSM nigdy nie znajduje się na hoście)Szczegółowość na poziomie wydarzeń (dotyczy tylko checkpointów)
Eksport OSCALMaszynowo 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 archiwumOffline ponowne wyprowadzenie wszystkich powyższychWydarzenia 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

Powiązane artykuły

Najczęstsze pytania

Czy atakujący, który przejmuje bazę danych, może ponownie podpisać sfałszowane zdarzenia?

Podpisy per-wydarzenie Ed25519 chronią przed kompromitacją wyłącznie bazy danych: kradzieżą kopii zapasowych, wstrzykniętymi wierszami lub repliką z rolą omijającą RLS. Klucz podpisujący znajduje się w katalogu danych, a nie w bazie danych. W przypadku kompromitacji hosta, platforma obsługuje podpisywanie punktów kontrolnych poza hostem za pośrednictwem KMS/HSM (AWS KMS, GCP Cloud KMS, Azure Key Vault), gdzie klucz prywatny nigdy nie znajduje się na hoście. Podpisy per-wydarzenie zatrzymują ataki na poziomie bazy danych; punkty kontrolne poza hostem zatrzymują ataki na poziomie hosta. Żaden z nich samodzielnie nie chroni przed obydwoma modelami zagrożeń.

Co się stanie, jeśli eksport OSCAL oznaczy kontrolę jako satisfied, ale później zmienią się dowody operacyjne?

Eksport OSCAL mapuje status produktu na enum statusu ustaleń OSCAL w momencie zamknięcia. Tylko kontrolki poparte aktualnymi dowodami operacyjnymi w danym momencie otrzymują OSCAL satisfied. Kontrola ze statusem by_design, partial, gap lub unmapped jest mapowana na OSCAL not-satisfied, przy czym dokładny status produktu jest zachowany w polu powodu oraz w niestandardowej właściwości. Każdy zamknięty pakiet dowodowy jest niezmienny i opatrzony znacznikiem czasu. Jeśli dowód ulega zmianie, nowy pakiet zostaje zamknięty, odzwierciedlając aktualny stan. Stary pakiet pozostaje nienaruszony i możliwy do ponownej weryfikacji, tworząc serię czasową postawy zgodności, a nie jedną nadpisywalną deklarację.

Sprawdź dostęp Twoich agentów

Olivares AI to otwarta platforma w modelu hostowanym samodzielnie dla całego środowiska AI w organizacji. Wdrożenie na własnej infrastrukturze zapewnia mapę dostępu, o którą od dawna proszą zespoły bezpieczeństwa i platformowe.