A check of every write path found gaps in what the audit log captured:
- Sign-ins and sign-outs are now recorded with the IP they came from
(sign-out is recorded first and can't block signing out).
- A new account is recorded when it's created on first sign-in, including
when the very first user becomes admin - so the log shows who gained
access, not only who changed things.
- The automatic log purge, which deletes audit entries, now records
itself, attributed to "system". recordAudit() takes an optional actor
for this. It only records when something was actually deleted.
- Settings updates record what changed (before and after) instead of only
which sections were touched. The notification channels (Gotify, ntfy,
SMTP, webhook) record field names only: they hold credentials, and
webhook URLs and public ntfy topics act as secrets, while the audit log
is readable by operators and Settings is admin-only.
- Integration edits record renames, enabling/disabling, whether
credentials were replaced (never the credentials), and which settings
fields changed (names only).
The Privacy page and README now say sign-ins store an IP in the audit log.
Verified through the real routes against a scratch database: user
creation, the logout route, the automatic purge, settings and
integration edits - including that a secret token and a webhook URL
appear nowhere in the stored entries. The sign-in callback itself needs
a real identity provider and wasn't run.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>