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.
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.
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.
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.1throughtrek.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.
