Hoppa till huvudinnehållet

Granskningskedjans integritet

Varje administrativ åtgärd som ändrar tillstånd i din organisation på fremforge, som ändringar i SSO-konfigurationen, undantag från Push-skydd, uppdateringar av branch-skydd, faktureringsövergångar och SCIM-provisionering, registreras i en granskningslogg. fremforge går ett steg längre än en vanlig logg: varje post är kryptografiskt kedjad till den föregående, och kedjans huvud förankras med WORM (Write-Once-Read-Many) i T Cloud Public OBS-lagring med Object Lock varannan minut.

Resultatet: du kan upptäcka varje manipulation av granskningsloggen, och du kan verifiera det själv utan att behöva lita på fremverks ord.

Den här sidan beskriver vad kedjan garanterar, vad den inte garanterar, och hur du kör verifieringen från början till slut. Det tekniska åtagandet finns i DPA bilaga A.7.

Garantin

För varje post i granskningsloggen som registreras för din organisation sparar fremforge:

  • Själva händelsen (aktör, åtgärd, tidsstämpel, fält).

  • En prev_hash, hashen av den föregående posten för din tenant.

  • En hash, SHA-256 över postens kanoniska JSON.

    Kanoniseringen är exakt, och ordningen nedan är alfabetisk efter nyckel, inte den ordning som fälten listas i på andra ställen på den här sidan. En verifierare som hashar dem i någon annan ordning får en annan digest och drar den felaktiga slutsatsen att kedjan är bruten:

    sha256( {"action":…,"actor":…,"created_at_iso":…,"fields_json":…,"prev_hash":…,"tenant_id":…} )

    Inga blanksteg. prev_hash är den bokstavliga null vid kedjans början. created_at_iso är tidsstämpeln som YYYY-MM-DDTHH:mm:ss.sssZ. fields_json är innehållet som en JSON-sträng, inbäddad som en sträng och inte som ett nästlat objekt, så att den återges byte för byte i stället för att serialiseras om. Den sista detaljen är den som brukar ställa till det: tolkar du och serialiserar om innehållet kommer din digest inte att stämma med vår.

Eftersom varje posts hash innehåller den föregående hashen bryter en ändring av någon post kedjan framåt. GET /orgs/{org}/audit/chain returnerar de råa raderna så att du kan upptäcka det själv; GET /orgs/{org}/audit/integrity rapporterar den genomgång vi gjorde.

Dessutom skriver ett separat förankringsjobb varannan minut (tenant_id, latest_hash, count, timestamp) till T Cloud Public OBS med Object Lock och lagringstiden 1095 dagar (3 år), i läget COMPLIANCE. Förankringsobjektet är fysiskt omöjligt att ta bort under lagringstiden: lagringslagret avvisar borttagningar från alla, även från fremverk. En angripare som på något sätt ändrade hela kedjan i databasen skulle ändå ha att göra med förankringarna i OBS, som inte stämmer.

Redovisning av luckor. Intervallet på 2 minuter är målet; vi utlovar ingen avtalsenlig RPO på ≤ 2 minuter för förankringen av kedjan. Om T Cloud Public OBS inte kan nås (regionalt underhåll, nätverkshändelse) försöker förankringsjobbet igen vid nästa tick och registrerar luckan i kedjans metadata. Det värsta observerade fönstret innan larmet om bruten kedja går är 12 minuter (intervallet + tröskeln ANCHOR_STALE). Luckor dokumenteras i nästa incidentrapport på statussidan och tas upp i DPA §A.7 om granskningsloggning.

Vad den fångar

HotFångas?
Innehållet i en enskild rad i granskningsloggen ändras (aktör, åtgärd, fält)✓, hash_mismatch på den raden
En enskild rad tas bort mitt i kedjan✓, prev_hash_mismatch på raden efter
Hela kedjan skrivs om av en angripare med full skrivåtkomst till databasen✓ om omskrivningen gjordes för mer än ~10 minuter sedan (12 minuter i värsta fall), WORM-förankringen stämmer inte
Hela kedjan skrivs om + WORM-bucketen manipuleras ocksåObject Lock i lagringslagret avvisar fysiskt skrivningen till OBS; skulle kräva intrång på T Cloud Publics sida
En administrativ åtgärd som inte genererar någon granskningshändelse allsDet är en lucka i täckningen, inte en bruten kedja, se “Vad den inte fångar”

Vad den inte fångar

  • Luckor i täckningen. Om en kodväg ändrar tillstånd utan att generera en granskningshändelse är kedjan tyst. Vi täcker de tillståndsändrande vägarna i bilaga A.7; vi gör en kvartalsvis granskning av vägarna för att bekräfta att täckningen håller.
  • Historik från före bilaga A.7. Granskningshändelser som genererades innan förankringen av kedjan aktiverades för din tenant har markeringen pre-chain-epoch och ingår uttryckligen INTE i den verifierbara kedjan.
  • Läshändelser. Kedjan registrerar bara ändringar. En läcka av läsåtkomst (någon ser data som den inte borde) syns inte som en post i kedjan. Granskningsloggens del registrerar läsningen där vägen genererar en, men täckningen av skrivskyddade vägar är en separat garanti, inte en garanti från kedjan.

Verifiera kedjan själv

Det här är en lokal omräkning, inte en statusläsning. fremforge audit-verify laddar ned de råa raderna från GET /orgs/{org}/audit/chain, var och en med sin hash och prev_hash, och räknar om varje digest och varje länk på din dator. Därefter hämtar den vår egen bedömning och jämför. Slutkoden är ditt resultat; om de två inte stämmer överens rapporteras det och kommandot misslyckas.

Skillnaden betyder mer än det kan verka. Allt ovanför det här avsnittet beskriver en garanti mot någon som når databasen men inte API:t: WORM-förankringen ligger i Object Lock i läget COMPLIANCE, så en manipulerad databas kan inte fås att stämma med den. Inget av det begränsar själva API:t, och fram till augusti 2026 gjordes korskontrollen mellan genomgången av kedjan och förankringen av samma part som rapporterade svaret. Lokal verifiering är det som gör att du kan kontrollera garantin i stället för att vi bara påstår den.

Förankringen är signerad, och det är du som verifierar signaturen, inte vi. Varje förankring skrivs med ett DSSE-kuvert som signeras med samma Ed25519-nyckel som vår SLSA-byggproveniens, vars publika del publiceras på www.frem.sh/.well-known/slsa-trust-root.json.

fremforge audit-verify hämtar den förtroenderoten direkt från www.frem.sh, inte från API:t, och det finns ingen reserv till en nyckel som API:t tillhandahåller: en signatur som kontrolleras mot en nyckel som den granskade parten gav dig bevisar ingenting. Därefter kontrolleras att kuvertets subject-digest är lika med sha256(anchor_head), så att ett kuvert som är kryptografiskt giltigt för en annan förankring avvisas i stället för att godtas.

Två ärliga reservationer:

  • Förankringar som skrevs före 2026-08-04 är osignerade och rapporteras som NOT CHECKED. Det finns ingen signering i efterhand: en förankrings värde ligger i att den skrevs vid en viss tidpunkt under Object Lock, och att skriva om historiken för att lägga till en signatur skulle motverka det.
  • Signeringen är avsiktligt inte kritisk på vår sida. Om signeraren misslyckas skrivs förankringen ändå: en osignerad förankring är mycket bättre än ingen förankring, eftersom förankringen är det sista skyddet för att upptäcka manipulation. Vi larmar om det (AUDIT_ANCHOR_UNSIGNED, Major) så att försämringen märks i stället för att vara tyst, och audit-verify rapporterar NOT CHECKED i stället för att låtsas.

Använd fremforges 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 --human

Utdata ser ut så här:

✓ 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.

Utan --human skriver CLI:t ut rå JSON för skript, inklusive båda bedömningarna och eventuella avvikelser mellan dem:

# Exit 0 only if YOUR walk was clean and ours agreed
fremforge audit-verify acme || echo "verification failed: $?"

Slutkoder, alla härledda från den lokala genomgången:

  • 0, kedjan verifierades och vår bedömning stämde
  • 2, delvis: antingen finns det rader med markeringen chain-broken (vi skrev raden i granskningsloggen men kedjetransaktionen misslyckades, så raden bevaras för synlighetens skull och kedjan saknar en länk där), eller så kortade radgränsen genomgången så att en del av kedjan inte kontrollerades. Observera att vi rapporterar ok för en avkortad genomgång medan CLI:t inte gör det: att intyga rader som ingen har gått igenom är precis den sortens överdrift som det här kommandot finns för att undvika.
  • 3, bruten: en rads omräknade hash stämmer inte med dess sparade hash, ELLER en rads prev_hash stämmer inte med den löpande kedjan. Utdata innehåller rad-id:t och orsaken i first_break.
  • 4, förankringen stämmer inte: den WORM-förankrade spetsen finns inte i din kedja, vilket betyder att den förankrade raden har tagits bort eller ändrats. Observera att det här är en kontroll av innehållande: en aktiv tenant vars nyaste rader ännu inte är förankrade är normalt och godkänns.
  • 5, vi var oeniga: din genomgång var ren men vår bedömning var det inte. Oftast en rad som lades till mellan de två anropen. Kör igen för att bekräfta; om det kvarstår ser vi något som du inte ser.

Riktningen på en oenighet är inte symmetrisk, och CLI:t talar om vilken det fann. Att vår bedömning är sämre än din är oftast en tidsartefakt. Att vår bedömning är bättre än din, att vi säger ok där din egen beräkning säger broken, är precis det fall som hela mekanismen finns för att synliggöra, och det rapporteras som en incident snarare än en varning.

Vad vi gör internt

Kedjans huvud förankras varannan minut i WORM-lagring, och förankringskedjan verifieras på nytt oberoende enligt ett kontinuerligt schema: varje tenants förankringskedja gås igenom framåt från början och kontrolleras för kontinuitet. Varje avvikelse, eller varje tenant vars förankring blir inaktuell (äldre än ~10 minuter; 12 minuter i värsta fall), larmar jourhavande direkt som en tidig indikator på att förankringsskrivningarna har upphört. Påverkan för kunderna täcks av DPA §11A, och luckor dokumenteras på vår statussida efter incidenten.

Pseudonymisering enligt GDPR

När du utövar rätten till radering pseudonymiseras granskningshändelserna för din tenant, de tas inte bort: fältet actor blir [erased] och fields_json återställs till {}. Kedjans länk (hashen) behålls så att kedjan förblir verifierbar; personuppgifterna tas bort.

Det betyder att en körning av fremforge audit-verify mot en raderad tenant visar tenant_erased_count > 0, och de raderna rapporteras separat i stället för som brott: en pseudonymiserad rad stämmer avsiktligt inte längre med sin egen hash, medan dess länk till nästa rad fortfarande kontrolleras. De fem räknarna (verified_count, pre_chain_epoch_count, chain_broken_count, tenant_erased_count, payload_redacted_count) summerar inte till rows_walked: en enskild rad kan vara både raderad och ha rensat innehåll, och räknas då under båda. Kedjan är intakt; innehållet är rensat. Själva raderna tas inte bort när WORM-förankringens lagringstid på 3 år löper ut; inget jobb tar bort dem i dag (rättat 2026-10-08: den här sidan sade tidigare att de togs bort).

Oberoende verifiering

Revisorer kan begära en genomgång av kedjan från början till slut under sekretessavtal: mejla compliance@frem.sh för att få det tekniska bedömningspaketet.