PHPackages                             anvildev/craft-trails - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Logging &amp; Monitoring](/categories/logging)
4. /
5. anvildev/craft-trails

ActiveCraft-plugin[Logging &amp; Monitoring](/categories/logging)

anvildev/craft-trails
=====================

Enterprise-grade audit trail and compliance logging for Craft CMS

1.1.0(1mo ago)03[1 PRs](https://github.com/anvildevxyz/craft-trails/pulls)proprietaryPHPPHP ^8.2CI passing

Since Apr 29Pushed 1w agoCompare

[ Source](https://github.com/anvildevxyz/craft-trails)[ Packagist](https://packagist.org/packages/anvildev/craft-trails)[ RSS](/packages/anvildev-craft-trails/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (2)Dependencies (22)Versions (5)Used By (0)

Trails - Enterprise Audit Trail for Craft CMS
=============================================

[](#trails---enterprise-audit-trail-for-craft-cms)

**Enterprise-grade audit trail and compliance logging for Craft CMS 5.x**

Track every action in your Craft CMS installation. Perfect for regulated industries (healthcare, finance, legal) that require complete audit trails for compliance.

Features
--------

[](#features)

### Comprehensive Logging

[](#comprehensive-logging)

- **Element Events**: Track creation, updates, and deletion of entries, assets, users, and all element types
- **Authentication**: Log user logins, logouts, and failed login attempts with brute-force detection
- **Config Changes**: Monitor when project config changes are applied
- **Asset Operations**: Track file uploads, modifications, and deletions
- **Field-level Change Tracking**: Before/after diffs with structured comparison view (not raw JSON)
- **Custom Event API**: Third-party plugins can log events via `logCustomEvent()` with reserved-prefix protection

### Enterprise Scale

[](#enterprise-scale)

- **Monthly Table Partitioning**: Automatic rotation of the active log table to monthly archives
- **Cursor-based Pagination**: No offset degradation - fast at any page depth
- **Cross-table Queries**: Transparent querying across partitioned tables via `UnionQueryBuilder`

### Cryptographic Chain-of-Custody

[](#cryptographic-chain-of-custody)

- **Hash Chain**: Every record links to the previous via `prevHash` - insertion, deletion, or reordering breaks the chain
- **Merkle Trees**: Batched Merkle root computation (configurable, default every 256 records)
- **External Anchoring**: Anchor Merkle roots to S3 Object Lock (WORM) or RFC 3161 Timestamp Authority > External anchors are independently re-verifiable by auditors using the AWS CLI (`aws s3api head-object`) or `openssl ts -verify` - the plugin uses the same primitives.
- **Certificate of Integrity**: Export signed JSON + PDF proof documents for auditors to verify independently
- **Full Verification**: CLI + CP verification of hashes, chain links, Merkle roots, and external anchors

### Security &amp; Compliance

[](#security--compliance)

- **GDPR Compliance**: IP anonymization (/16 IPv4, /48 IPv6), email encryption at rest, auto-anonymization on user deletion
- **Integrity Hashes**: HMAC-SHA256 per record for tamper detection
- **PII Gating**: Sensitive data restricted to admin/settings-manager users
- **SSRF Protection**: External endpoints validated against all private/reserved/metadata IP ranges
- **CSV Injection Protection**: Export fields sanitized against formula injection
- **Webhook HMAC Signing**: Outgoing payloads signed with `X-Trails-Signature` + `X-Trails-Timestamp`

### Developer API

[](#developer-api)

- **REST API**: 8 endpoints at `/actions/trails/api/v1/` with dual auth (CP session + permissions, or bearer token) and rate limiting
- **GraphQL**: Full schema integration - `trailsLogs`, `trailsLog`, `trailsSummary`, `trailsIntegrity` queries
- **Fluent Query Builder**: `Trails::getInstance()->audit->query()->event('element.*')->all()`
- **Typed DTOs**: `AuditLogEntry`, `CursorResult`, `MerkleProof`, `IntegrityReport`
- **Config Overrides**: `config/trails.php` for multi-environment configuration

### Real-time CP

[](#real-time-cp)

- **htmx-driven Log Viewer**: Filter changes update the table without page reload
- **Live Polling**: 5-second auto-refresh with visual indicator (opt-in toggle)
- **Server-Sent Events**: Optional SSE for sub-second push (opt-in via config)
- **Element Timeline**: Vertical chronology of all events for any element
- **User Timeline**: Chronology of all actions by a specific user

### Reporting &amp; Export

[](#reporting--export)

- **Streaming Export**: CSV, JSON, and HTML with no artificial cap - background job for large datasets
- **Activity Dashboard**: Summary stats with filterable log table and sparkline chart
- **Dashboard Widget**: 7/14/30-day summary with sparkline + last 5 events
- **Structured Diff View**: Side-by-side field change comparison with red/green highlighting

### External Integrations

[](#external-integrations)

- **Log Shipping**: Send logs to Splunk, Datadog, S3, or generic webhooks with batching
- **IP Geolocation**: Opt-in country/region/city resolution via external API with 24h caching
- **Email Alerts**: Configurable notifications for failed login bursts, deletions, and permission changes

### Configurable

[](#configurable)

- **Selective Logging**: Choose what to track (elements, auth, config, assets, permissions)
- **Retention Policies**: Auto-cleanup via Craft GC - drops entire archive tables for instant cleanup
- **Field Exclusions**: Skip sensitive field handles from change snapshots
- **Rate Limiting**: Configurable per-second and per-token (API) rate limits
- **Permission System**: Granular permissions for viewing, exporting, and managing

Requirements
------------

[](#requirements)

- Craft CMS 5.0+
- PHP 8.2+

Installation
------------

[](#installation)

```
composer require anvildev/craft-trails
php craft plugin/install trails
```

Configuration
-------------

[](#configuration)

Visit **Settings → Plugins → Trails** or use the Trails section in the control panel sidebar.

### Settings

[](#settings)

SettingTypeDefaultDescription`enabled``bool``true`Turn logging on/off globally`retentionDays``int``0`How long to keep logs (days, 0 = forever)`scheduledRetention``bool``false`Auto-cleanup via Craft's GC mechanism`logElements``bool``true`Track element CRUD operations`logAuthentication``bool``true`Track logins/logouts`logFailedLogins``bool``true`Track failed auth attempts`logConfigChanges``bool``true`Track project config changes`logAssets``bool``true`Track asset uploads/deletions`logPermissionChanges``bool``true`Track user group/permission changes`captureIpAddress``bool``true`Store user IP addresses`anonymizeIp``bool``false`GDPR-compliant IP masking (/16 IPv4, /48 IPv6)`captureUserAgent``bool``true`Store browser user agent strings`captureFieldChanges``bool``false`Record before/after field diffs on element saves`excludedFieldHandles``array``['password', 'apiKey', ...]`Field handles excluded from change capture`maxSnapshotBytes``int``1048576`Max bytes for a single field snapshot (1 MB)`logRateLimit``int``0`Max log writes per second (0 = unlimited)`alertsEnabled``bool``false`Enable email alerts`alertEmail``string`Email address for alert notifications`failedLoginThreshold``int``5`Failed logins within 1 hour before alerting`alertCooldownMinutes``int``15`Minutes between repeat alerts`externalLoggingEnabled``bool``false`Enable external log shipping`externalProvider``string`Provider: `splunk`, `datadog`, `s3`, or `webhook``externalEndpoint``string`HTTPS endpoint URL for the provider`externalApiKey``string`API key / auth token for the provider`externalBatchSize``int``50`Logs per batch when shipping`webhookSecret``string`Shared secret for HMAC-signing webhook payloads. Supports `$ENV_VAR``enableGeoIp``bool``false`Enable IP geolocation via external API`geoIpEndpoint``string``https://pro.ip-api.com/json/`Geolocation API endpoint (HTTPS only)`merkleBatchSize``int``256`Records per Merkle tree batch (16–4096)`anchorType``string|null``null`External anchoring: `null`, `'s3'`, or `'rfc3161'``s3Bucket``string`S3 bucket for Object Lock anchoring`s3Region``string`S3 region`s3AccessKeyId``string`S3 access key (supports `$ENV_VAR`)`s3SecretAccessKey``string`S3 secret key (supports `$ENV_VAR`)`tsaUrl``string``https://freetsa.org/tsr`RFC 3161 TSA endpoint`tsaTrustedCaBundle``string`Path to a PEM CA bundle trusted by `openssl ts -verify`. Required for RFC 3161 verification unless `tsaCaBundlePem` is set`tsaCaBundlePem``string`Inline PEM for the TSA trust root(s). Used when `tsaTrustedCaBundle` is empty`enableApi``bool``true`Enable REST API`enableGraphql``bool``true`Enable GraphQL schema`apiRateLimit``int``60`API requests per minute per token (0 = unlimited)`inlineExportLimit``int``10000`Records below this threshold export inline; above triggers background job`realtime``string``'poll'`Real-time mode: `'poll'` (htmx) or `'sse'` (Server-Sent Events)`maxSseConnections``int``5`Max concurrent SSE connections (1–50)External Anchoring
------------------

[](#external-anchoring)

Trails can anchor every batched Merkle root to an external system as durable proof. Two backends ship today.

### Why external anchoring matters

[](#why-external-anchoring-matters)

The hash chain and Merkle trees inside `trails_logs` already detect tampering, but every check happens against rows the same database admin can rewrite. External anchoring closes that loop: the Merkle root is committed to a system Trails does not control (an S3 bucket with Object Lock, or a public RFC 3161 timestamp authority), and the audit-trail integrity claim becomes verifiable **by anyone outside the plugin** using widely-deployed tooling. The plugin generates the anchor; the auditor uses `aws s3api head-object` or `openssl ts -verify` to re-check independently - Trails never has to be trusted as the verifier.

### S3 Object Lock (COMPLIANCE retention)

[](#s3-object-lock-compliance-retention)

Anchors are written as small JSON manifests to an S3 bucket with versioning + Object Lock COMPLIANCE retention. COMPLIANCE retention is cryptographically enforced by AWS - neither the bucket owner nor AWS root can remove or alter a locked object until the retention window expires (Trails defaults to 7 years).

> **Object Lock COMPLIANCE retention cannot be removed.** Anything you anchor sits in the bucket for the full retention window. Use a dedicated bucket - don't co-locate anchors with deletable application data.

#### Prerequisites

[](#prerequisites)

- An AWS account you control.
- The AWS CLI v2, configured with credentials for that account: `aws sts get-caller-identity` should return your IAM user / role.
- A working Trails install with the queue worker running.

The IAM principal you use needs at minimum:

```
s3:CreateBucket
s3:PutBucketVersioning
s3:PutBucketObjectLockConfiguration
s3:PutObject
s3:GetObject
s3:GetObjectVersion
s3:HeadObject
s3:GetObjectRetention
s3:GetObjectAttributes

```

If you also want to delete the bucket after the retention window expires (years from now), add `s3:DeleteObject`, `s3:DeleteObjectVersion`, and `s3:DeleteBucket`.

#### 1. Create the bucket (versioning + Object Lock)

[](#1-create-the-bucket-versioning--object-lock)

Object Lock **must** be enabled at bucket creation - it can't be turned on later. Versioning is implicitly enabled when Object Lock is enabled, but we set it explicitly to be safe.

```
BUCKET=trails-anchors-$(date +%s)
REGION=eu-central-1

aws s3api create-bucket \
    --bucket "$BUCKET" \
    --region "$REGION" \
    --create-bucket-configuration "LocationConstraint=$REGION" \
    --object-lock-enabled-for-bucket

aws s3api put-bucket-versioning \
    --bucket "$BUCKET" \
    --versioning-configuration Status=Enabled
```

> **`us-east-1` exception**: the `--create-bucket-configuration` flag is rejected in `us-east-1` (the API treats it as the default region). Drop the flag entirely:
>
> ```
> aws s3api create-bucket --bucket "$BUCKET" --object-lock-enabled-for-bucket
> ```

Confirm the configuration:

```
aws s3api get-bucket-versioning --bucket "$BUCKET"
# → {"Status": "Enabled"}
aws s3api get-object-lock-configuration --bucket "$BUCKET"
# → {"ObjectLockConfiguration": {"ObjectLockEnabled": "Enabled"}}
```

Both must report `Enabled`. If either is missing, **delete the bucket and start over** - Trails' anchor write requires both, and a misconfigured bucket will surface as a runtime `S3 PutObject did not return ETag/VersionId - bucket likely does not have versioning enabled` error.

#### 2. Configure Trails

[](#2-configure-trails)

Configure the four S3 settings either through the CP settings page (Settings → Trails → Anchoring) or via `config/trails.php`. The config-file form is preferred for ops automation:

```
