audit_trail
Tamper-evident database logging for Drupal 11+.
Every entry that lands in the chain embeds an HMAC of its own payload plus the SHA-256 hash of the previous entry in the same chain. Insert, modify or delete a record after the fact and the chain breaks at every entry downstream; verification pinpoints the first broken row, and the alteration is provably detectable.
Why
Drupal's dblog module records every event you tell it to, but the rows are plain database writes. Anyone with database write access (a sysadmin, a compromised app account, an attacker who escalated through some other vulnerability) can edit a row, drop a row, or insert a fake row without a trace. For most sites that is acceptable. For audit-worthy applications such as anything subject to regulated retention (notarial acts, financial transactions, medical records), or any system that needs a defensible answer to "who did what, when, with what content", it is not.
audit_trail adds a cryptographic chain on top of selected log entries. It does not stop tampering (no logging mechanism can do that without external storage) but it makes tampering provably detectable, which is the property regulators and auditors care about.
What it does
- Plugs into Drupal's standard logger pipeline (PSR-3 + the
loggerservice tag), so any module that calls\Drupal::logger($channel)->info(...)can land in the chain without code changes. - Chains entries selectively based on either a per-call
'chain' => TRUEflag in the PSR-3 context, ormode: autochains that capture every entry on their claimed channels automatically. - Stores each chained entry in a dedicated
audit_trailtable with a publicly-verifiable SHA-256 hash chain plus an operator HMAC layer. - Groups entries by chain id (defaults to channel; can map multiple channels into one chain via an
audit_trail_chainconfig entity) so verification is independent per chain. - Exposes an
AuditTrailVerifierservice to walk a chain in id order and report every contiguous broken range in a single pass. - Submodules add tamper-evident logging for entity CRUD, file operations, user-authentication events, paragraph entities, and RFC-3161 timestamping for external trust anchoring. A sibling
audit_trail_webdavmodule bridges the WebDAV contrib module's events into the chain.
Two-tier retention and lifecycle
Each row carries context in two separately-retained tiers. Permanent context (operator-attested, PII-free metadata: action verbs, resource ids, structural flags) is signed raw and kept for the chain's full retention. Transient context (operational payload: before/after diffs, IP addresses, request URIs) is hash-signed so the bytes themselves can be NULLed at short retention without breaking chain verification: the GDPR-respectful "purge-the-bytes, keep-the-hash" pattern.
A cron-driven lifecycle then moves rows through four stages. First live; then archived, exported to an NDJSON file under a three-layered HMAC plus a file SHA-256; then live-purged, with the live row deleted and the archive file remaining for queryability and restore; finally file-purged, with the NDJSON unlinked and segment bookkeeping retained alongside anchor hashes so the verifier still bridges across the now-empty range. Transient-purge runs ahead of archive on the same tick, so the archive file never seals expired transient bytes into long retention.
What it does not do
- It is tamper-evident, not tamper-proof. Anyone with the HMAC secret plus database write access can forge a fresh chain from scratch, but they cannot rewrite the existing chain retroactively without breaking it. Pair with external WORM storage (S3 Object Lock, Vault transit) and qualified RFC-3161 timestamps on batch boundaries for legally opposable archival.
- It is not at-rest encryption. The chain provides integrity, not confidentiality; layer DB-side encryption if sensitive data lands in audit rows.
- It is not a completeness guarantee. The chain protects what reaches it; if a call site bypasses the orchestrator the event never enters the chain.
Status
Under active development. Architecture, threat model, configuration, and verification docs live alongside the code at git.drupalcode.org/project/audit_trail.
Note
The current code base is a complete rewrite with no upgrade path from the prior 7.x module. The original module (created by kevin hankens in 2012) tracked Drupal 7 form submissions and form-element changes via regexp pattern matching, with hooks such as hook_audit_trail_form_change_log_alter. The current version is a Drupal 11 tamper-evidence primitive built on PSR-3 logging with an HMAC chain. Different design, different problem space, different audience.