Integrität der Audit-Kette
Jede zustandsändernde Admin-Aktion in deiner fremforge-Organisation, also Änderungen an der SSO-Konfiguration, Ausnahmen vom Push-Schutz, Änderungen am Branch-Schutz, Abrechnungsübergänge und SCIM-Provisionierung, wird in einem Audit-Log erfasst. fremforge geht einen Schritt weiter als ein einfaches Log: Jeder Eintrag ist kryptografisch mit dem vorherigen verkettet, und der Kopf der Kette wird alle 2 Minuten WORM-verankert (Write-Once-Read-Many), in einem Speicher mit OBS Object Lock in T Cloud Public.
Das Ergebnis: Du kannst jede Manipulation am Audit-Log erkennen, und du kannst das selbst prüfen, ohne dich auf das Wort von fremverk verlassen zu müssen.
Diese Seite beschreibt, was die Kette garantiert, was nicht, und wie du die Prüfung durchgängig ausführst. Der technische Vertrag steht in DPA Annex A.7.
Die Garantie
Für jeden Audit-Log-Eintrag deiner Organisation speichert fremforge:
Das Ereignis selbst (Akteur, Aktion, Zeitstempel, Felder).
Einen
prev_hash, den Hash des vorherigen Eintrags deines Mandanten.Einen
hash, SHA-256 über das kanonische JSON des Eintrags.Die Kanonisierung ist exakt, und die Reihenfolge unten ist alphabetisch nach Schlüssel, nicht die Reihenfolge, in der die Felder an anderer Stelle auf dieser Seite aufgeführt sind. Ein Prüfer, der sie in einer anderen Reihenfolge hasht, erhält einen anderen Digest und schließt fälschlich, dass die Kette gebrochen ist:
sha256( {"action":…,"actor":…,"created_at_iso":…,"fields_json":…,"prev_hash":…,"tenant_id":…} )Ohne Leerzeichen.
prev_hashist am Beginn der Kette das Literalnull.created_at_isoist der Zeitstempel alsYYYY-MM-DDTHH:mm:ss.sssZ.fields_jsonist die Nutzlast als JSON-String, eingebettet als String und nicht als verschachteltes Objekt, sodass sie Byte für Byte erhalten bleibt, statt neu serialisiert zu werden. Genau dieses Detail ist die häufigste Falle: Parst du die Nutzlast und serialisierst sie neu, passt dein Digest nicht zu unserem.
Weil der Hash jedes Eintrags den vorherigen Hash enthält, bricht die Änderung eines beliebigen
Eintrags die Kette ab dieser Stelle. GET /orgs/{org}/audit/chain gibt die Rohzeilen zurück,
damit du das selbst feststellen kannst; GET /orgs/{org}/audit/integrity meldet den Durchlauf,
den wir gemacht haben.
Zusätzlich schreibt ein separater Anker-Job alle 2 Minuten (tenant_id, latest_hash, count, timestamp) in OBS von T Cloud Public, mit einer Object-Lock-Aufbewahrung von 1095 Tagen (3 Jahren) im Modus COMPLIANCE. Das Ankerobjekt ist innerhalb der Aufbewahrung physisch nicht löschbar: Die Speicherschicht weist Löschvorgänge von jedem ab, auch von fremverk. Ein Angreifer, dem es irgendwie gelänge, die gesamte Kette in der Datenbank zu ändern, müsste immer noch mit den Ankern in OBS rechnen, die nicht übereinstimmen.
Offenlegung von Lücken. Der 2-Minuten-Takt ist das Ziel; wir sagen keinen vertraglichen RPO von ≤2 Minuten für die Verankerung der Kette zu. Ist OBS in T Cloud Public nicht erreichbar (regionale Wartung, Netzwerkereignis), versucht es der Anker-Job beim nächsten Takt erneut und hält die Lücke in den Metadaten der Kette fest. Das schlimmste beobachtete Fenster, bevor der Alarm für einen Kettenbruch auslöst, beträgt 12 Minuten (Takt plus Schwellwert ANCHOR_STALE). Lücken werden im nächsten Vorfallbericht der Statusseite dokumentiert und in der Klausel zum Audit-Logging in DPA §A.7 genannt.
Was das erkennt
| Bedrohung | Erkannt? |
|---|---|
| Der Inhalt einer einzelnen Audit-Zeile wurde geändert (Akteur, Aktion, Felder) | ✓, hash_mismatch an dieser Zeile |
| Eine einzelne Zeile wurde mitten aus der Kette gelöscht | ✓, prev_hash_mismatch an der Zeile danach |
| Die ganze Kette wurde von einem Angreifer mit vollem Schreibzugriff auf die Datenbank neu geschrieben | ✓, wenn das Neuschreiben mehr als ~10 Minuten zurückliegt (schlimmstenfalls 12 Minuten): Der WORM-Anker stimmt nicht überein |
| Die ganze Kette wurde neu geschrieben und zusätzlich der WORM-Bucket manipuliert | Object Lock auf der Speicherschicht weist den Schreibvorgang in OBS physisch ab; dafür wäre eine Kompromittierung auf Seiten von T Cloud Public nötig |
| Eine Admin-Aktion, die überhaupt kein Audit-Ereignis erzeugt | Das ist eine Lücke in der Abdeckung, kein Kettenbruch, siehe „Was das nicht erkennt“ |
Was das nicht erkennt
- Lücken in der Abdeckung. Ändert ein Codepfad den Zustand, ohne ein Audit-Ereignis zu erzeugen, bleibt die Kette stumm. Wir decken die zustandsändernden Routen aus Annex A.7 ab und prüfen die Routen vierteljährlich, um zu bestätigen, dass die Abdeckung hält.
- Historie vor Annex A.7. Audit-Ereignisse, die erzeugt wurden, bevor die Verankerung der Kette für deinen Mandanten eingeschaltet war, tragen einen Marker
pre-chain-epochund sind ausdrücklich NICHT Teil der prüfbaren Kette. - Leseereignisse. Die Kette erfasst nur Änderungen. Ein Leck beim Lesezugriff (jemand sieht Daten, die er nicht sehen sollte) erscheint nicht als Eintrag in der Kette. Der Ausschnitt des Audit-Logs erfasst den Lesezugriff zwar, wo die Route ein Ereignis erzeugt, aber die Abdeckung von reinen Leserouten ist eine eigene Garantie, keine Garantie der Kette.
Die Kette selbst prüfen
Das ist eine lokale Neuberechnung, kein Abruf eines Status. fremforge audit-verify lädt die
Rohzeilen von GET /orgs/{org}/audit/chain herunter, jede mit ihrem hash und prev_hash, und
berechnet jeden Digest und jede Verknüpfung auf deinem Rechner neu. Danach ruft es unser eigenes
Urteil ab und vergleicht. Der Exit-Code ist dein Ergebnis; eine Abweichung zwischen beiden wird
gemeldet und lässt den Befehl fehlschlagen.
Der Unterschied ist wichtiger, als er aussieht. Alles oberhalb dieses Abschnitts beschreibt eine Garantie gegen jemanden, der die Datenbank erreicht, aber nicht die API: Der WORM-Anker steht im Object-Lock-Modus COMPLIANCE, sodass eine manipulierte Datenbank nicht mit ihm in Übereinstimmung gebracht werden kann. Nichts davon schränkt die API selbst ein, und bis August 2026 hat dieselbe Partei, die die Antwort meldete, auch den Abgleich zwischen dem Kettendurchlauf und dem Anker durchgeführt. Die lokale Prüfung macht die Garantie für dich überprüfbar, statt dass wir sie nur behaupten.
Der Anker ist signiert, und du prüfst die Signatur, nicht wir. Jeder Anker wird mit einem
DSSE-Envelope geschrieben, signiert mit demselben Ed25519-Schlüssel wie unsere SLSA-Build-Provenienz,
dessen öffentlicher Teil unter
www.frem.sh/.well-known/slsa-trust-root.json
veröffentlicht ist.
fremforge audit-verify holt diese Vertrauensbasis direkt von www.frem.sh, nicht über die
API, und es gibt keinen Rückfall auf einen Schlüssel, den die API geliefert hat: Eine Signatur,
die gegen einen Schlüssel geprüft wird, den dir die geprüfte Partei gegeben hat, beweist nichts.
Danach prüft es, ob der Subject-Digest des Envelopes gleich sha256(anchor_head) ist, sodass ein
Envelope, der kryptografisch gültig ist, aber zu einem anderen Anker gehört, abgelehnt statt
akzeptiert wird.
Zwei ehrliche Einschränkungen:
- Anker, die vor dem 2026-08-04 geschrieben wurden, sind unsigniert und melden
NOT CHECKED. Es gibt keine nachträgliche Signatur: Der Wert eines Ankers liegt darin, dass er zu einem bestimmten Zeitpunkt unter Object Lock geschrieben wurde, und die Historie umzuschreiben, um eine Signatur hinzuzufügen, würde genau das zunichtemachen. - Das Signieren ist auf unserer Seite bewusst nicht fatal. Schlägt der Signierer fehl, wird der
Anker trotzdem geschrieben: Ein unsignierter Anker ist weit besser als gar keiner, denn der Anker
ist die letzte Absicherung, die Manipulation sichtbar macht. Wir alarmieren darauf
(
AUDIT_ANCHOR_UNSIGNED, Major), damit die Verschlechterung laut statt still ist, undaudit-verifymeldetNOT CHECKED, statt etwas vorzutäuschen.
Verwende die 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 --humanDie Ausgabe sieht so aus:
✓ 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.Ohne --human gibt die CLI rohes JSON für Skripte aus, einschließlich beider Urteile und einer
etwaigen Abweichung zwischen ihnen:
# Exit 0 only if YOUR walk was clean and ours agreed
fremforge audit-verify acme || echo "verification failed: $?"Exit-Codes, alle aus dem lokalen Durchlauf abgeleitet:
0: Die Kette wurde geprüft, und unser Urteil stimmte überein.2: teilweise. Entweder gibt es Marker-Zeilenchain-broken(wir haben die Audit-Zeile geschrieben, aber die Ketten-Transaktion ist fehlgeschlagen; die Zeile bleibt zur Sichtbarkeit erhalten, und der Kette fehlt dort ein Glied), oder die Zeilenobergrenze hat den Durchlauf abgeschnitten, sodass ein Teil der Kette nicht geprüft wurde. Wir melden bei einem abgeschnittenen Durchlaufok, die CLI nicht: Zeilen zu bestätigen, die niemand durchlaufen hat, ist genau die Art von Übertreibung, die dieser Befehl vermeiden soll.3: gebrochen. Der neu berechnete Hash einer Zeile stimmt nicht mit ihrem gespeicherten Hash überein, ODER derprev_hasheiner Zeile stimmt nicht mit der laufenden Kette überein. Die Ausgabe enthält die ID der Zeilefirst_breakund den Grund.4: Anker stimmt nicht überein. Die WORM-verankerte Spitze ist in deiner Kette nicht vorhanden, die verankerte Zeile wurde also gelöscht oder geändert. Das ist eine Prüfung auf Enthaltensein: Ein aktiver Mandant, dessen neueste Zeilen noch nicht verankert sind, ist normal und besteht.5: Wir waren anderer Meinung. Dein Durchlauf war sauber, unser Urteil nicht. Meist wurde zwischen den beiden Aufrufen eine Zeile angehängt. Führe ihn erneut aus, um das zu bestätigen; bleibt es dabei, sehen wir etwas, das du nicht siehst.
Die Richtung einer Abweichung ist nicht symmetrisch, und die CLI sagt, welche sie gefunden hat. Ist
unser Urteil schlechter als deines, ist das meist ein Zeitartefakt. Ist unser Urteil besser als
deines (wir sagen ok, wo deine eigene Rechnung broken ergibt), ist das genau der Fall, den
dieser ganze Mechanismus aufdecken soll, und er wird als Vorfall gemeldet, nicht als Warnung.
Was wir intern tun
Der Kopf der Kette wird alle 2 Minuten in WORM-Speicher verankert, und die Ankerkette wird fortlaufend unabhängig neu geprüft: Die Ankerkette jedes Mandanten wird vom Anfang an vorwärts durchlaufen und auf Lückenlosigkeit geprüft. Jede Abweichung und jeder Mandant, dessen Anker veraltet (älter als ~10 Minuten; schlimmstenfalls 12 Minuten), alarmiert sofort die Rufbereitschaft, als Frühindikator dafür, dass keine Anker mehr geschrieben werden. Auswirkungen auf Kunden regelt DPA §11A, und Lücken werden nach einem Vorfall auf unserer Statusseite dokumentiert.
Pseudonymisierung nach DSGVO
Wenn du das Recht auf Löschung ausübst, werden die Audit-Ereignisse deines Mandanten pseudonymisiert, nicht gelöscht: Das Feld actor wird zu [erased], und fields_json wird auf {} zurückgesetzt. Das Kettenglied (der Hash) bleibt erhalten, sodass die Kette prüfbar bleibt; die personenbezogenen Daten werden entfernt.
Ein Lauf von fremforge audit-verify gegen einen gelöschten Mandanten zeigt daher
tenant_erased_count > 0, und diese Zeilen werden getrennt gemeldet, nicht als Brüche: Eine
pseudonymisierte Zeile passt absichtlich nicht mehr zu ihrem eigenen Hash, während ihre Verknüpfung
zur folgenden Zeile weiterhin geprüft wird. Die fünf Zähler (verified_count, pre_chain_epoch_count,
chain_broken_count, tenant_erased_count, payload_redacted_count) ergeben zusammen nicht
rows_walked: Eine einzelne Zeile kann sowohl gelöscht als auch in der Nutzlast geschwärzt sein und
wird dann unter beidem gezählt. Die Kette ist intakt; die Inhalte sind bereinigt. Die Zeilen selbst werden nicht gelöscht, wenn das Aufbewahrungsfenster des WORM-Ankers von 3 Jahren abläuft; heute löscht sie kein Job (korrigiert am 2026-10-08: Diese Seite hatte das Gegenteil behauptet).
Unabhängige Prüfung
Prüfer können unter NDA einen durchgängigen Durchgang durch die Kette anfordern. Schreib an compliance@frem.sh, um das Paket für die technische Bewertung zu erhalten.