Integriteit van de auditketen
Elke beheeractie die de toestand van je fremforge-organisatie verandert, zoals wijzigingen in de SSO-configuratie, het overschrijven van push-bescherming, updates van branchbescherming, overgangen in de facturatie en SCIM-provisioning, wordt vastgelegd in een auditlog. fremforge gaat een stap verder dan een gewone log: elk item is cryptografisch geketend aan het vorige, en de kop van de keten wordt elke 2 minuten WORM-verankerd (Write-Once-Read-Many) in opslag met OBS Object Lock in T Cloud Public.
Het resultaat: je kunt elke manipulatie van de auditlog ontdekken, en je kunt dat zelf verifiëren zonder fremverk op zijn woord te hoeven geloven.
Deze pagina beschrijft wat de keten garandeert, wat niet, en hoe je de verificatie van begin tot eind uitvoert. Het technische contract staat in DPA-bijlage A.7.
De garantie
Voor elk item in de auditlog dat voor je organisatie wordt vastgelegd, slaat fremforge op:
De gebeurtenis zelf (actor, actie, tijdstempel, velden).
Een
prev_hash, de hash van het vorige item voor je tenant.Een
hash, SHA-256 over de canonieke JSON van het item.De canonicalisering is exact, en de volgorde hieronder is alfabetisch op sleutel, niet de volgorde waarin de velden elders op deze pagina staan. Een verificatietool die ze in een andere volgorde hasht, krijgt een andere digest en concludeert ten onrechte dat de keten gebroken is:
sha256( {"action":…,"actor":…,"created_at_iso":…,"fields_json":…,"prev_hash":…,"tenant_id":…} )Geen witruimte.
prev_hashis de letterlijke waardenullbij het begin van de keten.created_at_isois het tijdstempel alsYYYY-MM-DDTHH:mm:ss.sssZ.fields_jsonis de payload als JSON-string, ingebed als string en niet als genest object, zodat hij byte voor byte behouden blijft in plaats van opnieuw te worden geserialiseerd. Dat laatste detail is waar mensen op vastlopen: parse en serialiseer je de payload opnieuw, dan komt je digest niet overeen met de onze.
Omdat de hash van elk item de vorige hash bevat, breekt het wijzigen van een item de keten
vanaf dat punt. GET /orgs/{org}/audit/chain geeft de ruwe rijen terug, zodat je dat zelf
kunt opsporen; GET /orgs/{org}/audit/integrity rapporteert de controle die wij hebben gedaan.
Daarnaast schrijft een aparte verankeringsjob elke 2 minuten (tenant_id, latest_hash, count, timestamp) naar OBS in T Cloud Public, met een Object Lock-bewaartermijn van 1095 dagen (3 jaar), in COMPLIANCE-modus. Het ankerobject is binnen de bewaartermijn fysiek niet te verwijderen: de opslaglaag weigert verwijderingen van iedereen, ook van fremverk. Een aanvaller die op de een of andere manier de hele keten in de database zou wijzigen, krijgt nog steeds te maken met de ankers in OBS, die het daar niet mee eens zijn.
Openheid over hiaten. De frequentie van 2 minuten is het doel; we beloven contractueel geen RPO van ≤2 minuten voor de verankering van de keten. Is OBS in T Cloud Public onbereikbaar (regionaal onderhoud, een netwerkincident), dan probeert de verankeringsjob het bij de volgende tik opnieuw en legt hij het hiaat vast in de metadata van de keten. Het slechtste waargenomen venster voordat het alarm voor een gebroken keten afgaat, is 12 minuten (frequentie + drempel ANCHOR_STALE). Hiaten worden beschreven in het volgende incidentrapport op de statuspagina en genoemd in de clausule over auditlogging in DPA §A.7.
Wat dit opspoort
| Dreiging | Opgespoord? |
|---|---|
| De inhoud van één auditrij is gewijzigd (actor, actie, velden) | ✓, hash_mismatch op die rij |
| Eén rij is midden uit de keten verwijderd | ✓, prev_hash_mismatch op de rij daarna |
| De hele keten is herschreven door een aanvaller met volledige schrijftoegang tot de database | ✓ als het herschrijven meer dan ~10 minuten geleden gebeurde (in het slechtste geval 12 minuten), dan wijkt het WORM-anker af |
| De hele keten is herschreven + ook de WORM-bucket is gemanipuleerd | Object Lock in de opslaglaag weigert de schrijfactie naar OBS fysiek; daarvoor zou T Cloud Public zelf gecompromitteerd moeten zijn |
| Een beheeractie die helemaal geen auditgebeurtenis oplevert | Dit is een hiaat in de dekking, geen breuk in de keten, zie “Wat dit niet opspoort” |
Wat dit niet opspoort
- Hiaten in de dekking. Verandert een codepad de toestand zonder een auditgebeurtenis op te leveren, dan zwijgt de keten. We dekken de routes die de toestand veranderen in bijlage A.7; elk kwartaal controleren we de routes om te bevestigen dat de dekking standhoudt.
- Geschiedenis van vóór bijlage A.7. Auditgebeurtenissen die zijn vastgelegd voordat de verankering van de keten voor je tenant was ingeschakeld, hebben de markering
pre-chain-epochen maken uitdrukkelijk GEEN deel uit van de verifieerbare keten. - Leesgebeurtenissen. De keten legt alleen wijzigingen vast. Een lek van leestoegang (iemand die gegevens ziet die hij niet zou mogen zien) verschijnt niet als item in de keten. Het betreffende deel van de auditlog legt de leesactie wel vast als de route er een oplevert, maar de dekking van alleen-lezenroutes is een aparte garantie, geen garantie van de keten.
De keten zelf verifiëren
Dit is een lokale herberekening, geen uitgelezen status. fremforge audit-verify downloadt
de ruwe rijen van GET /orgs/{org}/audit/chain, elk met haar hash en prev_hash, en
berekent elke digest en elke schakel opnieuw op jouw machine. Daarna haalt het ons eigen
oordeel op en vergelijkt het. De exitcode is jouw resultaat; een verschil tussen de twee
wordt gemeld en laat het commando mislukken.
Dat onderscheid is belangrijker dan het lijkt. Alles boven deze sectie beschrijft een garantie tegen iemand die bij de database kan maar niet bij de API: het WORM-anker staat in Object Lock in COMPLIANCE-modus, dus een gemanipuleerde database kan er niet mee in overeenstemming worden gebracht. Niets daarvan legt de API zelf beperkingen op, en tot augustus 2026 werd de kruiscontrole tussen de controle van de keten en het anker uitgevoerd door dezelfde partij die het antwoord rapporteerde. Lokale verificatie maakt van de garantie iets wat jij kunt controleren, in plaats van iets wat wij beweren.
Het anker is ondertekend, en jij verifieert de handtekening, niet wij. Elk anker wordt
geschreven met een DSSE-envelop, ondertekend met dezelfde Ed25519-sleutel als onze
SLSA-buildprovenance, waarvan de publieke helft is gepubliceerd op
www.frem.sh/.well-known/slsa-trust-root.json.
fremforge audit-verify haalt die trust root rechtstreeks op van www.frem.sh, niet van de
API, en valt nooit terug op een sleutel die de API aanleverde: een handtekening die is
gecontroleerd met een sleutel die de gecontroleerde partij je gaf, bewijst niets. Daarna
controleert het of de subject-digest van de envelop gelijk is aan sha256(anchor_head), zodat
een envelop die cryptografisch geldig is voor een ander anker wordt geweigerd in plaats van
geaccepteerd.
Twee eerlijke kanttekeningen:
- Ankers die vóór 2026-08-04 zijn geschreven, zijn niet ondertekend en melden
NOT CHECKED. Er wordt niet achteraf ondertekend: de waarde van een anker is dat het op een bepaald moment onder Object Lock is geschreven, en de geschiedenis herschrijven om er een handtekening aan toe te voegen zou dat tenietdoen. - Ondertekenen is aan onze kant bewust niet fataal. Mislukt de ondertekening, dan wordt het
anker toch geschreven: een niet-ondertekend anker is veel beter dan geen anker, want het
anker is het laatste vangnet om manipulatie zichtbaar te maken. We slaan daarop alarm
(
AUDIT_ANCHOR_UNSIGNED, Major), zodat de achteruitgang luid is in plaats van stil, enaudit-verifymeldtNOT CHECKEDin plaats van te doen alsof.
Gebruik de fremforge-CLI:
# Install (one-liner — see /cli/ for manual download and SHA-256 verification)
curl -sSfL https://cli.frem.sh/cli/install.sh | sh
# Configure auth — generate a PAT at https://frem.sh/user/settings/applications
# with the audit:read scope.
export FREMFORGE_TOKEN='<your PAT>'
# Verify the chain for your org
fremforge audit-verify acme --humanDe uitvoer ziet er zo uit:
✓ local verification: ok
(hashes recomputed on this machine — the server was not asked)
walk_mode: tail
rows_walked: 1284
verified_count: 1280
pre_chain_epoch_count: 4
chain_broken_count: 0
tenant_erased_count: 0
payload_redacted_count: 0
last_verified_hash: a2b3c4...
✓ WORM anchor (OBS Object Lock):
anchor_head: f8e9d0...
chain_tip_at_anchor: a2b3c4...
count_at_anchor: 1280
timestamp: 2026-05-05T18:00:00.000Z
age_seconds: 120
agrees_with_local_walk: true
signature: VERIFIED (keyid fremforge-slsa-builder-v1)
against the trust root fetched from www.frem.sh, not from this API —
so the anchored tip is the tip that was anchored.
server verdict: ok
server agrees with the local walk.Zonder --human geeft de CLI ruwe JSON voor scripts, inclusief beide oordelen en elk verschil
daartussen:
# Exit 0 only if YOUR walk was clean and ours agreed
fremforge audit-verify acme || echo "verification failed: $?"Exitcodes, allemaal afgeleid van de lokale controle:
0: de keten is geverifieerd en ons oordeel kwam overeen.2: gedeeltelijk: er zijn rijen met de markeringchain-broken(we hebben de auditrij geschreven, maar de transactie voor de keten mislukte, dus de rij is bewaard om zichtbaar te blijven en de keten mist daar een schakel), of de maximale hoeveelheid rijen heeft de controle afgekapt, zodat een deel van de keten niet is gecontroleerd. Let op: wij meldenokbij een afgekapte controle en de CLI niet. Instaan voor rijen die niemand heeft gecontroleerd, is precies het soort overdrijving dat dit commando moet voorkomen.3: gebroken: de herberekende hash van een rij komt niet overeen met haar opgeslagen hash, OF deprev_hashvan een rij komt niet overeen met de lopende keten. De uitvoer bevat de id van de rijfirst_breaken de reden.4: afwijkend anker: de top die in het WORM-anker staat, komt niet voor in je keten, wat betekent dat de verankerde rij is verwijderd of gewijzigd. Let op: dit is een controle op aanwezigheid: een actieve tenant waarvan de nieuwste rijen nog niet zijn verankerd, is normaal en slaagt.5: wij waren het oneens: jouw controle was schoon, maar ons oordeel niet. Meestal is er tussen de twee aanroepen een rij toegevoegd. Voer het opnieuw uit om het te bevestigen; blijft het zo, dan zien wij iets wat jij niet ziet.
De richting van een verschil is niet symmetrisch, en de CLI zegt welke hij vond. Is ons oordeel
slechter dan het jouwe, dan is dat meestal een kwestie van timing. Is ons oordeel beter dan
het jouwe (wij zeggen ok waar je eigen berekening broken zegt), dan is dat precies het geval
waarvoor dit hele mechanisme bestaat, en het wordt gemeld als incident in plaats van als
waarschuwing.
Wat we intern doen
De kop van de keten wordt elke 2 minuten in WORM-opslag verankerd, en de ankerketen wordt doorlopend onafhankelijk opnieuw geverifieerd: de ankerketen van elke tenant wordt vanaf het begin doorlopen en gecontroleerd op continuïteit. Bij elke afwijking, of bij elke tenant waarvan het anker verouderd raakt (ouder dan ~10 minuten; in het slechtste geval 12 minuten), wordt de dienstdoende engineer direct opgeroepen, als vroege aanwijzing dat de verankering is gestopt. De gevolgen voor klanten vallen onder DPA §11A, en hiaten worden na een incident beschreven op onze statuspagina.
Pseudonimisering onder de AVG
Maak je gebruik van het recht op vergetelheid, dan worden de auditgebeurtenissen van je tenant gepseudonimiseerd, niet verwijderd: het veld actor wordt [erased] en fields_json wordt teruggezet naar {}. De schakel in de keten (de hash) blijft behouden, zodat de keten verifieerbaar blijft; de persoonsgegevens worden verwijderd.
Een run van fremforge audit-verify voor een gewiste tenant toont daarom
tenant_erased_count > 0, en die rijen worden apart gemeld in plaats van als breuken: een
gepseudonimiseerde rij komt bewust niet meer overeen met haar eigen hash, terwijl haar schakel
naar de volgende rij nog wel wordt gecontroleerd. De vijf tellers (verified_count,
pre_chain_epoch_count, chain_broken_count, tenant_erased_count, payload_redacted_count)
tellen niet op tot rows_walked: één rij kan zowel gewist als van haar payload ontdaan zijn, en
wordt dan onder beide geteld. De keten is intact; de inhoud is opgeschoond. De rijen zelf worden
niet verwijderd wanneer de bewaartermijn van 3 jaar voor het WORM-anker afloopt; er is vandaag
geen job die ze verwijdert (gecorrigeerd op 2026-10-08: deze pagina zei eerder van wel).
Onafhankelijke verificatie
Auditors kunnen onder NDA een volledige doorloop van de keten aanvragen. Mail compliance@frem.sh voor het pakket voor de technische beoordeling.