Security hardening release. The container now runs as a non-root user — existing deployments need a one-time volume permission fix before upgrading (see below).
Security
- Fixed session store expiry bug: stored sessions outlived the cookie by ~1000x; a stolen session ID stayed usable for years instead of 7 days
- Fixed authorization gap: any authenticated user could render any account's MICR line (routing + account number) via the layout preview endpoint
- Session ID regeneration on login, setup, and SSO login (session fixation)
- Password reset links built from new
APP_BASE_URLenv var instead of the Host header (reset-link poisoning) - Rate limiting on password reset requests (5/IP/15 min)
- Secure cookies on TLS connections + new
TRUST_PROXYenv var for reverse-proxy deployments - OIDC logs no longer record authorization codes, subject IDs, or emails
- QBO import records re-validated server-side; routing numbers validated as 9 digits; upload size caps; atomic user updates; last admin cannot be demoted
- Container runs as unprivileged
nodeuser;.dockerignorekeeps local.env/database/git files out of published images - Patched
qstransitive dependency (GHSA-q8mj-m7cp-5q26, moderate DoS)
Performance
- Hot-path SQL statements (auth checks, session load/save) prepared once instead of per request
Upgrading from v0.4.x
The data volume was written as root by older images. Fix ownership once before pulling:
docker compose down
docker run --rm -v check-printing-data:/data alpine chown -R 1000:1000 /data
docker compose pull && docker compose up -dNew optional env vars (recommended in production):
| Variable | Purpose |
|---|---|
APP_BASE_URL |
Public URL of the app (e.g. https://checks.example.com) — used in password reset links |
TRUST_PROXY |
Set to 1 when behind a reverse proxy / TLS termination |
Full Changelog: v0.4.6...v0.5.0