Who Changed It/Docs/Core concepts
How tamper detection works, what an authorised gap is, and how to verify a handed-over export without WordPress.
An audit log is only worth as much as your confidence that it has not been edited. Anyone with database access can normally delete the row that incriminates them, and nothing about the remaining table would look wrong. From version 0.6.0 the log defends against exactly that.
Every record carries three hashes:
Because each record_hash folds in the one before it, editing or removing any row breaks every link after it. The verifier walks the chain and reports precisely what broke: rows altered, rows missing, links broken.
Deleting rows is sometimes legitimate — retention purges, manual purges, GDPR erasure. If those simply removed rows, the chain would break and every legitimate cleanup would look like an attack.
Instead, an authorised removal records a gap: the range removed, how many records it covered, the record_hash of the last row in the removed span, and the reason. The surviving row's prev_hash can then still be matched across the hole, so the chain verifies. Anything that removes rows without recording a gap — a hand-written DELETE, a database restore, a row edited in phpMyAdmin — is what "flagged" means.
The chain detects tampering by anyone who cannot compute the HMAC. Where the key lives therefore decides what the chain is worth:
// wp-config.php - set this before you accumulate records you care about.
define( 'WHOCHITA_CHAIN_KEY', 'a-long-random-string-kept-out-of-the-database' );Whether the key is held outside the database is recorded in every export's manifest, as key_held_outside_database, so whoever receives the file can judge the strength of what they are holding.
Changing the key after records exist invalidates the existing chain — the old hashes were computed with the old key. Set it early, keep it backed up with your other secrets, and treat losing it as losing the ability to prove the history.
Verification status is shown on the log screen and on the settings page. It reports how many records were checked, and counts of altered rows, missing rows, broken links and authorised gaps. A healthy log reports zero of the first three, and however many gaps your retention policy has legitimately created.
Exports are not just a dump of rows. Every export carries a manifest describing what was asked for and what came back, and the whole thing is covered by one HMAC-SHA256 signature. The manifest records:
Because records_digest is inside the signed manifest, neither the records nor the stated bounds can be altered without invalidating the signature. That is what makes a filtered export meaningful to a third party: they can see it was a filtered view, and see exactly which filter.
Verification needs no WordPress and no plugin — only the file and the site's chain key:
The algorithm block is repeated inside every manifest, so the file explains how to check itself years later without reference to this page.
Worth being precise, because "tamper-proof" gets used loosely. A verified chain proves that the records are internally consistent and that nothing was removed or altered without leaving a mark, given that whoever produced it did not hold the key. It does not prove the events themselves were recorded correctly, it cannot see activity that never passed through WordPress, and it is not a substitute for backups. It raises the cost of quietly rewriting history from trivial to high, which is the honest claim.