Audit Log + Hash Chain
Every Grit project records who did what — automatically. Each authenticated mutation is appended to a SHA-256 hash chain that makes the log tamper-evident: change one row in the database and the chain no longer verifies. This is the audit trail auditors actually trust.
Reference docs: Security Guide →
Why audit logs matter
Compliance, forensics, and accountability — who changed what, when, and from where.
How the hash chain works
Each entry seals the one before it, so any edit or deletion breaks the chain.
Reading the trail
Filter activity by user, action, and resource straight from the admin panel.
Proving integrity
Verify the entire chain with a single GET request and surface tampering instantly.
What is an audit log?
An audit log is an append-only record of meaningful changes in your system. Unlike application logs (which are for debugging), an audit log is for accountability — it answers “who created this invoice,” “who deleted that user,” and “when did the price change.”
In Grit, audit logging is automatic. A middleware records every authenticated mutating request (POST, PUT, PATCH, DELETE) into an activity_logs table — no per-handler code required.
How the hash chain works
Each log entry stores a hash computed from the entry's contents plus the previous entry's hash. That links every row to the one before it, like blocks in a blockchain. Recompute the chain and a single altered row invalidates every hash after it.
hash(n) = SHA256(hash(n-1) + payload(n)). Because hashes are one-way and collision-resistant, you can't edit a past entry without redoing every hash that follows — which the verifier will catch.// Pseudocode for the chain seal Grit computes per entry
entry.PrevHash = lastEntry.Hash
payload := entry.UserID + entry.Action + entry.Resource +
entry.ResourceID + entry.Timestamp + entry.PrevHash
entry.Hash = sha256(payload) // stored on the rowReading the trail
The admin panel exposes the activity log with filters for user, action, and resource. Each row captures the actor, the HTTP method + path, the target resource, the timestamp, and the request's public IP.
a3f1...c92 maya@acme.com POST /api/invoices 192.0.2.10
b9d2...44e jb@grit.dev PUT /api/users/42 198.51.100.7
c1e7...8af maya@acme.com DELETE /api/blogs/9 192.0.2.10Challenge: Generate some activity
Log in to the admin panel and create, update, then delete a record. Open the Activity page. Do you see three new entries? What actor, action, and resource does each show?
Proving integrity
Verification re-walks the chain from the beginning, recomputing each hash and comparing it to what's stored. If every entry matches, the log is intact. If one doesn't, you get the exact entry where the chain broke.
# Returns { "ok": true } when the chain is intact,
# or the first entry id where verification failed.
GET /admin/activity/integrity
Authorization: Bearer <admin JWT>ok: false. That turns a passive log into an active tripwire.Challenge: Break the chain on purpose
Open GORM Studio at localhost:8080/studio and edit a single field on an old activity_logs row. Now call /admin/activity/integrity. Does it report the tampering? Which entry id does it flag?
Challenge: Schedule a daily check
Add a cron entry that hits the integrity endpoint every night. Where would you send an alert if it fails? Sketch the flow.
Challenge: Map it to a compliance control
Pick SOC 2, HIPAA, or PCI-DSS. Name one specific control the hash-chained audit log helps you satisfy, and explain why “tamper-evident” matters for it.
Enjoying the course?
Help us grow, star us on GitHub, subscribe on YouTube, and follow on LinkedIn.
