Skip to content

Security

BillTracker is self-hosted. The application provides security controls, and the operator remains responsible for HTTPS, host access, volume protection, and updates.

How does authentication work?

  • Local passwords are hashed with bcrypt.
  • Successful login creates a random server-side session with a 7-day expiry.
  • The bt_session browser cookie is HTTP-only and SameSite strict.
  • Password resets, role changes, and account deactivation invalidate sessions.
  • The bootstrap admin is blocked from user tracker routes.
  • Optional OIDC uses PKCE, state, nonce, provider discovery, and token validation.
  • TOTP and WebAuthn are available as second factors; see Profile and Login Privacy.

Who can access what?

User-data routes require authentication and derive ownership from the active session. Admin routes require the admin role. Single-user mode may attach one configured regular user without login, but it does not grant access to admin routes.

How is CSRF protected?

Mutating API requests use a double-submit CSRF token:

  1. The server sets bt_csrf_token (httpOnly by default since v0.35).
  2. The SPA fetches the token from GET /api/auth/csrf-token on startup and stores it in an in-memory module cache.
  3. The SPA sends the value in x-csrf-token.
  4. The server rejects a missing or mismatched token.

The httpOnly default removes the token from the XSS-accessible cookie surface. Set CSRF_HTTP_ONLY=false only for a custom client that needs document.cookie access. See Tune Security Settings for the full knob list.

What rate limits apply?

Operation Limit
Login 10 per 15 minutes per IP
Password changes 5 per 15 minutes per IP
SimpleFIN sync and backfill 10 per 15 minutes per user (since v0.37; keyed by user ID, not IP)
Imports 20 per 15 minutes per IP
Exports 30 per 15 minutes per IP
Admin actions 30 per 15 minutes per IP
OIDC requests 20 per 15 minutes per IP
Backup operations 5 per 60 minutes per IP
Demo-data clear 3 per 15 minutes per IP

What response headers does the server send?

The server sends:

  • A nonce-based Content Security Policy
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: SAMEORIGIN
  • Referrer-Policy: strict-origin-when-cross-origin
  • X-Permitted-Cross-Domain-Policies: none
  • HSTS when HTTPS=true

How is sensitive data protected?

  • SQLite and backup files are not served as static files.
  • SimpleFIN access URLs, SMTP passwords, OIDC client secrets, TOTP secrets and recovery codes, push notification tokens, and login-history IP/UA/location columns are encrypted at rest with AES-256-GCM (HKDF-SHA-256).
  • Sessions are stored as SHA-256(token) (since v0.37) — the raw token lives only in the browser cookie.
  • IP geolocation is a per-user opt-in from Profile → Privacy. When disabled, no outbound ip-api.com call is made for that user's login and location_* columns in user_login_history are left null for new entries.
  • Sync, backfill, and password-change endpoints are rate-limited per user.
  • Backup restore validates managed paths and creates a pre-restore snapshot.
  • Import flows separate preview from apply and validate uploaded content.
  • Sensitive administrative actions are recorded in audit_log.

What should I do before exposing the app?

  1. Use HTTPS and verify cookie flags behind your reverse proxy.
  2. Keep /data readable only by trusted operators.
  3. Remove or rotate bootstrap passwords after first setup.
  4. Back up the database before upgrades.
  5. Keep the image and host patched.
  6. Protect backup downloads and exports; BillTracker does not encrypt those files.

The Calendar Feed URL is a bearer credential; treat it like a password and regenerate it on suspicion of exposure.

See also

Next steps