* feat(plugins): TREK_PLUGINS_IGNORE_TREK_RANGE turns the version gate into a warning A plugin whose author has not updated its `trek` range for the running TREK can now be installed, updated and activated when the operator sets TREK_PLUGINS_IGNORE_TREK_RANGE to a truthy value. Every gate (registry picker, post-extraction re-check, sideload, dev-link, activation) warns instead of refusing; a missing range is tolerated the same way; "install latest" takes the newest published version. The plugin-API version gate is not lifted and the Discover compatibility verdict stays truthful. The admin is warned at every step: each bypass is logged, install/upload/link/ update responses carry a `trekRangeBypassed` marker, and both admin panels show a "Version checks off" pill, an "Install anyway" confirm dialog before a registry install, the same notice after a path that could not ask first, and a persistent chip on the row while the plugin runs outside its range. Nine new i18n keys in every locale; wiki pages updated (Environment-Variables, Admin-Plugins, Plugin-Development, Security-Hardening). * refactor(plugins): one useRangeBypass hook behind both admin shells SonarQube flagged PR 2226 at 10.8% duplicated new code: the range-bypass logic had been added to the desktop and phone plugin panels as identical copies. The marker/warning types, the row chip, the "Install anyway" offer, the dialog copy and the confirm-or-notice state now live in useRangeBypass (next to useInstanceSettings, the existing shared-logic precedent); each panel keeps only its own dialog/sheet markup. * docs(claude): record the SonarQube gate rules and the no-English-placeholder i18n rule The CI SonarCloud gate (Sonar way, on new code: >=80% coverage, <=3% duplication, A ratings, hotspots reviewed) was only discoverable by waiting 20-25 minutes for the workflow. The root CLAUDE.md now states the conditions, the desktop/mobile shell-mirror duplication trap and its shared-hook fix, and how to check a PR with the sonarqube MCP first; client and server CLAUDE.md point at it. shared/CLAUDE.md says every locale needs a real translation — parity cannot tell an English placeholder from a translation. * docs(claude): keep CLAUDE.md to commands, invariants, gates and philosophy Verified every concrete claim in the five CLAUDE.md files against the code and fixed the stale ones: the websocket server is the Nest realtime gateway, MCP tools live in <domain>.mcp.ts files, the client "native shell" section described code that was never written, offline maps also prefetch vector basemaps, plus several renamed files and wrong table names. Then trimmed everything that drifts on its own: dates, migration history, counts, and inventories of stores, slices, domains, sync files and CLI commands. What stays is the commands, the invariants with the file that enforces each one, what CI actually runs, the reference implementations to copy, and the rules for new code. Each file now opens with that rule.
10 KiB
Security Hardening
A production TREK deployment checklist. All items reference actual TREK configuration options.
Encryption & Secrets
- Set a strong
ENCRYPTION_KEY(generate withopenssl rand -hex 32). See Encryption-Key-Rotation. - Back up
ENCRYPTION_KEYseparately from the database backup ZIP — losing it makes all stored API keys and secrets unreadable. Stored secrets use AES-256-GCM encryption derived from this key. - Rotate
ENCRYPTION_KEYif it may have been exposed. See Encryption-Key-Rotation.
HTTPS & Network
- Run TREK behind a TLS-terminating reverse proxy (nginx, Caddy, Traefik). See Reverse-Proxy.
- Set
TRUST_PROXY=1so client IPs are captured correctly in the audit log. InNODE_ENV=productionthis defaults to1automatically, but set it explicitly if you use a non-standard proxy hop count. - Set
FORCE_HTTPS=trueto 301-redirect HTTP to HTTPS and addupgrade-insecure-requeststo the CSP. Your proxy must sendX-Forwarded-Proto: https(or terminate TLS on the same connection), otherwise the redirect fires on every request and loops. - Know that HSTS (
max-age=31536000) does not depend onFORCE_HTTPS: it is sent wheneverFORCE_HTTPS=trueorNODE_ENV=production, which the Docker image sets by default — so an instance behind Traefik, Caddy or a Cloudflare Tunnel advertises HSTS without settingFORCE_HTTPSat all. SetHSTS_INCLUDE_SUBDOMAINS=trueto addincludeSubDomains; it is off by default so an install on an apex domain does not force HTTPS onto sibling subdomains you may still serve over plain HTTP. - Keep
ALLOW_INTERNAL_NETWORK=falseunless a strict-guard integration — Immich, Synology Photos, AirTrail, a notification webhook or ntfy — is on your LAN. See Internal-Network-Access. Note: loopback (127.x,::1) and link-local (169.254.x) addresses are always blocked regardless of this setting.
Authentication
- Enable two-factor authentication for your admin account. See Two-Factor-Authentication.
- Require MFA for all users if your use case demands it: Admin Panel → Settings → Require two-factor authentication (2FA). Note: you must secure your own admin account first, with either TOTP or a registered passkey — the server refuses the toggle otherwise. A passkey satisfies the policy for everyone else too, so nobody is forced onto TOTP specifically.
- Disable open registration if you control who can access the instance. See Admin-Users-and-Invites.
- Rotate the JWT signing secret if a session may have been leaked: Admin Panel → Settings → Danger Zone → Rotate (
POST /api/admin/rotate-jwt-secret). This invalidates all active sessions immediately, including your own.
Session Security
TREK stores sessions as JWTs in an httpOnly trek_session cookie (SameSite=Lax). A normal login expires after SESSION_DURATION (default 24 hours) and rides in a browser-session cookie that the browser drops when it closes; ticking Remember me issues a persistent cookie whose lifetime and JWT expiry are SESSION_DURATION_REMEMBER (default 30 days) — shorten it if a 30-day window is too wide for your threat model. The secure flag is set automatically when NODE_ENV=production, when FORCE_HTTPS=true, or when Express sees that the request arrived over TLS (X-Forwarded-Proto: https) — the last of those needs trust proxy active, which is automatic in production and otherwise means setting TRUST_PROXY yourself. Tokens are also accepted via Authorization: Bearer header for MCP and API clients.
- Ensure
FORCE_HTTPS=true(orNODE_ENV=production) so thetrek_sessioncookie carries thesecureflag and is never sent over plain HTTP. - Set
COOKIE_SECURE=falseonly as a temporary escape hatch for LAN testing without TLS — do not use in production.
Password Policy
TREK enforces a minimum password policy on all registrations and password changes:
- Minimum 8 characters
- Must contain uppercase, lowercase, digit, and special character
- Common passwords and fully-repetitive strings are rejected
- Passwords are hashed with bcrypt (cost factor 12)
No configuration is required; this policy is always active.
Rate Limiting
Built-in in-memory rate limits protect authentication endpoints:
| Endpoint | Limit | Window |
|---|---|---|
| Login / Register / Invite | 10 attempts | 15 minutes |
| MFA verify-login / enable | 5 attempts | 15 minutes |
| Password change | 5 attempts | 15 minutes |
| MCP token creation | 5 attempts | 15 minutes |
These limits are per source IP. If TREK is behind a reverse proxy, set TRUST_PROXY so the real client IP is used rather than the proxy's IP.
Content Security Policy
Helmet applies a strict CSP on all responses. Key directives:
default-src 'self'script-src 'self' 'wasm-unsafe-eval' 'unsafe-eval'(nounsafe-inline;'unsafe-eval'is required by the in-browser HEIC converter, which initialises throughnew Function())object-src 'self'(so same-origin file previews can embed PDFs via<object>/<embed>)frame-src 'self'(for the sandboxed plugin frames at/plugin-frame/*, which run at an opaque origin under their own CSP)frameAncestors 'self'(prevents clickjacking from external frames)upgrade-insecure-requests(added automatically whenFORCE_HTTPS=true)
Plugin Runtime Hardening
Installed plugins run untrusted third-party code. TREK contains a plugin in several independent layers so a hostile or buggy plugin can neither read TREK's data nor take the instance down. Nothing here needs configuration — it is all on by default — but the escape hatches below exist for tuning.
- Leave the plugin system's defaults in place. It is on by default but installed plugins still have to be activated one by one, so no third-party code runs until an admin turns a specific plugin on. Set
TREK_PLUGINS_ENABLED=false(acceptsfalse/0/off/no) to switch the whole system off — installed plugins stay on disk, deactivated, and the runtime is idle. - Keep the OS permission jail enabled (the default). In production each plugin runs in an isolated child process launched with Node's
--permissionmodel: filesystem writes,child_process, worker threads and native addons are denied outright, and reads are scoped to just the plugin's own code — so a plugin cannot readtrek.dbor the secret files, or shell out. The child's environment is scrubbed (noJWT_SECRET, no DB credentials). SettingTREK_PLUGIN_PERMISSIONS=offdisables this jail (isolation then falls back to crash-only) and logs a loud warning — only ever do this on a machine you fully trust. - Rely on the private-egress block (SSRF backstop). Even a plugin that declared an outbound host cannot reach a destination that resolves to a loopback, private, link-local, ULA, carrier-grade-NAT, cloud-metadata (
169.254.169.254), multicast or reserved address — the guard re-checks the resolved IP, so a plugin can't pivot to internal services or DNS-rebind to them. This is independent ofALLOW_INTERNAL_NETWORK(which governs the core integrations reached through the strict guard — Immich, Synology Photos, AirTrail, notification webhooks and ntfy — not plugin egress). The one escape hatch isTREK_PLUGIN_ALLOW_PRIVATE_EGRESS=on, for a plugin that has to reach a service on your LAN (a Gotify, an ntfy, an Ollama). It is instance-wide rather than per plugin, and it lifts the block entirely rather than narrowing it: a declared host may then resolve to any address, the169.254.169.254metadata IP included, and unix-socket or named-pipe connects (docker.sock, a local database socket) are permitted without even being declared, since the OS jail does not gate socket connects. Leave it unset unless a specific plugin genuinely needs a LAN target. - The supervisor caps each plugin's resident memory (default 300 MB,
TREK_PLUGIN_MAX_RSS_MB) — measured host-side from the OS, never the plugin's self-report — and kills a plugin that blows the ceiling or stops sending heartbeats; repeat offenders auto-disable. Everyctx.*capability call is also rate-limited at the dispatch boundary (a token bucket: ~60-call burst, 20 calls/sec sustained, 16 concurrent;TREK_PLUGIN_RPC_BURST/TREK_PLUGIN_RPC_PER_SEC/TREK_PLUGIN_RPC_INFLIGHT), so one plugin in a tight loop gets throttled instead of freezing the instance. - Review the capability audit if you grant plugins broad data access. Every host-mediated core-data read and broadcast a plugin makes is recorded at the RPC boundary against the real acting user (not a value the plugin supplies) in a per-plugin, hash-chained, tamper-evident log. Admins see it per plugin; each user can see "what have plugins done in my name?". Retention is capped per plugin (default 20 000 rows,
TREK_PLUGIN_AUDIT_MAX_ROWS).
The developer dev-link feature (
TREK_PLUGINS_DEV_LINK=1) loads unsigned local code and, undernpm run dev, runs with the OS jail off — keep it off on any instance that isn't a throwaway dev box you control. See Plugins and Plugin Permissions.Likewise leave
TREK_PLUGINS_IGNORE_TREK_RANGEunset. It turns the plugin TREK-version gate into a warning so a plugin whose author has not updated itstrekrange can still be installed and activated — the admin panel warns at every step, but a plugin running on a TREK it was never tested against can misbehave and, in rare cases, corrupt data. Set it only for a specific plugin you need, and remove it once the author ships a release that admits your TREK.
Backups
- Enable auto-backup with an appropriate retention window. See Backups.
- Store backups off-site — copy backup ZIPs to a separate location outside the TREK host.
Monitoring
- Review the audit log periodically for unexpected logins or admin changes. See Audit-Log.
- Check for TREK updates regularly. See Admin-GitHub-Releases and Updating.