Security
Most of this page is backed by a continuously-running test. The rest is labelled, not implied.
The companion to /privacy. Privacy answers "what do you do with my data?"; this page answers "how do I know you actually do that?"
The full, collection-by-collection retention list is published as DATA_RETENTION.md with every release — the same reference our validation suite checks against the live database.
Security posture
The defenses in place today. Each one is tagged with how you can know it holds — we would rather tell you where our evidence stops than let a label do work it hasn't earned.
- Tested
- Implemented in the application and covered by an automated test that runs on every commit.
- Platform
- Enforced by our hosting and provider configuration. Real, but not something a test in this repository can demonstrate — so we label it instead of implying we verify it.
Transport security
- HTTPS-only — TLS 1.3 terminated at the Cloudflare edge. Platform
- HSTS with includeSubDomains and a 1-year max-age, submitted to the preload list. Platform
- Strict CSP with a per-request nonce on every HTML response. Tested
- HttpOnly, Secure auth cookies use SameSite=None for supported app domains, with Origin-checked mutation routes. Tested
Authentication
- Bcrypt password hashing, 12 rounds (~250ms target — slows offline attackers). Tested
- Optional TOTP second factor (RFC 6238, 30-second windows). Tested
- Optional WebAuthn / passkey enrollment with platform-bound credentials. Tested
- Brute-force lockout after 5 failed attempts (15-minute window, IP+email scoped). Tested
- Step-up auth required for sensitive admin actions. Tested
Data at rest
- Postgres product-state tables sit on provider-managed AES-256 disk encryption. Platform
- Field-level encryption for credential material (TOTP secrets, passkey IDs). Tested
- Backups encrypted with separate keys; retained 30 days hot, then 1 year cold. Platform
Application hardening
- Rate limiting in the FastAPI middleware and per-route budgets, plus connection and request limits in the bundled Nginx config. Tested
- A further rate-limiting and bot-mitigation layer runs at the Cloudflare edge. Platform
- Pydantic-validated request bodies (no raw deserialisation paths). Tested
- OWASP top-10 mitigations covered by validation tests in tests/validation/security_audit. Tested
- A Server-Action WAF blocks reflected ID-spray attempts on Next.js mutations. Tested
Privacy by design
- No third-party tracking pixels (verifiable in any devtools Network tab). Tested
- Marketing-funnel analytics use a client-generated visitor_id (UUID), never a user_id. Tested
- Public viability share links scrub all owner identifiers and use anonymised view-count hashes. Tested
- Outbound emails carry no tracking pixels. Tested
Data retention windows
Every row below is backed by a database retention index or repository cleanup path and asserted by a validation test that fails the build if the contract drifts.
| Data | Window | Why |
|---|---|---|
| Audit logs (security events) | 365 days | Compliance retention window; long-term archival is handled outside this TTL. |
| In-app notifications | 90 days (30 post-dismiss) | Release fan-outs and financial alerts. |
| Marketing-funnel analytics | 90 days | Anonymous visitor_id only. No PII. |
| Active session records | 90 days inactive | Per-device login list; revoke any session anytime. |
| Insight snapshots (cache) | 180 days | Computed financial insights; rebuilt on demand. |
| JWT session rows | 24 hours / 30 days | Default vs. 'remember me'. User-controlled. |
| Auth artifacts (OTP, password reset, MFA challenges) | 5 minutes – 1 hour | Single-use tokens; immediate after consumption. |
| Brute-force lockout state | 1 hour | Auto-clears so legitimate users can retry. |
| Idempotency keys | 1 day | API duplicate-write protection. |
Account deletion contract
One click, one cascade, nothing left behind.
When you delete your account from Settings → Security, the API runs a transactional cascade that hard-deletes every row in:
- users · workplaces · pay_rules · shifts · shift_templates
- bills · debts · goals · budget_categories · time_off_*
- households · household_invites · household_goal_contributions
- documents · document_extractions · ocr_corrections · receipts
- viability_scenarios · viability_shares · viability_share_views · viability_exports
- auth_sessions · user_sessions · active_sessions · mfa_* · webauthn_credentials
- notifications · merchant_aliases · user_notification_preferences
- decisions · feedback_reports
Preserved: audit_logs entries (anonymised, age out via 365-day TTL), payment_transactions (financial-ledger retention; mirrored by Stripe). Both are retention-bounded.
Commitments and honest disclaimers
What we'll commit to. What we won't pretend to be yet.
Verified by tests
- Every TTL above has a validation test that fails CI if the index is missing or the field type is wrong.
- Auth and brute-force flows have integration coverage; a security gate blocks merges that disable security lint rules.
- Account-deletion cascade has end-to-end coverage; an orphan-row scan runs in CI for every collection touched by the cascade.
Honest disclaimers
- We are not yet SOC 2 Type II certified. We design for the controls but the audit is on the roadmap, not complete.
- We are not a HIPAA business associate. Don't upload Protected Health Information; use a HIPAA-compliant tool for that.
- We do not maintain a public bug-bounty program yet. Responsible-disclosure reports go to security@viably.tech and we reply within 2 business days.
How drift is caught
- Our published DATA_RETENTION.md reference is the canonical source. The validation tests check that every documented retention window is backed by a database index or repository cleanup path.
- If an engineer adds a new collection without TTL or with the wrong field type, CI fails and the PR can't merge.
- Materially-changed retention windows are released in the public changelog with at least 30 days notice for tightening windows.
Reporting a security issue
Email security@viably.tech. PGP key on request. We aim to acknowledge within 1 business day, triage within 3, and ship a fix or mitigation within 10 for high-severity findings.
We do not yet operate a paid bug-bounty program but we will publicly credit responsible-disclosure researchers in our changelog at your discretion.
Questions answered
The security posture is real.
The product is too.
Run your audit Free. No credit card. Account creation requires only an email.