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.

DataWindow
Audit logs (security events)365 days
In-app notifications90 days (30 post-dismiss)
Marketing-funnel analytics90 days
Active session records90 days inactive
Insight snapshots (cache)180 days
JWT session rows24 hours / 30 days
Auth artifacts (OTP, password reset, MFA challenges)5 minutes – 1 hour
Brute-force lockout state1 hour
Idempotency keys1 day

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.