Skip to main content

Security at Vision

Vision is built for optical chains where customer prescriptions, billing records, and audit trails sit at the heart of regulated operations. This page summarises the technical controls in production today and the third-party certifications on our roadmap. It is not a substitute for a Master Service Agreement — but it should answer most of your security-questionnaire rows directly.

Tenant isolation

Every shop is a tenant. No row from one tenant is ever readable by another, regardless of the path taken — URL, search, audit log, error message, export. Isolation is enforced at the database, not just in application code, so an SQL injection or a forgotten WHERE clause cannot leak across the boundary.

  • Database-level row isolation on every tenant-scoped table
  • Per-request session GUC (app.organization_id) bound at the start of every transaction
  • RLS policies are tested explicitly — not inferred — by an integration suite that runs as the app role
  • Cross-tenant URL probes return 404 / 307 to the user's own organisation, never the foreign data

Audit log: append-only at the database

The audit log is append-only at the database grant level, not just in application code. The application database role has SELECT and INSERT on the audit_log table; UPDATE, DELETE, and TRUNCATE are revoked at the role-grant level. Even a server-side bug or a compromised application token cannot mutate audit history. Every export bundle ships with a SHA-256 chain hash and an HMAC signature so an external auditor can independently verify integrity — the verifier is open-source and ships in our repo.

  • Verifiable via pg_class.relacl (pg_table_privileges → app role: r/INSERT only)
  • Every state-changing action emits a structured row with key-level diffs
  • JSONB diffs render per-key (added/removed/changed), not raw blobs
  • External verifier: scripts/verify-audit-chain.mjs (Node built-ins only; see docs/runbooks/audit-log-verification.md). Exit 0 on a clean bundle; exit 1 on any row-hash mismatch or chain-link break

Encryption — what the application enforces

Every transport hop into and out of Vision is TLS-protected, and the secrets we store are hashed or encrypted before they hit the database. The bullets below are enforced at the code level — they ship with the application regardless of where you host it.

  • Browser → app: TLS 1.2+ floor, HSTS pinned in production (max-age 2 years, includeSubDomains, preload)
  • App ↔ database: SSL required in production. The DB client refuses to start if DATABASE_URL is set to sslmode=disable
  • Passwords: industry-standard memory-hard hashing (scrypt, N=16384, r=16, p=1, dkLen=64); never stored in plaintext, no recovery codepath that ever sees the cleartext
  • API tokens: SHA-256 hashes only; cleartext shown to the owner exactly once at creation, never logged or returned again
  • MFA secrets and recovery codes: XChaCha20-Poly1305 via the auth library's symmetric-encrypt helper; the encryption key is derived from BETTER_AUTH_SECRET, which fails hard at startup if missing in production

Authentication and access control

MFA is required for paid-tier OWNERs (T2+) and for every member of any org whose owner has set requires_mfa = true. Session cookies use HttpOnly + Secure + SameSite=Lax across every authentication-relevant endpoint. The session-signing secret fails hard at startup if the production environment is missing it — a misconfigured deploy refuses to boot rather than silently issuing weak sessions.

  • TOTP MFA enforced for paid-tier OWNERs and for every member of orgs with the requires_mfa toggle on; available to opt in for everyone else
  • Payment-processor webhook signatures verified via SDK (300s replay window enforced)
  • Trusted origins pinned to our own baseURL — callbackURL on email-verification + password-reset flows cannot bounce a user off-domain
  • E2E test endpoints have a hard NODE_ENV !== 'production' floor so a leaked staging deploy cannot accidentally enable them

Data residency and backups

Vision ships as a self-deployable application. Two parts of the residency-and-backups story are code-enforced and ship with the app; two parts depend on your hosting choice. We're explicit about which is which because a procurement form will be.

  • Code-enforced — self-serve export from /settings/data: JSON or CSV bundles with customers, visits, line items, audit log (including the per-org audit-chain hash so an external auditor with a past chain-head snapshot can verify integrity downstream)
  • Code-enforced — GDPR/PDPL erasure on customer request: PII fields wiped, visit history preserved with placeholder identifiers so reporting integrity is not lost
  • Hosting choice — primary region: pick a region whose data-protection regime matches your buyer's residency. UAE/KSA for PDPL, EU region for GDPR-aligned operations. Vision's reference deployment runs in a UAE region; self-deployers pick what fits
  • Hosting choice — at-rest encryption + backup retention: enable encryption at rest at the storage layer (RDS, Supabase, Cloud SQL all default it on) and configure ≥30-day point-in-time recovery. Vision does not ship its own backup orchestration; we rely on the managed database provider

Security headers (ASVS L2 §V14.4)

Every Vision response carries the baseline security headers: X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, Content-Security-Policy with same-origin defaults plus the Stripe payment iframe, Strict-Transport-Security pinned in production, and X-Powered-By stripped to remove server fingerprinting.

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin-when-cross-origin
  • Content-Security-Policy: same-origin defaults + payment-processor checkout iframe
  • Strict-Transport-Security: max-age=63072000; includeSubDomains; preload (prod only)
  • Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(self)
  • X-Powered-By stripped (no framework fingerprint)

Privacy: PDPL and GDPR

Vision is built to support compliance with the UAE Personal Data Protection Law (PDPL) and the EU General Data Protection Regulation (GDPR). The cookie-consent banner ships with no third-party trackers firing before opt-in. The contact form, the dashboard, and every authenticated surface respect a denied analytics consent without degrading functionality.

  • Cookie-consent banner with explicit opt-in for analytics
  • Analytics SDK does not boot until consent is granted
  • All in-product analytics events use first-party identifiers; no cross-site tracking
  • Right to erasure honoured at the customer level; right to export self-serve at /settings/data

Vulnerability management and disclosure

Vision runs a recurring offensive-security walk against the production code path (the cybersec persona walk in the repo's `ux-reports/` history). Each walk produces a verified-controls report and an issues list, both checked into the repo. We welcome responsible disclosure from external researchers — write to the address below and we'll reply within one business day.

  • Internal security-walk skill executed each release cycle; reports live under `ux-reports/<run>/`
  • Verified-controls list in `ux-reports/2026-05-04b-security-walk-testdrive/checks-passed.md` (re-walk produces a fresh dated report)
  • Public security disclosure address: security@visionsaas.example (reply within one business day)

Certifications: present and planned

We are honest about what we have and what's on the roadmap. Vision is not yet SOC 2 or ISO 27001 certified. Both certifications are on our 2026 roadmap. The technical controls listed above are in production today; the audit-evidence collection and third-party assessment are the deferred work.

  • Today: technical controls verified by internal security-walk
  • Q3 2026: SOC 2 Type I audit kickoff (planned)
  • Q4 2026: ISO 27001 readiness assessment (planned)
  • PDPL alignment: continuous

Have a security question we didn't answer?

Procurement teams, CISOs, and infrastructure architects: we'd rather have an explicit conversation than a copy-pasted questionnaire. We typically reply within one business day.

Talk to security

Suspected breach? See our 72-hour response runbook — disclosure within the regulatory window is a contractual commitment, not a courtesy. Breach response procedure

Security · Vision