Uw auditor vraagt om bewijs van wat uw AI-agenten het afgelopen kwartaal hebben gedaan. U overhandigt hen een CSV-export van het auditlogboek. Ze stellen één vraag: “Kun je bewijzen dat dit achteraf niet is aangepast?” De meeste teams kunnen dat niet. Het logboek bevindt zich in een veranderlijke database, geëxporteerd door hetzelfde systeem dat het heeft geschreven. De auditor moet het hele spoor van het geloof volgen, en dat is precies de eigenschap die het geen bewijs maakt.
In dit bericht wordt uitgelegd hoe het platform auditbewijsmateriaal construeert dat zonder vertrouwen standhoudt. In een vorig bericht werd besproken waarom de identiteit per agent en een fraudebestendig grootboek van belang zijn voor Claude Code- en MCP-implementaties. Deze gaat dieper in op de drie eigenschappen die het bewijsmateriaal onafhankelijk verifieerbaar maken: hash-chaining, cryptografische ondertekening per gebeurtenis en exporteren in een machinaal leesbaar compliance-formaat dat de tools van een auditor al begrijpen.
De hash-keten: elk evenement verbindt zich met alle voorgaande evenementen
Het grootboek kan alleen worden toegevoegd en is gekoppeld aan een hash-keten per tenant. Elke gebeurtenis bevat de SHA-256-hash van de vorige gebeurtenis in het veld prev_hash. De keten-hash van gebeurtenis N wordt berekend over een canonieke binaire voorafbeelding die het volgende omvat: de eigen velden van gebeurtenis N (tenant, volgnummer, tijdstempel, acteur, actie, doel, metagegevensoverzicht, payload-hash) samengevoegd met de prev_hash van gebeurtenis N-1. In reeks 1 bestaat prev_hash uit allemaal nullen: het ontstaansanker.
De kritische eigenschap: het wijzigen, invoegen of verwijderen van een gebeurtenis in het midden van de keten verandert de hash, waardoor de prev_hash-link van de volgende gebeurtenis wordt verbroken, waardoor de volgende wordt verbroken, enzovoort tot aan de tip. Een enkele bewerking kan worden gedetecteerd door de keten te doorlopen en elke hash opnieuw te berekenen.
De preimage is een vaste binaire codering met lengte-voorvoegsel en een domeinscheidingsteken met versienummer, niet JSON. Dit is een bewuste keuze tegen drie aanvalsoppervlakken die een op JSON gebaseerde hash open zou laten:
- Sleutelvolgorde: De sleutelvolgorde van JSON-objecten wordt door de meeste serializers niet gegarandeerd. Een andere sleutelvolgorde produceert een andere hash, zelfs voor semantisch identieke gegevens, wat valse kettingbreuken veroorzaakt - of erger nog, een aanvaller in staat stelt de sleutels opnieuw te ordenen om een overeenkomende hash te vervalsen.
- Witruimte- en getalopmaak:
{"seq": 1}en{"seq":1}en{"seq": 1.0}zijn semantisch equivalente JSON maar produceren verschillende SHA-256-samenvattingen. - Aaneenschakelingsvervalsing: zonder lengtevoorvoegsels kunnen twee aangrenzende korte velden worden samengevoegd om een derde lang veld met dezelfde hash te smeden. Door elk veld een lengte-voorvoegsel te geven met het aantal bytes (4-byte big-endian), wordt dit afgesloten.
De implementatie in core/internal/store/canon/canon.go is de enige bron van waarheid. Zowel Append (schrijven) als Verify (teruglezen) roepen dezelfde EventHash-functie aan. Er is geen tweede implementatie die kan afdrijven:
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[:]
}
De domeinscheidingsteken "olivares.audit.v1" bindt de hash aan het doel en de versie ervan. Een hash van een ander domein (een checkpoint, een payload, een metadata-samenvatting) kan nooit botsen met een gebeurtenis-hash, zelfs als de onbewerkte bytes toevallig overeenkomen.
Ed25519 ondertekening: elk evenement is zijn eigen anker
Een hashketen bewijst interne consistentie, maar niet de authenticiteit. Een aanvaller met onbewerkte database-schrijftoegang zou de hele keten helemaal opnieuw kunnen berekenen met gewijzigde gebeurtenissen en een geldige keten kunnen produceren - anders dan het origineel, maar intern consistent. Hash-ketens detecteren manipulatie; ze bewijzen oorsprong niet.
Ed25519-handtekeningen per gebeurtenis sluiten deze gap. Elke gebeurtenis die aan het grootboek wordt toegevoegd, wordt tijdens het schrijven ondertekend. De handtekening omvat een door een domein gescheiden preimage van de tenant, het volgnummer en de ketenhash van de gebeurtenis:
domain ("olivares.audit.event.v1") || tenant || seq (8 bytes, big-endian) || hash
De handtekening wordt opgeslagen op de gebeurtenis, maar is door het ontwerp uitgesloten van de chain-hash-voorafbeelding. Dit is niet toevallig: als de handtekening in de hash zou zijn opgenomen, zou het ondertekenen van een gebeurtenis de hash veranderen die het zou moeten bevestigen. De handtekening bevestigt de hash zonder deze te wijzigen.
Een externe verificateur die alleen de openbare sleutel vasthoudt, kan elke gebeurtenis afzonderlijk bevestigen: de ketenhash opnieuw berekenen op basis van de velden van de gebeurtenis, de voorafbeelding reconstrueren en de handtekening Ed25519 verifiëren. Als een gebeurtenis na ondertekening is gewijzigd, mislukt de handtekeningcontrole voor die specifieke gebeurtenis; de verificateur hoeft het systeem dat het bewijsmateriaal heeft geproduceerd niet te vertrouwen.
Het platform ondersteunt ook sleutelrotatie. Een keten waarvan de ondertekeningssleutel halverwege de levensduur is gewijzigd, verifieert end-to-end door de huidige sleutel plus de openbare sleutels van voorgaande generaties vast te zetten. De verificatiefunctie accepteert een reeks kandidaatsleutels en beschouwt een gebeurtenis als geldig als een kandidaat deze verifieert.
Naast handtekeningen per gebeurtenis notariseren periodieke controlepunten de ketentip onder een afzonderlijk handtekeningdomein (olivares.audit.checkpoint.v1). Voor organisaties die zich moeten verdedigen tegen compromittering op hostniveau (niet alleen op databaseniveau) kunnen controlepunten worden ondertekend met een off-box KMS/HSM-sleutel (AWS KMS, GCP Cloud KMS, Azure Key Vault), waarbij de privésleutel nooit op de host staat. Handtekeningen per gebeurtenis behandelen de alleen-DB-aanvaller; off-box-controlepunten behandelen de aanvaller die een host compromittert. De twee dreigingsmodellen zijn verschillend; geen enkele handtekening dekt beide.
Het grootboekcontract: verzegeld in dezelfde transactie
Een veel voorkomende fout in auditsystemen is de uiteindelijke consistentie tussen de statusmutatie en het auditrecord. De status verandert, het auditschrijven wordt in de wachtrij geplaatst of in batches geplaatst, en als het auditschrijven mislukt, is de statuswijziging al doorgevoerd. Het resultaat: niet-gecontroleerde mutaties die wel in het systeem voorkomen, maar niet in het bewijsmateriaal.
Het platform dwingt een sterker contract af. Zowel de statusmutatie als de grootboekzegel vinden plaats in dezelfde databasetransactie. Als het zegel mislukt, wordt de hele transitie teruggedraaid – de staatsmutatie zal nooit plaatsvinden. Dit is geen inspanning; het is ontkennen-gesloten.
Het runtime-grootboek van de sessie illustreert dit. Wanneer een Claude Code-sessie overgaat naar de status (aangemaakt, gestart, gestopt, gestopt, mislukt), registreert appendRunEvent de overgang atomair op twee plaatsen:
- Het globale, met hash-chain verbonden auditgrootboek via
sc.Audit().Append- de manipulatiebestendige keten verankerd door eenPayloadHash. - Het per sessie doorzoekbare grootboek: een projectie die alleen als bijlage kan worden toegevoegd en die door
audit_seqaan de mondiale keten is gekoppeld.
Beide schrijfbewerkingen vinden plaats binnen de Mutate-transactie van de beller. Het codecommentaar in runtime_ledger.go vermeldt de bedoeling van het ontwerp rechtstreeks: “het grootboek is het registratiesysteem, dus als de verzegeling mislukt, wordt de hele overgang teruggedraaid - het is NIET de beste poging.”
De PayloadHash zelf houdt zich alleen bezig met canonieke, niet-gevoelige overgangsfeiten: de runreferentie, volgorde, gebeurtenistype, statusovergang en tijdstempel. Het bevat nooit transcriptie-inhoud, aanwijzingen, omgevingswaarden of geheimen. Het grootboek bewijst wat er is gebeurd; het slaat niet op wat er is gezegd.
Hetzelfde patroon is van toepassing op mutaties in werkruimtebestanden in workspace_ledger.go. Een bestand schrijven, mkdir, verplaatsen of verwijderen wordt verzegeld voordat de bestandssysteembewerking wordt uitgevoerd. Als het bewijsmateriaal niet kan worden toegevoegd, wordt de mutatie niet uitgevoerd. Het zegel bevat het bewerkingstype, het pad en een SHA-256 van de geschreven inhoud - nooit de inhoudsbytes zelf.
OSCAL-export: machinaal leesbaar bewijs dat de tooling van uw auditor opneemt
Een fraudebestendig grootboek is noodzakelijk, maar niet voldoende voor een auditor. Als het bewijsmateriaal een bedrijfseigen formaat heeft, is de auditor nog steeds afhankelijk van uw hulpmiddelen om het te interpreteren. OSCAL - de Open Security Controls Assessment Language, onderhouden door NIST - is het formaat waarmee deze gap wordt afgesloten.
Het platform exporteert verzegelde bewijspakketten als een OSCAL-bundel met daarin drie modellen:
- Componentdefinitie: de mogelijkheden van het besturingsvlak, uitgedrukt als geïmplementeerde vereisten op basis van een complianceframework (NIST SP 800-53, ISO 27001, EU AI Act en andere). Elke geïmplementeerde vereiste draagt de controle-ID, de capaciteitssleutels die dit bewijzen, en de echte status als een aangepaste eigenschap.
- Beoordelingsresultaten: bevindingen per controle met een OSCAL-conforme status. Het doel van elke bevinding bevat
satisfiedofnot-satisfied, waarbij de exacte productstatus wordt bewaard in het redenveld. - Control mapping: een kruising van raamwerkcontroles naar het capaciteitsreferentiemodel van het platform, met behulp van het OSCAL 1.2.0 control mapping-model. De relatie is altijd
intersects-with: de mogelijkheden hebben betrekking op een deel van een besturingselement. Het beweert nooit conformiteit; die bewering leeft alleen in beoordelingsresultaten, gebaseerd op live operationeel bewijs.
De eerlijkheidsbeperking in de OSCAL-export is de moeite waard om expliciet te vermelden. De zoekstatusenum van OSCAL heeft precies twee waarden: satisfied en not-satisfied. Er is geen “partial” of “door ontwerp”. Een besturingselement dat partially is geïmplementeerd, geadresseerd door het ontwerp, gapped of unmapped, wordt toegewezen aan OSCAL not-satisfied, waarbij de echte productstatus in status.reason wordt weergegeven en een aangepaste eigenschap onder de eigen naamruimte van het platform (https://olivares.ai/ns/oscal). Bij de export wordt een partial-controle nooit witgewassen in satisfied. Alleen controles ondersteund door live operationeel bewijs op het moment van verzegeling ontvangen OSCAL satisfied.
Elk OSCAL-document heeft grootboekankereigenschappen: de manifesthash, het grootboekvolgnummer op het moment van verzegeling, de grootboekhash en het resultaat van de integriteitsverificatie. Dit vormt de brug tussen het OSCAL-document dat de auditor in zijn GRC-tool leest en de onderliggende fraudebestendige keten die hij onafhankelijk kan verifiëren.
Wat offline verificatie concreet betekent
“Offline verificatie” is geen marketinguitdrukking. Het beschrijft een specifieke technische procedure: de verificateur neemt het geëxporteerde bewijsmateriaal, voert een verificatietool uit op een air-gapped-machine en bevestigt de integriteit van het bewijsmateriaal zonder enige netwerktoegang tot het systeem dat het heeft geproduceerd.
De archiefexport van het platform schrijft een directorystructuur van JSONL-segmenten (één regel per gebeurtenis, canonieke JSON) plus een manifest per segment. Het manifest registreert het reeksbereik van het segment, het aantal gebeurtenissen, de hashes van de eerste en laatste keten, een SHA-256 van het gebeurtenissenbestand en de laatste hash van het vorige segment voor continuïteit tussen segmenten.
De offline verifier (VerifyArchiveDir) doet vervolgens het volgende, geheel in constant geheugen, zonder netwerkoproepen:
- Laad manifesten en koppel ze aan gebeurtenisbestanden. Een verdwaald gebeurtenissenbestand (geen manifest) of een manifest waarvan het gebeurtenissenbestand ontbreekt, is een fout. De bewijseenheid is het paar.
- Stream elk gebeurtenisbestand regel voor regel. Leid voor elke gebeurtenis de keten-hash opnieuw af van de gearchiveerde velden met behulp van dezelfde
EventHash-functie die het live-systeem gebruikt. Vergelijk het met de opgeslagen hash. Controleer deprev_hash-koppeling en volg de gap-vrijheid. - Controleer de canonaliteit. Hergroepeer elke ontlede regel en bevestig dat deze byte-identieke uitvoer produceert voor de bytes op de schijf. Dit voorkomt smokkel van onbekende velden of aanvallen met dubbele sleutels die een hash-controle zouden doorstaan maar verborgen gegevens bevatten.
- Verifieer de Ed25519-handtekeningen per gebeurtenis. Reconstrueer voor elke niet-controlepuntgebeurtenis de voorafbeelding van de handtekening en verifieer deze aan de hand van de vastgezette openbare sleutel(s).
- Verifieer de handtekeningen van het controlepunt. Verifieer voor elke controlepuntgebeurtenis de handtekening onder het controlepuntdomein aan de hand van de vastgezette controlepuntsleutel(s). Als alleen gebeurtenissleutels zijn vastgezet (geen controlepuntsleutel), wordt een controlepuntgebeurtenis gemarkeerd als “niet verifieerbaar” - geweigerd-gesloten, niet overgeslagen.
- Controleer de continuïteit tussen segmenten. Bevestig dat de eerste reeks van elk segment de laatste reeks van het vorige segment plus één volgt, en dat de laatste hash van het vorige segment overeenkomt met de
prev_segment_last_hashvan het huidige segment. - Verifieer de samenvatting van het gebeurtenisbestand. De SHA-256 van het gebeurtenisbestand dat tijdens het streamen wordt berekend, moet overeenkomen met de
events_sha256van het manifest.
De verificateur rapporteert de eerste inconsistentie die hij vindt, met het specifieke volgnummer en een machinaal leesbare reden: hash-mismatch, prev-mismatch, seq-gap, event-sig-invalid, event-sig-missing, checkpoint-sig-invalid, count-mismatch, events-sha256-mismatch of segment-link-mismatch.
Een eerlijke beperking: de offline verifier bevestigt precies het bereik dat hij heeft gecontroleerd en niets daarbuiten. Een verwijderd voorvoegsel of staart is offline niet detecteerbaar; de map vermeldt niet waar de keten begon of eindigde. Het verificatierapport bevat een Ranges-veld per tenant met de vlag StartsMidChain, zodat de auditor precies weet wat er is bevestigd. De in de keten ondertekende controlepunten van het live-systeem bedekken de staart; de offline export omvat het gearchiveerde bereik. Samen vormen ze het volledige attest.
| Laag | Wat het bewijst | Wat het niet bewijst |
|---|---|---|
| Hasj-keten | Interne consistentie; elke bewerking verbreekt de keten | Origin (die de gebeurtenissen schreef) |
| Per gebeurtenis Ed25519 | Herkomst; elke gebeurtenis werd ondertekend door de sleutelhouder | Verdediging tegen compromissen op hostniveau |
| Controlepunt buiten de doos | Weerstand tegen compromissen door host (KMS/HSM-sleutel nooit op host) | Granulariteit per gebeurtenis (alleen van toepassing op controlepunten) |
| OSCAL-export | Machineleesbaar, in het raamwerk in kaart gebracht nalevingsbewijs | Dat aan alle controles volledig wordt voldaan (alleen live bewijsmateriaal telt) |
| Archiefverificatie | Offline herafleiding van al het bovenstaande | Gebeurtenissen vóór of na het geëxporteerde bereik |
Het codepad: van staatsmutatie tot verzegeld bewijsmateriaal
De volgorde van een Claude Code-sessiestatusverandering tot verifieerbaar bewijsmateriaal raakt drie lagen. In runtime_ledger.go construeert de functie appendRunEvent een PayloadHash door SHA-256 de canonieke overgangsvelden met lengte vooraf te hashen:
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
}
Die hash wordt vervolgens doorgegeven aan sc.Audit().Append, die het volgende volgnummer per tenant toewijst, linkt naar de hash van de vorige gebeurtenis, de ketenhash van deze gebeurtenis berekent via EventHash, deze ondertekent met Ed25519 en de verzegelde gebeurtenis invoegt - allemaal binnen de transactie van de beller.
Het resultaat: tegen de tijd dat de transactie wordt doorgevoerd, zijn de statuswijziging van de sessie en het fraudebestendige auditrecord beide gehandhaafd of beide teruggedraaid. Er is geen venster waar de toestand veranderde, maar het bewijsmateriaal niet.
Koppelingen
- Product: Audit — het audittraject en de export van bewijsmateriaal
- Product: Naleving — raamwerkbeoordelingen en OSCAL
- Beveiligingsmodel — het vertrouwensmodel dat ten grondslag ligt aan het grootboek