Every control below is named honestly: in place, available, or planned. We are building toward SOC 2 readiness and do not claim a certification we do not hold.
Isolation enforced in the database
Encrypted secrets
Audit everything
MFA: planned
Per-company isolation
In place
Two independent layers: the application refuses to run a query without a company in scope, and the database refuses to store a row without an owner.
A database extension merges the company id into every query on every business model and stamps it on every write. No company in scope: the query fails closed. A different company named in the query: a 403.
CHECK constraints on every business table make a row without a company id impossible even if the application layer were bypassed.
Business identifiers (customer, invoice, estimate numbers, SKUs, slugs, staff emails) are unique per company, never globally, so two companies never collide.
Realtime events carry the company id and are dropped for anyone else before any other check. Phone webhooks resolve the company from the called number first.
Row Level Security policies for every company-scoped table exist and can be enabled on a dedicated database role; they are a defence-in-depth option, not a dependency.
Host and session binding
In place
A session presented on another company's website yields no user; a cookie can never move data across companies.
The hostname is validated by middleware and passed to server code in a header; custom domains must be verified rows. Server code never trusts an arbitrary Host header.
A signed-in user's company wins. If the hostname belongs to a different company than the session, the session is ignored for that request.
Company users cannot reach platform-staff pages; platform staff cannot act inside a company without an impersonation session (see below).
Passwords and sessions
In place
argon2id hashing, lockouts, database-backed sessions that can be revoked instantly.
Passwords are hashed with argon2id (memory 19 MiB, 2 passes). Minimum 12 characters with mixed case and a digit. Never logged.
Login lockout for 15 minutes after repeated failures, plus a database-backed rate limit of 8 attempts per 15 minutes per identity.
Sessions are database rows bound to an httpOnly, secure, SameSite=Lax cookie with a 14-day lifetime; revoking a row ends the session immediately. Expired sessions are pruned automatically.
Every page checks permissions server-side; the edge middleware only redirects and is not an authorisation boundary.
Password-reset tokens are stored hashed with an expiry.
Multi-factor authentication
Planned
Not implemented today. MFA for owner, admin and platform roles is on the roadmap; until then use long unique passwords and revoke sessions from Settings → Users if a device is lost.
Encrypted secrets
In place
Provider credentials a company stores (Twilio tokens, SMTP passwords, Discord webhook URLs) are encrypted at rest with AES-256-GCM under a server-held key.
Platform-level secrets are marked as secret and encrypted the same way.
Health checks report only whether a secret is present, never its value. Audit rows are passed through a sanitiser that redacts password, token, secret and key fields before they are written.
Application logs drop stack traces in production and API errors return generic messages for unexpected failures.
Audit logs
In place
Every privileged action is written with actor, action, entity, before/after (secrets redacted), IP and user agent.
Companies see their own audit log in the office app (Audit log); platform actions (suspend, cancel, schedule deletion, entitlement override, impersonation start and end) are audited with a reason.
Audit rows are append-only and have no automatic deletion.
Support access to your account
In place
Platform staff can only act inside a company through an impersonation session that requires a written reason, lasts 30 minutes, shows a banner in your account and is audited on both ends.
The platform console never shows a company's customer data; it shows plan, status, usage and health.
Signed webhooks
In place
Inbound Twilio and Stripe requests are signature-verified before anything else happens.
Twilio: the signed URL is rebuilt from the platform's own configured address (never from request headers), the company is resolved from the called number, and the signature is validated with that company's token. Failures return 403 and are recorded.
Stripe: signatures are checked against the raw body; every event id is stored once so a retried event is never applied twice.
Media-stream sockets for the AI receptionist require a short-lived HMAC token.
Cloudflare in front
In place
TLS termination, CDN and WAF at the edge; Turnstile bot protection on public forms when enabled.
Private paths (office, technician and customer portals, platform console, API, guest document links) are sent with no-store cache headers for both browsers and the CDN, and are marked noindex.
Uploads are sniffed by magic number (JPEG, PNG, GIF, WebP, HEIC/HEIF, PDF only), capped at 12 MB, stored under random names and served with nosniff.
Backups
In place
A compressed database dump runs nightly on the server, with a documented restore procedure.
14 daily and 8 weekly copies are kept on the server; off-server copies and scheduled restore drills are on the roadmap (see the security programme below).
Least privilege
In place
Role-based permissions per company (owner, admin, dispatcher, office staff, accounting, technician, customer) and a separate platform role set; technicians and customers see only their own jobs and documents.
Guest links for estimates and receipts are hashed tokens with expiry and revocation; no account is needed and no other data is reachable from them.
The database role used by the application should have no superuser or CREATEDB rights; a dedicated RLS role and a secrets manager are next on the list.
Toward SOC 2 readiness
Security programme
Where each area stands today and what comes next. This is a roadmap, not an attestation.
Area
Now
Next
Access control
RBAC by permission, platform/company split
MFA for owner, admin and platform roles
Change management
Version history, forward-only migrations, documented rollback
Protected branch, review before deploy
Logging
Structured app logs, audit log, provider and billing event logs
Central log shipping and alerting
Backups
Daily database backup, restore procedure
Off-server copies, scheduled restore drills
Incident response
Written outline: detection, containment, evidence, notification
On-call rota, customer notification templates
Vendors
Subprocessor list published on Data handling
Data processing agreements with each subprocessor
What we store, where it lives, who can see it and how long we keep it is on the Data handling page. To report a vulnerability, use the support address in the footer or contact us.
Questions about a specific control?
Ask before you sign up — we would rather answer than over-promise.