Hash-chained audit log for GDPR: how it works and how to build one
In short
A hash-chained audit log records every state-changing action in your application in an append-only sequence where each entry's hash includes the hash of the previous entry. This makes retroactive alteration detectable: changing any historical record breaks the chain from that point forward. For GDPR, it provides a tamper-evident record of data access, modification, DSAR fulfilment, and consent changes — which is useful for demonstrating compliance to a data protection authority. Alleex Cloud generates a hash-chained audit log into every app, with chain heads witnessed in Sigstore Rekor so third parties can verify integrity without access to your database.
What this means
What this means in practice
An audit log records what happened, when, and who initiated it. A hash-chained audit log goes further: each row contains a cryptographic hash that includes the content of that row and the hash of the previous row. This creates a chain where any modification to a historical record — changing a timestamp, altering a data field, deleting a row — produces a hash mismatch from that point forward. The chain is broken in a detectable way. For GDPR accountability (Article 5(2)), being able to demonstrate that your access logs and DSAR records have not been tampered with is a significant compliance asset. It also satisfies common enterprise due-diligence requirements for audit trails in regulated industries.
How Alleex Cloud handles this
How Alleex Cloud handles this
Every Alleex Cloud app includes a hash-chained audit log implemented as an append-only Postgres table with a server-side trigger. The trigger runs on every INSERT to the `audit_events` table — it computes SHA-256 over the new row's content concatenated with the previous row's hash, and stores the result in a `chain_hash` column. The trigger is defined at the database level, not application level, so it cannot be bypassed by application code bugs or accidental omission. Chain heads — the hash of the most recent row — are periodically submitted to Sigstore Rekor, a public, append-only transparency log. Rekor returns a signed inclusion proof (a log entry with a globally unique ID). This means that any third party — your auditor, a regulator, or your customer — can verify the chain head against the public Rekor log without access to your database. The audit log is exportable as JSON or NDJSON including the Rekor index, available in every Alleex Cloud app at every pricing tier.
Step by step
Step-by-step
- 1
Design the audit_events table schema
Create an append-only table with columns: `id` (UUID), `event_type` (text), `actor_id` (UUID or null for system), `subject_id` (UUID or null), `payload` (JSONB — the data that changed), `created_at` (timestamptz), `prev_hash` (text — the chain_hash of the previous row), `chain_hash` (text — computed hash of this row). Add a partial index on `created_at` descending for efficient tail queries.
- 2
Implement the chain trigger in Postgres
Write a `BEFORE INSERT` trigger function that: (a) fetches the `chain_hash` of the most recent row (or a genesis constant for the first row), (b) computes `SHA-256(event_type || actor_id || payload::text || prev_hash)`, (c) sets `NEW.prev_hash` and `NEW.chain_hash` before the insert completes. This runs inside the same transaction as the inserting query — chain integrity is guaranteed even under concurrent inserts when combined with a sequence or advisory lock.
- 3
Insert audit events from every write path
Wrap every state-changing operation (user data updates, login events, DSAR fulfilment steps, consent changes) with an `insertAuditEvent(type, actorId, subjectId, payload)` call inside the same database transaction. Never insert audit events outside a transaction — a failed business operation should not produce an audit entry.
- 4
Verify chain integrity on demand
Write a verification function that reads all rows in `chain_hash` order and recomputes each hash, comparing it to the stored value. Run this on a schedule and alert if the chain is broken. Expose a read-only chain-verify endpoint for your auditor.
- 5
Submit chain heads to Sigstore Rekor
On a schedule (e.g., every hour), fetch the current chain head hash and submit it to Rekor via the Rekor client library. Store the returned Rekor log index alongside your chain head. To verify: given a chain head and a Rekor log index, any party can call the Rekor public API to confirm the inclusion proof without accessing your database.
Common questions
Frequently asked questions
- Is a hash-chained audit log required by GDPR?
- GDPR does not mandate a specific technical implementation for audit logs. Article 5(2) requires that you be able to demonstrate compliance (accountability principle). A tamper-evident audit log is a strong technical measure for that demonstration, and it is commonly expected in enterprise security reviews and DPA audits — but the specific implementation is your choice.
- What is Sigstore Rekor and why does it matter?
- Sigstore Rekor is a public, append-only transparency log for software supply chain artefacts, operated by the Linux Foundation. By submitting your audit chain heads to Rekor, you create a public, verifiable record that the chain head existed at a specific time — without revealing the content of your audit log. A third party can verify the timestamp and hash independently, which strengthens the non-repudiation property of your audit trail.
- Can someone with database admin access modify the audit log anyway?
- A database admin with direct access can modify the audit table — no purely software-layer solution prevents this. The Rekor witness addresses this: once a chain head is submitted to Rekor, any modification to earlier records that changes the chain head would produce a mismatch against the Rekor-attested value. The audit log cannot be silently rewritten without the tampering being detectable via Rekor.
- Does the audit log affect database performance?
- The trigger adds one read (fetch previous hash) and one hash computation per audit event INSERT. For typical web app write volumes, this is negligible. The audit_events table should be indexed on `created_at` only — do not add indexes on `chain_hash` or `prev_hash` as these are write-path columns. Partition the table by month if event volume is high.
Every Alleex Cloud app ships with a tamper-evident audit log
Hash-chained, Sigstore Rekor-witnessed, exportable as JSON. Available on every pricing tier — not an enterprise upsell.