Download the PHP package anvildev/craft-trails without Composer

On this page you can find all versions of the php package anvildev/craft-trails. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package craft-trails

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

Comprehensive Logging

Enterprise Scale

Cryptographic Chain-of-Custody

Security & Compliance

Developer API

Real-time CP

Reporting & Export

External Integrations

Configurable

Requirements

Installation

Configuration

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

Settings

Setting Type Default Description
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

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

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)

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

The IAM principal you use needs at minimum:

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)

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.

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:

Confirm the configuration:

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

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:

The $AWS_ACCESS_KEY_ID syntax uses Craft's App::parseEnv() - anything that resolves there is fine.

IAM-role deployments: when running on EC2, ECS, or EKS with an instance role / task role attached, leave s3AccessKeyId and s3SecretAccessKey empty. The AWS SDK's default credential provider chain will pick up the role automatically and you avoid baking long-lived keys into your config.

After saving the config, restart any long-running PHP workers (queue, FPM) so the new settings take effect.

3. Trigger an anchor

Trails anchors a Merkle root every merkleBatchSize audit-log writes. The full chain is audit log write → ComputeMerkleRootJob → MerkleRootRecord → AnchorMerkleRootJob → AnchorService → S3AnchorBackend → S3 PutObject → AnchorRecord.

  1. Generate audit traffic (any CP click counts: log in, edit an entry, change a setting), or run php craft trails/integrity/backfill to enqueue a BackfillChainJob.
  2. Drain the queue:

    Watch for Anchoring Merkle root #N job descriptions - that's AnchorMerkleRootJob doing its work.

  3. Confirm the anchor row landed:

    The Verifying anchors... block should report Anchors: N verified, 0 failed.

The anchor row in trails_anchors will have anchorType=s3, an anchorRef of the form s3://<bucket>/trails/merkle/YYYY/MM/DD/<hash-prefix>.json, and an anchorProof containing JSON like {"eTag":"…","versionId":"…","retainUntil":"…"}.

4. Independent auditor verification

This is what S3 anchoring buys you: an auditor can re-verify the anchor end-to-end without touching the plugin's code. Pull the JSON anchorProof from the DB row, then:

In the response, look for:

Then fetch the manifest body and confirm rootHash matches the corresponding MerkleRootRecord.rootHash in the DB:

If both checks pass, the anchor is provably (a) the same object Trails wrote, (b) locked from modification by COMPLIANCE retention, and (c) carrying a manifest whose rootHash matches the on-record root. That's the full audit chain.

Cost estimate

For a production deployment doing the default merkleBatchSize=256:

At one anchor per hour (8760/year), the annual total is about $0.05 in PUTs and $0.05 in storage. Storage at the 7-year compliance retention reaches ~12 MB - still pennies per year. The constraint on S3 anchoring is the IAM permissions and the inability to delete locked objects, not cost.

RFC 3161 Timestamp Authority

RFC 3161 anchoring submits each Merkle root to a public (or commercial) timestamp authority and stores the signed timestamp response (.tsr) as the proof. Verification re-runs openssl ts -verify against the TSA's CA chain - the timestamp's authenticity rests on the TSA's signature, not on transport security.

Prerequisites

1. Configure Trails

Set anchorType to rfc3161 and provide both the TSA URL and the CA bundle. The bundle can be supplied as either a filesystem path or inline PEM - if both are set, the path takes precedence.

These can also be set via the CP at Settings → Trails → Security → Anchoring. Restart long-running PHP workers after changing settings.

2. Trigger an anchor

The flow is identical to S3: audit-log writes accumulate into Merkle batches, AnchorMerkleRootJob posts the root hash to the configured TSA, and the response is stored as a row in trails_anchors with anchorType=rfc3161. Drain the queue with php craft queue/run, then confirm:

The anchor row's anchorRef contains the TSA URL and the anchorProof is the base64-encoded TSR (timestamp response) that the TSA signed.

3. Independent auditor verification

The auditor needs three things: the Merkle root hash being attested, the TSR file, and the TSA's CA bundle. Trails' verification path is exactly this command, and an external auditor can run the same:

Verification: OK proves the TSA signed exactly that root hash at the time embedded in the TSR. Any tamper with the row - flipping a byte in the root, swapping the TSR for a different one - changes the result to Verification: FAILED.

Trails 1.0.0 ships with real CMS/PKCS#7 verification via openssl ts -verify. The substring-match-on-output verifier from earlier development builds is gone; operators on 1.0.0 have correct verification by default and don't need to migrate anything.

Limitations

Usage

Control Panel

Access the audit trail via the Trails nav item:

Template Variable

Custom Events

Third-party plugins and custom code can log events through Trails:

Event types must follow namespace.action format (e.g., myplugin.user_synced). The namespace becomes the event category in the audit log, making it easy to filter custom events by plugin in the CP or exports.

Reserved prefixes: The following prefixes are reserved for system events and will throw InvalidArgumentException if used with logCustomEvent(): element., user., asset., config., audit., trails., auth..

Metadata size limit: The metadata array is capped at 64 KB when JSON-encoded. Larger payloads throw InvalidArgumentException.

You can also validate an event type string without logging:

Event Bridge

The Event Bridge lets any plugin register Yii events that Trails should automatically log - no need to call logCustomEvent() manually.

Registering events from your plugin

In your plugin's init() method:

The handler receives the raw Yii event and returns an array with any of:

If the handler is null, a generic log entry is created with just the event type.

Event types must use namespace.action format. Reserved prefixes (element., user., auth., trails., etc.) are rejected.

Built-in integrations

Trails ships with automatic event logging for:

These activate automatically when the respective plugin is installed. No configuration needed.

Note: Commerce and Booked event names are based on their respective public APIs. If an event constant does not exist in the installed version, it simply never fires - no errors occur.

Permissions

Assign granular permissions per user group in Settings > Users > User Groups.

Permission What it gates
trails-viewLogs View the Activity Logs page and log detail view. PII (emails, IPs, field diffs) is restricted unless the user also has trails-manageSettings or is an admin
trails-exportLogs Download log exports (CSV, JSON, HTML)
trails-manageSettings Configure all plugin settings. Also unlocks PII visibility in log detail

Admins bypass all permission checks.

Console Commands

REST API authentication modes

IP Geolocation

Trails can optionally resolve IP addresses to country, region, and city using a free external API. This is opt-in and disabled by default.

Enable it in Settings → Data Capture → IP Geolocation. Once enabled, a background queue job (ResolveGeoIpJob) is dispatched after each log write. The job calls the configured endpoint and stores the result back on the log record. Private/reserved IPs and anonymized IPs (last octet .0) are never sent to the external API.

Default endpoint

http://ip-api.com/json/ - free, no API key required, rate-limited to ~45 requests/minute per IP. For higher volume sites, configure an alternative endpoint (e.g. a self-hosted MaxMind service or ipinfo.io) via the Geolocation endpoint field.

Alternative endpoint

Any endpoint that accepts GET /{ip} and returns a JSON object in the ip-api.com format (status, countryCode, regionName, city) is compatible. Update the endpoint field:

Geo data in logs

Resolved country (2-letter ISO code), region, and city appear in the User Information section of each log detail view. The data is stored in three new columns (country, region, city) added to the trails_logs table by the migration m260405_000000_AddLogGeoColumns.

Webhook Signature Verification

When a Webhook Secret is configured in Settings → External Log Shipping, Trails signs every outgoing webhook payload with HMAC-SHA256. Two headers are added to each request:

Header Value
X-Trails-Signature sha256=<hex-digest>
X-Trails-Timestamp Unix timestamp (seconds) at send time

The signature is computed over the string {timestamp}.{body} - the dot-joined concatenation of the timestamp and the raw JSON body. Tying the timestamp to the signature prevents replay attacks.

PHP verification example

The webhookSecret setting supports $ENV_VAR syntax so the secret can be stored outside project.yaml.

Troubleshooting

Plugin enabled but no logs appearing

Symptom. enabled = true in settings, CP activity is happening, but trails_logs stays empty (or grows much slower than expected).

Diagnosis. Check the capture flags and exclusions:

Then check the rate-limit setting - logRateLimit silently drops events beyond N per second:

Fix. Enable the right capture flags. Confirm logRateLimit isn't 0 if you set it as a cap (note: the default 0 means unlimited, but a misconfigured low value silently throttles). If the queue is the bottleneck, drain it with php craft queue/run and address the underlying worker health.

Queue jobs piling up

Symptom. php craft queue/info reports a growing backlog of ShipLogJob or BatchShipLogJob.

Diagnosis.

The most common cause is an unreachable external shipping endpoint: ShipLogJob retries on failure with exponential backoff and pile up while the endpoint is down. BatchShipLogJob follows the same retry pattern at the batch level.

Fix. Verify the configured externalEndpoint is reachable from the worker host (curl -I <endpoint>). If the endpoint is temporarily unavailable, set externalLoggingEnabled = false until it's restored - the audit log itself is unaffected; only shipping is paused.

Hash chain reports broken links after a user deletion

Symptom. php craft trails/integrity/verify reports broken chain links at row positions corresponding to a recently deleted user.

Diagnosis.

If the broken positions correlate with user.gdpr.anonymized events, this is expected behaviour, not tampering.

Fix. This is documented in DEVELOPER_GUIDE.md → GDPR & Hash Chain Integrity. The local hash chain is intentionally invalidated when PII is rewritten on Article 17 erasure; the integrity guarantee is preserved by the external Merkle anchors which were computed before anonymization. Direct auditors to the developer guide section for the full trade-off explanation.

Anchor verification failing for newly-created anchors (RFC 3161)

Symptom. New trails_anchors rows with anchorType=rfc3161 report verified=0 and php craft trails/integrity/verify flags them.

Diagnosis.

Then check storage/logs/web-YYYY-MM-DD.log for Trails warnings of the form RFC 3161 verification: no CA bundle configured or openssl ts -verify failed.

Fix. The most common cause is a missing or unreachable CA bundle - verify() returns false and logs a warning if both tsaTrustedCaBundle and tsaCaBundlePem are empty, or if the configured path isn't readable by the PHP process (filesystem permissions, container mount missing, etc.). Set the bundle correctly and re-run verification. If the TSA's CA chain has expired, fetch a current bundle from the TSA's website.

Anchor verification failing for newly-created anchors (S3)

Symptom. New trails_anchors rows with anchorType=s3 report verified=0.

Diagnosis.

Fix. verify() returns false silently if any check fails - check storage/logs/web-YYYY-MM-DD.log for the specific Trails warning that pinpoints which check (ETag mismatch, retention downgrade, missing version, AccessDenied). ETag or retention mismatches on a COMPLIANCE-locked object indicate an audit-trail violation worth investigating, not a config issue.

php craft trails/integrity/verify flags many anchors as legacy

Symptom. Verification output reports a large number of anchors as legacy / unverifiable.

Diagnosis.

A non-zero count is the legacy hex-HMAC proof format from pre-1.0.0 development builds. These rows can't be third-party-verified because the proof is a local HMAC, not an external attestation.

Fix. Re-anchor: delete the row from trails_anchors and push a fresh AnchorMerkleRootJob for the corresponding merkleRootId. There is no built-in batch CLI for this in 1.0.0 - a --re-anchor console action is on the roadmap. For now, re-anchor manually:

What Makes This Enterprise-Grade

Capability What it does Why it matters
Hash chain Every record links to the previous via prevHash Proves no records were inserted, deleted, or reordered
Merkle trees Batched cryptographic roots over groups of records Efficient single-record verification without scanning all records
External anchoring Roots stored in S3 Object Lock or RFC 3161 TSA Even a DB admin can't rewrite history without the anchor disagreeing
Certificate of Integrity Signed JSON + PDF export for auditors Independent verification without access to the Craft site
Table partitioning Monthly archive tables with cross-table queries Scales to millions of records without query degradation
Streaming export Background job with progress tracking Export datasets of any size without browser timeouts
REST API + GraphQL Programmatic access with rate limiting Headless Craft sites, SIEM integration, custom dashboards

Developer Setup

This section is for contributors and CI — production users don't need any of it.

Running tests

Smoke testing the integrity surface

bin/smoke-test-integrity.sh runs a live end-to-end sweep across the integrity verify panel, certificate generation, the ChainLinkValidator matrix, S3 + RFC3161 anchor flows, mixed-anchor verification, and chain backfill. Designed to run in under a minute against a DDEV stack.

The runner takes a DDEV DB snapshot before any destructive case and restores on exit. Evidence saved under .smoke-test-evidence/<timestamp>/. Reads CRAFT_SECURITY_KEY from .env. Exit code = number of failed assertions (0 = green).

The S3 test (test 32) auto-skips when no S3-compatible endpoint is reachable, so the runner is safe to use without MinIO.

Optional: MinIO for local S3 anchor testing

To exercise the S3 anchor backend without an AWS account, install the official DDEV MinIO add-on:

Then add a .ddev/docker-compose.minio_snmd_override.yaml so MinIO runs in single-node multi-drive mode (single-drive filesystem mode does not support Object Lock, which the anchor flow requires):

Restart, then create a bucket with Object Lock enabled:

Configure trails to use it (in config/trails.php):

MinIO console: https://<your-ddev-url>:9090/login (login ddevminio / ddevminio). Useful for inspecting anchor manifests, verifying COMPLIANCE retention, and debugging anchor flow issues.

Full setup steps and negative paths are documented in the smoke test plan at docs/smoke-tests/plugins/trails/tests/32-anchor-s3.md (in the parent project), including how to switch between MinIO and real AWS S3.

Test fixtures

License

Proprietary - see LICENSE.md

Support


All versions of craft-trails with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
craftcms/cms Version ^5.0
aws/aws-sdk-php Version ^3.300
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package anvildev/craft-trails contains the following files

Loading the files please wait ...