How firm's audit log knows if someone tampered with itHoe firm's auditlog merkt dat er in geknoeid is
Visit the site ↗Bekijk de site ↗ view on githubbekijk op github
Imagine you keep a diary, and the rule is: you only ever add new lines. You never erase anything. That’s an audit log — a list of things that happened, in order, that you’re supposed to be able to trust forever. “User 12 changed the price.” “Invoice 4200 was paid.” “Admin deleted an account.”
Now here’s the uncomfortable question. The diary lives in your database. Lots of people — and programs — can reach that database. What stops someone from quietly opening the diary, changing one line, and closing it again as if nothing happened?
With a normal audit log: nothing. And that’s the whole problem. A log you can’t trust is just a story.
firm’s audit module has an opt-in feature called tamper evidence that fixes this. The name is the important part: it doesn’t stop a determined person from messing with your data — nobody can promise that. What it guarantees is that if they do, you will find out. No silent edits. Ever.
Here’s how it works, in three layers, each one closing a hole the previous one left open.
Layer 1: a secret fingerprint on every line
When you turn tamper evidence on, you give firm a secret key — think of it as a password only your app knows.
Every time a new line goes into the diary, firm uses that secret to stamp a tiny fingerprint next to it. The fingerprint is calculated from the exact words of the line plus the secret. (The technical name is an HMAC, but “fingerprint made with a secret” is all you need.)
The magic: change a single letter in the line, and the fingerprint no longer matches. And because you need the secret to make a correct fingerprint, an attacker who edits a line can’t fake a new one. They don’t have the password.
So Layer 1 catches: someone edited an existing entry.
But it leaves a hole. What if they don’t edit a line — what if they tear a whole page out? Delete entry #50 entirely. Every remaining line still has a perfect fingerprint. Nothing looks wrong… because the evidence is the thing that’s gone.
Layer 2: sealing whole pages shut
To catch missing pages, firm doesn’t just fingerprint each line. Every so often it takes a batch of lines — say entries 1 through 100 — and writes one signed seal that says: “Entries 1 to 100. This exact set. Sealed.”
Now try to tear out entry #50. The individual fingerprints are fine, but the seal over the whole range breaks, because the range isn’t what it was sealed as. Removing a page, sneaking in a fake page, shuffling the order — all of it breaks the seal.
There’s one honest exception you do want: sometimes you’re allowed to throw old entries away. Audit logs get big, and deleting last year’s records on purpose is normal (it’s called retention). So how does firm tell “legitimately cleaned up” apart from “an attacker deleted the evidence”?
It signs that too. When old entries are retired, firm records a signed floor: “Everything below entry #500 was removed on purpose, and here’s my signature to prove it was me.” An attacker can’t forge that line — no secret — so they can’t hide a deletion behind a fake “oh, that was just cleanup.”
So Layer 2 catches: someone removed, inserted, or reordered entries.
But there’s still a bigger hole. What if the attacker doesn’t tear out a page — what if they burn the whole diary? Delete the log and the seals, all of it, and start a fresh empty book. Now there’s nothing left to compare against.
Layer 3: a note kept outside the building
The fix for “burn everything” is to keep a little proof somewhere else — outside the database entirely.
firm can append a one-line note to an external file (an anchor): “As of today, I had at least 100 sealed entries, and the floor was at 500.” The file only ever grows, and it lives apart from the database.
Now if the diary comes back looking suspiciously empty — 40 entries where there should be 100 — the note outside says otherwise, and the mismatch screams tampering. You can’t erase what you can’t reach. Burning the diary no longer covers your tracks; it just moves the alarm outside.
So Layer 3 catches: someone wiped the whole log to cover it up.
Two keys, so one break isn’t game over
One more clever bit. The everyday part of your app — the code writing new lines all day — needs the secret to stamp fingerprints. But that’s exactly the code most likely to get compromised.
So firm lets you use two keys: a writing key for the busy everyday code, and a separate sealing key kept somewhere safer, used only to sign the range seals. If an attacker steals the writing key, they still can’t forge the seals that catch missing pages. One break doesn’t hand them everything.
What it actually looks like
You turn it on with a key, and firm does the stamping for you:
from firm.audit import AuditLog
# The key lives in an env var, not your code.
audit = AuditLog(database_url="postgresql://localhost/myapp")
audit.record("invoice.paid", subject=invoice, actor=user, data={"amount": 4200})
Then, whenever you want the truth, you ask firm to check the whole diary:
firm-audit verify
It reads every fingerprint, every seal, the floor, and the outside note, and tells you one of two things: everything checks out, or entry #73 was tampered with — pointing right at the problem. There’s also a dashboard with a shield that turns green when all is well and red the moment something doesn’t add up, plus an option to run that check quietly in the background on a timer.
The one thing to remember
Tamper evidence is evidence, not a force field. A person with enough access can still destroy your data — that’s true of any system. What firm takes away is their ability to do it quietly. Every edit, every missing page, every attempt to wipe the slate breaks a signature the attacker can’t fix without a secret they don’t have.
An audit log’s whole job is to be trustworthy. This is how firm earns it.
The full details — key rotation, how retention and verification agree on what’s prunable, the exact guarantees with and without the external anchor — are in the tamper-evidence docs. firm’s audit module is part of firm, the pure-Python port of the Rails Solid stack.
Stel je houdt een dagboek bij, met één regel: je mag alleen nieuwe regels toevoegen. Je gumt nooit iets uit. Dat is een auditlog — een lijst van dingen die gebeurd zijn, op volgorde, waar je voor altijd op moet kunnen vertrouwen. “Gebruiker 12 heeft de prijs gewijzigd.” “Factuur 4200 is betaald.” “Beheerder heeft een account verwijderd.”
En nu de vervelende vraag. Dat dagboek staat in je database. Heel veel mensen — en programma’s — kunnen bij die database. Wat houdt iemand tegen om het dagboek stiekem open te slaan, één regel te veranderen, en het weer dicht te doen alsof er niets is gebeurd?
Bij een gewone auditlog: niets. En dát is precies het probleem. Een log die je niet kunt vertrouwen is gewoon een verhaaltje.
De audit-module van firm heeft een optionele functie die tamper evidence (knoeidetectie) heet en dit oplost. De naam zegt het al: het houdt een vastberaden persoon niet tegen om aan je data te zitten — dat kan niemand beloven. Wat het wél garandeert, is dat áls ze dat doen, jij het merkt. Nooit meer stiekeme wijzigingen.
Dit is hoe het werkt, in drie lagen, waarbij elke laag een gat dicht dat de vorige open liet.
Laag 1: een geheime vingerafdruk op elke regel
Als je tamper evidence aanzet, geef je firm een geheime sleutel — zie het als een wachtwoord dat alleen jouw app kent.
Elke keer dat er een nieuwe regel in het dagboek komt, gebruikt firm dat geheim om er een kleine vingerafdruk naast te stempelen. Die vingerafdruk wordt berekend uit de exacte woorden van de regel plus het geheim. (De technische term is een HMAC, maar “vingerafdruk gemaakt met een geheim” is alles wat je hoeft te onthouden.)
De truc: verander één letter in de regel, en de vingerafdruk klopt niet meer. En omdat je het geheim nodig hebt om een goede vingerafdruk te maken, kan een aanvaller die een regel wijzigt er geen nieuwe bij verzinnen. Hij heeft het wachtwoord niet.
Laag 1 betrapt dus: iemand heeft een bestaande regel aangepast.
Maar er blijft een gat. Wat als ze geen regel wijzigen — maar een hele bladzijde eruit scheuren? Regel #50 helemaal verwijderen. Elke overgebleven regel heeft nog steeds een perfecte vingerafdruk. Er lijkt niets mis… omdat juist het bewijs verdwenen is.
Laag 2: hele bladzijdes dichtzegelen
Om verdwenen bladzijdes te betrappen, stempelt firm niet alleen elke regel. Zo nu en dan neemt het een batch regels — zeg regel 1 tot en met 100 — en schrijft één getekend zegel dat zegt: “Regel 1 tot 100. Precies deze set. Verzegeld.”
Probeer nu regel #50 eruit te scheuren. De losse vingerafdrukken kloppen nog, maar het zegel over het hele bereik breekt, want het bereik is niet meer wat er verzegeld werd. Een bladzijde weghalen, een valse bladzijde ertussen schuiven, de volgorde omgooien — het breekt allemaal het zegel.
Er is één eerlijke uitzondering die je juist wél wilt: soms mág je oude regels weggooien. Auditlogs worden groot, en de administratie van vorig jaar bewust verwijderen is normaal (dat heet retentie). Hoe weet firm dan het verschil tussen “netjes opgeruimd” en “een aanvaller heeft het bewijs gewist”?
Dat tekent firm ook. Als oude regels worden opgeruimd, legt firm een getekende ondergrens vast: “Alles onder regel #500 is bewust verwijderd, en hier is mijn handtekening als bewijs dat ik het was.” Een aanvaller kan die regel niet vervalsen — geen geheim — dus kan hij een verwijdering niet verstoppen achter een nep-“o, dat was gewoon opruimen.”
Laag 2 betrapt dus: iemand heeft regels verwijderd, ingevoegd of van volgorde gewisseld.
Maar er is nog een groter gat. Wat als de aanvaller geen bladzijde eruit scheurt — maar het hele dagboek verbrandt? De log én de zegels weggooien, alles, en met een vers leeg boek beginnen. Dan is er niets meer om mee te vergelijken.
Laag 3: een briefje dat je buiten het gebouw bewaart
De oplossing voor “verbrand alles” is om een klein beetje bewijs ergens anders te bewaren — helemaal buiten de database.
firm kan een briefje van één regel toevoegen aan een extern bestand (een anker): “Op vandaag had ik minstens 100 verzegelde regels, en de ondergrens stond op 500.” Het bestand groeit alleen maar, en het staat los van de database.
Komt het dagboek nu verdacht leeg terug — 40 regels waar er 100 hoorden te staan — dan zegt het briefje buiten iets anders, en die mismatch schreeuwt “hier is geknoeid”. Je kunt niet wissen wat je niet kunt bereiken. Het dagboek verbranden verbergt je sporen niet meer; het verplaatst het alarm alleen naar buiten.
Laag 3 betrapt dus: iemand heeft de hele log gewist om het te verdoezelen.
Twee sleutels, zodat één lek niet meteen game over is
Nog een slim detail. Het alledaagse deel van je app — de code die de hele dag nieuwe regels schrijft — heeft het geheim nodig om vingerafdrukken te stempelen. Maar dat is nou juist de code die het meest kans loopt om gekraakt te worden.
Daarom laat firm je twee sleutels gebruiken: een schrijf-sleutel voor de drukke alledaagse code, en een aparte zegel-sleutel die je veiliger bewaart en alleen gebruikt om de bereik-zegels te tekenen. Steelt een aanvaller de schrijfsleutel, dan kan hij nog steeds niet de zegels vervalsen die verdwenen bladzijdes betrappen. Eén lek geeft hem niet alles.
Hoe het er in de praktijk uitziet
Je zet het aan met een sleutel, en firm doet het stempelen voor je:
from firm.audit import AuditLog
# De sleutel staat in een env-var, niet in je code.
audit = AuditLog(database_url="postgresql://localhost/myapp")
audit.record("invoice.paid", subject=invoice, actor=user, data={"amount": 4200})
En wanneer je de waarheid wilt weten, vraag je firm om het hele dagboek te controleren:
firm-audit verify
Het leest elke vingerafdruk, elk zegel, de ondergrens en het briefje buiten, en zegt je één van twee dingen: alles klopt, of er is geknoeid met regel #73 — met de vinger recht op het probleem. Er is ook een dashboard met een schild dat groen wordt als alles in orde is en rood zodra er iets niet klopt, plus een optie om die controle stilletjes op een timer in de achtergrond te draaien.
Het enige dat je moet onthouden
Tamper evidence is bewijs, geen krachtveld. Iemand met genoeg toegang kan je data nog steeds vernietigen — dat geldt voor elk systeem. Wat firm wegneemt, is de mogelijkheid om het stiekem te doen. Elke wijziging, elke verdwenen bladzijde, elke poging om de boel te wissen breekt een handtekening die de aanvaller niet kan repareren zonder een geheim dat hij niet heeft.
De hele taak van een auditlog is om betrouwbaar te zijn. Zo verdient firm dat vertrouwen.
De volledige details — sleutelrotatie, hoe retentie en verificatie het eens worden over wat weg mag, de exacte garanties met en zonder het externe anker — staan in de tamper-evidence-documentatie. De audit-module is onderdeel van firm, de pure-Python port van de Rails Solid-stack.
Visit the site ↗Bekijk de site ↗ view on githubbekijk op github