Files
TREK/wiki/Audit-Log.md
T
Maurice 38f3cb3101 fix(server): the second pass over this branch's own fixes
An adversarial review of what had already landed here turned up three that
were wrong, and they are the reason for this commit.

The CSP change removed 'unsafe-eval', which is not leftover: heic-to's
libheif build initialises embind through new Function(), and without it every
iPhone .heic upload fails in the browser. 'wasm-unsafe-eval' alone was tried
once already and was not enough (93b51a0b). Put back, with the reason written
down this time and the test pinning it rather than its absence.

The idempotency in-flight guard never fired. finalize() runs when the
handler's observable completes, but Nest writes the response - and therefore
stores the row - several microtasks later, so the waiting request always woke
up before the row existed and ran the handler a second time. It releases from
the point the row is written now; finalize is the backstop for handlers that
answer another way. The test drove the two in the opposite order from Nest,
which is why it passed either way.

And the trip-scoping check treated an id that resolves to nothing like one
belonging to someone else. accommodation_id has no foreign key, so shortening
a trip's dates leaves a booking pointing at a gap - which update() has always
healed by clearing the link, and which would have become a permanent 400.

The rest: a reply to a soft-deleted chat message is refused and the create
path blanks the quote like the list path does; the ws-token buckets are keyed
by account rather than by IP, where every user behind one NAT shared a
ceiling; the weather and vacay caps read through the stream, since a chunked
response has no content-length to check; the Google host gate takes country
domains again; abandoned response bodies are cancelled instead of left to GC;
the boot failure prints its errno; and a share link that is not yours to read
now says so instead of reporting that the journey has none.

TICKETJSON gets a real repair: the in-place GLOB fix only helps installs that
have not run it yet, so an appended migration puts back the note text the
case-insensitive LIKE cut off, for rows where what it stored is not valid
JSON. A genuine receipt always parses, so it cannot be touched.

Wiki: the audit log records the IP Express resolves after TRUST_PROXY, not
the leftmost X-Forwarded-For entry a caller can write. That is the right way
round, but it means the hop count has to be accurate - said plainly in both
places now.
2026-08-23 11:59:12 +02:00

6.5 KiB

Audit Log

The audit log records significant actions taken on your TREK instance. Use it to monitor logins, admin changes, and integration configuration.

Where to find it

Admin Panel → Audit tab.

Audit log

What the log captures

Actions are grouped by area below. The Action key is the raw value stored in the log.

Authentication

Action key Description
user.register User registered
user.login User logged in
user.login_failed Login attempt failed
user.password_change User changed their password
user.account_delete User deleted their account

MFA

Action key Description
user.mfa_enable MFA enabled on an account
user.mfa_disable MFA disabled on an account

Trips

Action key Description
trip.create Trip created (includes title)
trip.update Trip updated (includes changed fields)
trip.copy Trip duplicated (includes source and new trip IDs)
trip.delete Trip deleted (includes trip ID and title)

Admin actions

Action key Description
admin.user_create User created by admin
admin.user_update User edited by admin (role, email, username, etc.)
admin.user_delete User deleted by admin
admin.invite_create Invite link created
admin.invite_delete Invite link deleted
admin.permissions_update Instance permissions updated
admin.oidc_update OIDC/SSO settings updated
admin.addon_update Addon enabled, disabled, or configured
admin.oauth_session.revoke OAuth session revoked by admin
admin.rotate_jwt_secret JWT secret rotated
admin.bag_tracking Bag tracking feature toggled
admin.places_photos Places photos feature toggled
admin.places_autocomplete Places autocomplete feature toggled
admin.places_details Places details feature toggled
admin.collab_features Collaboration features updated
admin.packing_template_delete Packing template deleted
admin.default_user_settings_update Default user settings updated
admin.demo_baseline_save Demo baseline snapshot saved
settings.app_update App settings updated (SMTP, webhooks, MFA policy, etc.)

Backups

Action key Description
backup.create Manual backup created
backup.restore Restore from stored backup
backup.upload_restore Restore from uploaded ZIP
backup.delete Backup deleted
backup.auto_settings Auto-backup schedule saved

MCP

Action key Description
mcp.tool_call MCP tool invoked (resource = tool name)

OAuth

Action key Description
oauth.client.create OAuth client application created
oauth.client.rotate_secret OAuth client secret rotated
oauth.client.delete OAuth client application deleted
oauth.consent.grant User granted OAuth consent
oauth.token.issue OAuth access token issued
oauth.token.refresh OAuth access token refreshed
oauth.token.revoke OAuth token revoked
oauth.token.grant_failed OAuth token grant attempt failed
oauth.token.client_auth_failed OAuth client authentication failed

Integrations

Action key Description
immich.private_ip_configured Immich URL saved that resolves to a private IP

Log columns

Column Description
Time Timestamp of the action
User Username and email of the acting user (or anonymous for unauthenticated events)
Action Action key (see tables above)
Resource Affected resource (filename, trip ID, tool name, etc.) where applicable
IP Client IP address
Details Additional context in JSON format

Pagination

The panel loads 100 entries at a time by default. Click Load more at the bottom to fetch the next page. The total count is shown above the table.

IP addresses

The client IP is the one Express resolves after applying TRUST_PROXY, not whatever the X-Forwarded-For header happens to say. That distinction matters: the header is written by the caller, and an audit row an attacker can address to someone else's IP is worse than no row at all.

TRUST_PROXY is the number of proxy hops in front of TREK, and it has to be accurate. With TRUST_PROXY=1 (the default) TREK trusts exactly one hop, so a request that passed through two proxies is recorded as coming from the inner one. If your setup is Cloudflare in front of nginx in front of TREK, set TRUST_PROXY=2. Set it to 0 to trust nothing and always record the socket address.

See Environment-Variables.

Log file

In addition to the database, audit events are written to a plain-text log file:

  • Path: ./data/logs/trek.log
  • Rotation: rotated when the file reaches 10 MB
  • Retention: the 4 most recent rotated files are kept (trek.log.1 through trek.log.4)

Database retention

Audit entries in the database are never automatically deleted. They accumulate and are paginated in the UI.

Plugin capability audit

Separate from the instance audit log above, TREK keeps a dedicated hash-chained capability audit for installed plugins. Every host-mediated action a plugin takes — core-data reads, WebSocket broadcasts, notifications, AI calls, cross-plugin calls — is recorded at the point the plugin cannot reach, together with the acting user (bound by the host, never supplied by the plugin), the resource touched, and the outcome.

Each plugin's entries form a per-plugin hash chain (hash = sha256(previous_hash + row)), so the log is tamper-evident: any altered or removed entry breaks the chain. Older rows are pruned per plugin once the row cap is reached (default 20,000 rows/plugin, tunable via TREK_PLUGIN_AUDIT_MAX_ROWS; 0 disables pruning). Pruning keeps the retained window verifiable.

Two views read this log:

View Who Where Shows
Per-plugin audit Admins Admin plugin management (GET /api/admin/plugins/:id/audit) Every action one plugin took, across all users
My plugin activity Any user Settings → Plugins (GET /api/plugin-activity) Every action any plugin took in that user's name

The user-facing view is what makes broad read grants accountable to the person whose data a plugin reads, without needing admin access.

See also