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=0

Three 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 stored row_hash values 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_through and seal_intact alongside ok / broken_at. The shape already exists in miniature — verify() skips rows with a NULL or empty row_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

jmcerda created an issue. See original summary.

jmcerda’s picture

Version: 1.0.2 » 1.x-dev

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

jmcerda’s picture

Status: Active » Needs review

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

jmcerda’s picture

Version: 1.x-dev » 1.3.0
Status: Needs review » Fixed

Fixed 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

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.