How it's encrypted
TLS 1.3 in transit (Let's Encrypt). External API keys, bank tokens, and broker secrets — AES-256-GCM with a unique IV per record. Passwords — bcrypt (12 rounds). Encryption key lives in env, not in the database.
Security
MyFina is a home ledger. Financial data is special — here's how it's protected, without marketing fluff.
Three short answers to the big questions
TLS 1.3 in transit (Let's Encrypt). External API keys, bank tokens, and broker secrets — AES-256-GCM with a unique IV per record. Passwords — bcrypt (12 rounds). Encryption key lives in env, not in the database.
PostgreSQL 16 in a managed instance at Hetzner (Germany, EU). Isolated Docker network, no external connections to the DB. Backups — daily, encrypted before upload, 30-day retention.
Account hard-delete in a single frame: transactions, accounts, categories, currencies, tokens cascade-removed. No soft-delete tables, no "just in case" archive. Marketing leads under the same email — separate erase-hook (GDPR Art. 17).
Six pillars of defence
NextAuth.js v5 on JWT, passwords hashed with bcrypt. Login throttling — 5 attempts / 15 min per identifier and 20 / 15 min per IP. Optional Google OAuth. Password reset uses a 32-byte token; only its SHA-256 is stored.
External API keys (banks, Resend, Anthropic) and broker secrets are encrypted with AES-256-GCM. The key never leaves the server. Each bank connection carries a per-connection 32-byte hex webhook secret.
Every UPDATE/DELETE filters by `userId`. IDOR protection is centralized in `assertCanAccessAccount/Category` — access to another user's account returns 403, not 404. Shared family accounts use explicit OWNER/MEMBER/VIEWER roles.
JWT with an incrementing `session_version`. Password change, account block, or role change invalidate all live sessions. Mobile-API refresh tokens rotate on every refresh.
Hard-deleting the account removes all transactions, accounts, and categories cascading in a single transaction. Data export — CSV/XLSX/PDF, one click. Cookie consent with two categories (essential / functional), no analytics, no trackers.
All admin actions and sensitive user operations are written to `audit_log` with IP, user-agent, and source. Login-attempt logs are kept 30 days. Stripe webhook events sit in a dedicated idempotency journal for safe replay on failure.
What we do NOT do
If you connect Monobank or a PSD2 bank via GoCardless, we only receive what's needed to sync transactions: outbound payments, incoming transfers, account balances. No bank-side personal data (passport, photo, biometrics). The bank API token is stored encrypted with AES-256-GCM; you can revoke access in one click from Settings → Banks. GoCardless consent expires every 90 days per PSD2 requirements — we remind you 7 days in advance.
Deeper details
Full list of third-party services processing your data: Hetzner (hosting), Stripe (billing), Resend (email), Anthropic (AI, DRAFT), GoCardless (PSD2), Monobank API, FCM.
Open listPer-feature matrix: what's stored, how long, how it's deleted. GDPR Art. 15/17/20 flow and retention windows per entity.
Open matrixWhat you can verify yourself
Every claim above is auditable via code and API.
src/lib/crypto.ts (AES-256-GCM, randomBytes IV, auth tag).src/auth.ts, src/lib/auth/rate-limit.ts.src/lib/auth/ownership.ts (assertCanAccessAccount).https://app.my-fina.com/api/openapi (OpenAPI 3.1).https://app.my-fina.com/api/health (public liveness).Subscribe to hear about CVEs, security patches, and threat-model changes. No marketing.