cboxdk / laravel-audit-chain
Cbox Audit Chain: a tamper-evident, hash-chained audit trail for Laravel, with signed checkpoints and external anchoring.
Requires
- php: ^8.4
- ext-json: *
- ext-sodium: *
- illuminate/console: ^12.0 || ^13.0
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/database: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^3.5 || ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A tamper-evident, hash-chained audit trail for Laravel.
Every entry carries the SHA-256 of its own content and of the entry before it, so any later change, deletion in the middle, or reordering is detectable. Ed25519-signed checkpoints catch what a chain alone cannot (entries deleted off the end), and an anchor exports each checkpoint to storage your database's writers cannot reach.
use Cbox\AuditChain\Contracts\AuditChain; use Cbox\AuditChain\ValueObjects\ChainActor; use Cbox\AuditChain\ValueObjects\ChainEvent; use Cbox\AuditChain\ValueObjects\ChainKey; $chain = app(AuditChain::class); $key = ChainKey::of($tenant->id, 'billing'); $chain->record($key, ChainEvent::by(ChainActor::of('user', $user->id), 'invoice.voided')->on('invoice', $invoice->id)); $chain->verify($key)->valid; // false if anything recorded was changed, removed or reordered $chain->checkpoint($key); // sign the head, so truncation becomes detectable too
What you get
- Independent chains addressed by
ChainKey(partition, scope): one per tenant, per ledger, per whatever you need. - Safe concurrent appends. Parallel writers to one chain never lose an entry or share a position. Proven with eight forked writers on PostgreSQL 16, MySQL 8.4 and MariaDB 11.8.
- Signed checkpoints (Ed25519 via ext-sodium) with key rotation, and an idempotent
audit-chain:checkpointsweep. - Anchoring to any Laravel disk, including S3 and Cloudflare R2.
audit-chain:verifyfor full or windowed verification, with a non-zero exit on any break.- Contracts for everything: bring your own codec, signer, anchor, tenant context or models, including adopting a chain another implementation wrote without rewriting it.
- Least privilege: run the app as a role that can only SELECT and INSERT, with
append-only triggers behind it.
audit-chain:grantsprints the exact SQL per engine, and the setup is tested on PostgreSQL, MySQL and MariaDB. - A test fake (
FakeAuditChain,InteractsWithAuditChain) with assertions.
Honest scope
This is tamper-evident, not tamper-proof: someone who can rewrite the whole table can recompute every hash. Only checkpoints exported to a store they cannot write stop that. And the chain proves integrity, not completeness: it cannot know about an event nobody recorded. See Guarantees and limits.
Requirements
PHP 8.4+, ext-sodium, Laravel 12 or 13. See Requirements.
Installation
composer require cboxdk/laravel-audit-chain php artisan vendor:publish --tag=audit-chain-config php artisan vendor:publish --tag=audit-chain-migrations php artisan migrate php artisan audit-chain:keygen
Then read the Quickstart.
Documentation
- Overview and Quickstart
- Getting started: installation, testing
- Core concepts: architecture, hash format, checkpoints, concurrency, guarantees
- Cookbook: anchor to R2, adopt an existing chain, rotate keys, scheduling
- Extension points
- Configuration
- Security
Quality gate
vendor/bin/pint --test vendor/bin/phpstan analyse --no-progress --memory-limit=1G # level max, no baseline vendor/bin/pest composer audit --no-dev composer license-check composer sbom && git diff --exit-code sbom.json
Security
Report vulnerabilities privately through GitHub Private Vulnerability Reporting. See SECURITY.md.
License
MIT. See LICENSE.