DEV Community

Vasileios Ntoufoudis
Vasileios Ntoufoudis

Posted on

I Built a Verifiable Audit Log for Laravel

Most Laravel applications record important events:

  • user logins
  • orders created
  • payments processed
  • settings changed

These events are usually stored in an activity log table.

Something like this:

id | action | user_id | created_at

At first glance, this seems fine.

But there’s a serious problem.


Audit Logs Can Be Modified

In most applications, audit logs are just database records.

That means they can usually be:

  • modified
  • deleted
  • reordered

If someone with database access changes a record, there’s often no reliable way to detect it.

For many systems this is unacceptable.

Especially for:

  • financial systems
  • healthcare platforms
  • security-sensitive applications
  • compliance reporting

These systems need tamper-detectable audit trails.


The Idea: Turn Logs into a Ledger

Instead of recording events as simple database records, I built a system that records them in a cryptographically verifiable ledger.

I called it Chronicle.

Chronicle is an append-only audit ledger for Laravel.

Each entry is linked to the previous one using a hash chain.

If any entry is modified or removed, the ledger verification fails.


Recording an Event

Using Chronicle looks like this:

use Chronicle\Facades\Chronicle;

Chronicle::record()
    ->actor($user)
    ->action('order.created')
    ->subject($order)
    ->metadata([
        'amount' => 5000,
        'currency' => 'USD',
    ])
    ->commit();
Enter fullscreen mode Exit fullscreen mode

Each entry records:

  • who performed the action
  • what happened
  • what entity was affected

Entries are immutable once recorded.


Hash Chaining

Chronicle protects the ledger using a hash chain.

Each entry references the previous entry:

chain_hash(n) = SHA256(chain_hash(n-1) + payload_hash(n))

This creates a cryptographic chain across the entire audit history.

If any entry is:

  • modified
  • deleted
  • reordered

the chain verification fails.


Detecting Tampering

Chronicle includes a verification command:

php artisan chronicle:verify

If the ledger was tampered with, verification fails.

This allows systems to detect unauthorized changes to audit logs.


Exporting a Verifiable Dataset

Chronicle can export the ledger as a verifiable dataset.

php artisan chronicle:export
Enter fullscreen mode Exit fullscreen mode

Exports include:

entries.ndjson
manifest.json
signature.json
Enter fullscreen mode Exit fullscreen mode

The dataset contains:

  • a dataset hash
  • a digital signature
  • ledger boundaries

This allows the exported audit log to be verified independently.


Querying the Ledger

Chronicle includes query helpers for retrieving entries:

use Chronicle\Models\Entry;

Entry::forActor($user);

Entry::forSubject($order);

Entry::action('order.created');
Enter fullscreen mode Exit fullscreen mode

It also supports:

  • cursor pagination
  • streaming large ledgers
  • indexed queries

This makes it practical to use in real systems.


Why I Built This

Many applications rely on audit logs to answer questions like:

What happened?

But traditional logs can’t always answer a more important question:

Can we prove what happened?

Chronicle attempts to solve that problem.

Instead of storing events as editable records, it records application history as a ledger.


Open Source

Chronicle is open source and available on GitHub:

https://github.com/laravel-chronicle/core

If the idea sounds interesting, feel free to check it out.

Feedback and contributions are welcome.

Top comments (0)