Audit Trail (audit_trail)¶
Tamper-evident database logging for Drupal 11.3 and later.
Every entry that lands in the chain hashes its own payload together with the hash of the entry before it in the same chain, and an operator-held secret signs that hash. 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: 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 flag
(
'audit_trail' => TRUEin the PSR-3 context), ormode: autochains that capture every entry on their claimed channels automatically.mode: autoon thedefaultchain claims only the channels it explicitly names: it does NOT wildcard every other channel, so unrelated dblog noise stays out. - 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 a
AuditTrailVerifierservice to walk a chain in id order and report every contiguous broken range in a single pass, and signs checkpoints so repeat verification is incremental rather than a cold walk from genesis. - Rotates signing secrets without breaking history. Secrets are
audit_trail_secretconfig entities backed bydrupal/key, and every row records which one signed it. - Enforces retention without breaking the chain, through a five-stage segment lifecycle (transient-purge, archive to NDJSON, live-purge, file-purge, compaction) driven by cron with per-chain overrides. A purged range stays verifiable because the segment row attests the purge.
- Anchors chain heads to a qualified RFC 3161 Time-Stamping
Authority, through the
audit_trail_tsasubmodule. - Ships admin pages for entries (with a before/after diff), chains, secrets, segments and acknowledgments, and drush commands for every lifecycle and verification operation. See Verifying integrity and Configuration.
What it does not do¶
- It is tamper-evident, not tamper-proof. Anyone with the HMAC secret and database write access can forge a fresh chain from scratch. What they cannot do is rewrite the existing chain retroactively without breaking it, nor reproduce the TSA timestamps already anchored to the head they are replacing.
- It does not move archives off the machine. The archive step writes the NDJSON file and records its SHA-256; copying that file to Write-Once-Read-Many storage (S3 Object Lock, an institutional archival service) is an operator step, and it is what turns tamper-evidence into tamper-resistance.
- It does not sign exported batches with an eIDAS-qualified key. See the roadmap.
- It does not install on Drupal 12 yet. The code supports core
12 and the next-major CI lane is green, but
drupal/keyis a hard dependency and no published key release allows core 12, so composer cannot resolve the install and Drupal refuses to enablekey. The gate is key's core compatibility, not this module's.
Comparison with existing modules¶
| Module | Scope | Tamper-evident? |
|---|---|---|
Core dblog |
All log calls to DB table | No |
Core syslog |
All log calls to syslog | No (file) |
audit_log |
Entity events (Node, Taxonomy, User) to DB / pluggable | No |
audit_trail |
Selected PSR-3 calls to DB with HMAC chain | Yes |
The three approaches complement each other. audit_log answers
"what entity changed?" with a rich entity-aware UI. audit_trail
answers "did the log itself get tampered with?" with a low-level
cryptographic guarantee. They can run side-by-side.
Status¶
Under active development. Project home is
drupal.org/project/audit_trail;
the canonical repository is at
https://git.drupalcode.org/project/audit_trail. Published
documentation: project.pages.drupalcode.org/audit_trail.
Issues, merge requests and security disclosures go through the
drupal.org project page.
Documentation¶
architecture.md: chain construction, cryptographic primitives, canonical form, segment lifecycle, forensic envelope.configuration.md: chain / secret / settings configuration, retention policy, bucket granularity, filter plugins.consumers.md: guide for code that LOGS into the chain (bridges, contributors, private routing keys).verification.md: what the verifier proves and does not prove; incremental walks and signed checkpoints.commands.md: every drush command, what it does and the options that change what it does.security.md: operator deployment guidance (secret rotation, key providers, retention policy, two-tier retention model).threat-model.md: consolidated threat model for security reviewers and compliance assessors (actor matrix, defenses, non-goals, cryptographic assumptions, operator responsibilities).roadmap.md: shipped scope + post-1.0 intent.