Last updated · June 5, 2026

Security

An honest, current view of how VYNE is built + operated. We update this page when our posture changes; nothing here is aspirational.

Encryption

LayerAlgorithmDetail
In transitTLS 1.3HSTS with max-age 2 yrs + includeSubDomains + preload. Enforced by Vercel edge.
At rest (DB)AES-256Neon Postgres — full-disk encryption + per-branch isolation.
At rest (Blob)AES-256Vercel Blob — backups + uploads.
Application (MFA secrets)AES-256-GCMPer-row encryption with MFA_ENCRYPTION_KEY env. Fresh IV per call.
Password hashingPBKDF2-SHA256Per-user salt + 100k iterations.
Password-reset tokensSHA-256Raw token never stored; only the hash. 1-hour TTL, single-use.

Tenancy + access control

Every Postgres row carries an orgId. Every API route is gated through requireTenant() which resolves the caller's session orgId from the HMAC-signed session cookie. Reads + writes filter on that orgId; cross-tenant probes return 404, never 403 (so we don't leak existence). The factory that serves 42 CRUD routes is unit-tested in src/lib/api/__tests__/crud.tenant-isolation.test.tsfor: GET filters on orgId; POST stamps caller's orgId; body-provided orgId can never override; PATCH/DELETE on cross-tenant rows return 404 and never call .update / .delete.

Authentication

Email + password (PBKDF2). Optional TOTP-based MFA (RFC 6238) with 10 single-use recovery codes (sha256-hashed). Sessions are HttpOnly, Secure, SameSite=Strict cookies signed with HMAC. Password-reset flow uses a 1-hour single-use token; reset endpoint enforces rate limiting + no-enumeration (same response for valid + invalid emails).

Rate limiting + abuse

Per-IP + per-route limits via Upstash Redis. Fail-open on Upstash outage to avoid taking down legitimate traffic; abusive behaviour during outage is still bounded by Vercel-edge global limits. /api/auth/login: 5 req / minute / IP. /api/account/delete: 3 req / hour / IP. /api/admin/backup + /restore: 6 req / minute / IP.

Audit trail

Every state-changing action writes to audit_events: actor, target, action, ip, userAgent, severity, optional diff. Daily admin-only export endpoint with sha256-checksum manifest for tamper-evidence (in progress for SOC2 prep). Retention: 2 years; anonymised after 90 days post account-deletion.

Backups + disaster recovery

Daily JSON dump at 06:00 UTC via Vercel Cron → Vercel Blob (when BACKUP_BLOB_TOKEN set). Weekly automated restore-verification at 04:00 UTC Sunday: pulls latest dump, dry-runs through /api/admin/restore, alerts Sentry on failure (SEV2). Neon point-in-time recovery: 7-day WAL window. Full DR runbook (3 scenarios incl. cross-region failover) at docs/runbooks/db-restore.md. Public DR metrics on /status. Targets: RPO 5 min, RTO 30 min.

Application security

ControlHow
Strict Content-Security-PolicySet on every response via vercel.json; allowlist for Stripe, Anthropic, Groq, LiveKit only.
HSTS preloadSubmitted to hstspreload.org; 2-year max-age.
Frame-deny + XSS protectionX-Frame-Options: DENY; X-Content-Type-Options: nosniff.
CSRFDouble-submit token on every state-changing fetch via csrfFetch().
Permissions-PolicyCamera/microphone/screen-capture self-only; no geolocation.
Static + dep scanningESLint + tsc --noEmit gated in CI; vitest unit + integration coverage.
Sentry PII redactionbeforeSend strips Authorization headers, cookies, email-shaped strings before send.

Supply chain

Dependencies pinned by pnpm lockfile; Dependabot enabled for npm + GitHub Actions. Production builds use --frozen-lockfile equivalent on Vercel. We pull @vercel/blob, stripe, @sentry/nextjs from npm direct.

Compliance + certifications

  • GDPR + UK GDPR — SCCs + UK IDTA executed; DSAR endpoints live (Privacy §Your rights).
  • CCPA / CPRA — "Do not sell or share" honoured by design (we don't).
  • SOC 2 Type I — preparation in progress (PH-I); ETA Q4 2026.
  • HIPAA / FedRAMP — not currently supported; roadmap dependent on enterprise demand.

Vulnerability reporting

Report security issues to security@vyne.app. We aim to acknowledge within 24 hours, triage within 3 business days, and patch high-severity findings within 30 days. Please give us a reasonable window before public disclosure — typically 90 days. We do not currently run a paid bug-bounty program but will publicly credit responsible reporters with their consent.

Incident response

SEV1 (data exposure or full outage) escalation: page on-call, post a /status incident within 30 minutes, customer notification within 72 hours per GDPR Art. 33. SEV2 (partial outage, automated alert): Sentry → engineering chat → mitigated same-day. The full runbook lives in docs/runbooks/.