Security

Last reviewed September 9, 2026

You are handing us your clients' names, phone numbers and money. Rather than list certifications we do not hold, this page names the specific mechanisms that are in the product today, and ends with what is not in it. If something you need is missing, the answer is that it is missing — not that it is coming soon.

1Workspace isolation

Every customer record is bound to exactly one workspace — by a workspace id on the row itself, or through the record that owns it — and every read and write in the application is scoped to the workspace of the signed-in user. An id supplied by a browser is never trusted on its own: it is re-scoped by workspace, so another customer's id resolves to nothing rather than to their record.

Underneath that, row-level security is enabled on every table — the database itself refuses to hand one workspace's rows to another. A new table gets it in the migration that creates it, rather than in a later pass. Before every release a check queries the live database for any table or partition without it, and any view that would sidestep it, and fails the release if it finds one — the state of the database is asked, never assumed from the schema on paper.

Being precise about what that buys: the application connects with a privileged database role and does its own scoping, so row-level security is the second wall, not the first. It is what stands between a customer record and anything that reaches the database outside the application — including the public API key that necessarily ships in every page of a web app.

2Credentials and secrets

Your third-party keys are encrypted at rest

The Stripe, Resend and Twilio keys you save are encrypted with AES-256-GCM before they are stored, under a key that lives in the deployment environment and not in the database. A copy of the database alone does not yield working keys.

They are yours, and rotatable without us

Each workspace holds its own credentials, so you rotate a key in your own vendor dashboard and paste the new one in. No support ticket, no deploy.

Passwords are never ours to lose

Sign-in is handled by Supabase Auth. We do not store password hashes ourselves, and no part of this application can read a password back.

3Links that act as keys

A proposal, an agreement, an invoice, a client portal, a booking confirmation and an unsubscribe link are all reachable without an account, because asking a customer to create a login to sign a contract is how contracts go unsigned. Each of those URLs carries a capability token, and the token is treated as the credential it is:

256 bits, and only the hash is stored

Tokens are 32 random bytes. The database holds their SHA-256 hash, never the token, so a database leak yields hashes rather than working links. Comparison is constant-time.

Scoped, expiring, revocable

A token opens exactly one thing — a token for one proposal cannot open an invoice — expires by default, can be made single-use, and can be revoked. None of them is ever a session, and none of them opens the team application.

Never indexed

Every tokenized path answers with a noindex header, so a link pasted somewhere public does not become a search result. The pages meant to be found — your public booking pages and this marketing site — are enumerated one by one, and everything else is private by default.

4Payments

Card numbers never reach our servers. Payment happens on Stripe's own hosted checkout page; we create the session and receive the result. There is no card field anywhere in this application, which is the only card-data control worth having at our size.

Every delivery from Stripe is checked against Stripe's own signature, with a constant-time comparison, before a single byte of it is acted on. A payment is then confirmed by that webhook rather than by the browser landing on a success page, so a customer who pays and closes the tab is still confirmed, and a replayed delivery changes nothing.

5Backups, and restores that are actually tested

A backup nobody restores is a hope. Ours are verified, and the verification is the point of the design:

Nightly, encrypted, off the application

Every night an automated job exports every table that holds your records, encrypts it with AES-256-GCM under a key derived separately from the application's own, and writes it to private object storage. The only tables left out are derived rollups and short-lived operational logs, which are rebuilt from their sources rather than restored.

Downloaded back and hash-compared, every run

The same job immediately fetches the file back, decrypts it and compares hashes. A backup that cannot be read is reported as a failure that night, not discovered during an emergency. Each run is recorded and surfaced on an internal health page.

A second copy, reloaded before every migration

Before any schema change we take a full local export outside the code repository and reload the largest tables into a scratch schema to diff the row counts. The very first run of that check caught a real defect: array columns were being exported in a form that could not be loaded back.

Migrations only ever add

No migration drops a column, rewrites data or destroys anything. Retiring a field means the code stops reading it; the data stays. Deploys ship code and never touch data, so a bad release is a rollback rather than an incident.

The honest limitation: the nightly copy lives with the same vendor as the database, in different storage. It protects against a bad query, a bad deploy and a bad day — not against losing that vendor entirely. A second, independent leg is designed and is not built.

6A record of what happened

Activity log in your workspace

Changes to your records are written to your workspace's activity log with the record they touched, so a changed price or a cancelled booking has a history rather than an argument.

Operator actions are logged separately

Anything we do as the operator of the platform — pausing a workspace, toggling a module, running the engine by hand — is recorded in a separate audit log with who did it, to which workspace, and when.

Agreements are content-hashed at signing

The exact document shown at the moment of signature is hashed and stored with the signature, so any later alteration of the document is detectable.

Most deletes are soft, and the rest are small

Deleting a lead, client, project, invoice, proposal, expense, document, task, schedule or event type hides it and moves it to a workspace-wide trash you restore from in one click, and nothing in that bin is purged on a timer. A handful of small configuration objects are removed outright instead — a note on a client, a recurring-invoice template, a one-off day on a calendar, a checklist line, a connected calendar. So the records a dispute would turn on survive an accidental delete; only a deliberate request to delete the workspace destroys anything for good.

7Access control inside the app

There are three roles — owner, collaborator and client — and each page and each action checks its own permission in its own body, rather than relying on a shared wrapper. That is a deliberate choice: in this framework a crafted request can render a page without its surrounding layout ever running, so a guard that lives only in the layout is not a guard.

A client signing into the portal sees only their own records, and the identity used to scope that comes from their own database row — never from anything their browser sends.

8What we deliberately do not do

No AI at runtime

Every automation is deterministic rules and templates. Your client data is never sent to a model provider, because nothing in the product calls one.

No ad trackers, no data sales

Only the cookies that keep you signed in — no advertising or analytics cookies, no third-party analytics scripts, no pixels. Your workspace data is never sold, never used to train anything, and never shared with anyone but the vendors listed in the privacy policy that operate the product.

No surprise sending

A workspace sends email under its own sender identity, using its own provider key. A workspace we manage can ask to borrow ours instead; that is a switch we set per workspace, never a default, and a self-serve signup can never turn it on. Every attempted send is recorded — delivered, skipped or failed, with the reason — so what went out to a customer is a record rather than a recollection.

Opt-outs are enforced, not remembered

Commercial email carries a postal address and a one-click unsubscribe that works without an account. The suppression list is consulted before every commercial send, and a check that cannot complete blocks the send rather than assuming nobody opted out.

9Getting your data out, or gone

Export. Email us from your account address and we will produce a complete export of your workspace. It is a manual request today — a self-serve export button is designed and not yet built.

Deletion. Delete individual records in the app, or ask us to delete the entire workspace and account. We confirm when it is done. The retention windows are written out in the privacy policy, which also lists every vendor that touches your data.

10What is NOT in place

Stated plainly, because a security page that only lists strengths is a sales page:

  • No SOC 2, ISO 27001, HIPAA or PCI certification, and no audit in progress.
  • No multi-factor authentication on accounts yet.
  • No self-serve data export in the app — export is a request we fulfil by hand.
  • No penetration test by an outside firm.
  • No contractual uptime guarantee, and no 24/7 on-call rotation. This is a small company.
  • No EU data residency. Data is stored in the United States.

If one of those is a requirement for your business, say so before you sign up rather than after. We would rather lose the sale than be the wrong vendor for a regulated obligation.

11Questions we get asked

Do you hold SOC 2, ISO 27001, HIPAA or PCI certification?

No. We hold no security certifications and will not imply otherwise. If your business requires one, we are too early to be the right vendor for that requirement yet.

Where is my data stored?

In a US-region PostgreSQL database hosted by Supabase. The application runs on Vercel. Both are named in our privacy policy along with everything else that touches your data.

Can one customer see another customer's data?

Every customer record belongs to exactly one workspace, every query is scoped to it in the application, and row-level security is enabled on every table in the database as a second wall underneath the application.

Can I get my data out, or get it deleted?

Yes. Email us from your account address and we will produce a full export of your workspace, or delete the workspace and account and confirm when it is done. Most records you delete inside the app go to a trash you can restore from; a few small configuration objects are removed outright.

12Reporting a vulnerability

Email nvarsinclair@nvarstudios.com with what you found and how to reproduce it. We will confirm that we received it. We do not run a paid bounty programme, and we will not threaten anyone who reports a problem in good faith.

Read next: the privacy policy for who processes what, and the terms for what we promise and what we do not.