Problem/Motivation
Dual-venue record for GitHub #5.
On a real deployment, audit-chain:verify reported the chain broken at row 1. Walking all 2,002 rows and testing each against the configured key and against no key gave:
total=2002 keyed=0 unkeyed=1997 neither=5 nohash=0 discontinuous=0Three things follow. discontinuous=0 with no gaps in ids 1..2002 means nothing was inserted, deleted or reordered — the chain was never tampered with. keyed=0 means no row verifies under HMAC: the key was unresolvable for the whole period those rows were written (see #3613960). The remaining 5 are a separate defect where the row content was destroyed on write.
Those 1,997 rows are not recoverable: the key they were written without does not exist.
Re-chaining is not the fix. Recomputed hashes prove only that the recompute ran, and would also silently repair a chain that had been tampered with beforehand — the one thing this module exists to make impossible.
Proposed resolution
Seal, do not repair.
- A genesis anchor recording
sealed_through_id, the row count, a digest over the storedrow_hashvalues of the sealed prefix, the timestamp, the operator and the reason — itself HMAC'd with the current key, so the seal is tamper-evident going forward. Sealing proves nothing about the past; it makes the boundary explicit and any future change to the prefix detectable. verify()returns more:verified_from,sealed_throughandseal_intactalongsideok/broken_at. The shape already exists in miniature —verify()skips rows with a NULL or emptyrow_hash— so a sealed segment generalises existing behaviour rather than introducing an exception.- The exit-code contract is the delicate part. Exit 0 when every post-seal row verifies and the seal digest still matches; non-zero when a post-seal row fails or the sealed prefix changed since sealing. That must stay loud.
- Sealing may only cover rows that already fail verification. Without that constraint, sealing is a tamper-hiding tool. The seal operation must write its own audit entry.
- Split the dashboard signal into live-chain integrity versus sealed historical segment, so a permanent unclearable critical stops causing alarm fatigue and masking a real one.
- Anchor the head externally going forward, so a full-table rewrite is detectable rather than self-consistent.
Remaining tasks
Note the premise has shifted since filing: with #3613960 fixed, verify() now reports those rows as written-unkeyed rather than as tampering, so the chain is no longer reported broken at row 1. Sealing becomes a way of recording the boundary rather than a rescue. Re-scope accordingly.
Not decided here: who signs the genesis anchor, and when the seal runs against production. Sealing is a one-way door on the audit record and is the operator's call.
Comments
Comment #2
jmcerdaQueue disposition: still design / not implemented. GitHub companion #5.
Related Closed (fixed) on 1.1.0: unkeyed diagnosis (#3613960) and UTF-8 row destruction (#3614138). Sealing covers historical unkeyed/unreadable prefixes; no implementation yet.
Leaving Active until the seal design is ratified and scheduled.
Comment #3
jmcerdaNeeds review: implemented in GitHub PR #11 (companion #5).
drush audit-chain:seal --through=ID --reason="…"records a keyed digest over stored row hashes for an unverifiable prefix.verify()content-checks only post-seal rows and reports sealed_through / seal_intact / verified_from. Editing a sealed hash fails as seal_broken. Sealing a still-verifiable row is refused.Awaiting human merge.
Comment #4
jmcerdaFixed in 1.3.0.
- drush audit-chain:seal --through=ID --reason="…" records a keyed digest over stored row_hash values for a historical unverifiable prefix
- verify() reports sealed_through / seal_intact / verified_from; exit 0 when the seal is intact and post-seal rows verify
- Sealing still-verifiable rows is refused; post-seal edits to sealed hashes fail as seal_broken
- Status report WARNING when a seal is active
GitHub: https://github.com/Wilkes-Liberty/audit_chain/issues/5
PR: https://github.com/Wilkes-Liberty/audit_chain/pull/11
Release: https://www.drupal.org/project/audit_chain/releases/1.3.0