How one firm is kept from another.
You are trusting us with your customers’ data. This page says exactly how it is protected, and names the mechanism behind each claim.
- Tenancy
- Every row carries the firm it belongs to
- Every business record has a firm identifier and an index on it. Reads and writes go through a client scoped to the signed-in firm; the few paths that must run before anyone is signed in are named, one by one, in a test that fails the build if another appears.
- Tenancy
- And again, inside the database
- Every tenant table carries a Postgres row-level security policy: the database itself refuses a row that belongs to another firm, so a mistake in the application still cannot cross. The application connects as a role those policies bind; only sign-in, token links and scheduled jobs use one that does not, and a test names each file allowed to.
- Tenancy
- Cross-firm access is tested as an attack
- The suite signs in as one firm and asks for another firm’s records by identifier, through every route. The answer must be "not found" — the same answer as a record that never existed, so a probe learns nothing.
- Access
- Permissions checked where the data is read
- A hidden menu item is not a control: every route handler checks the capability and the module again. Money is a separate permission from the record it belongs to, so a contractor can open a customer without seeing what they owe.
- Authentication
- Passwords hashed with bcrypt, cost 12
- No password is stored or logged. Sign-in is rate-limited per address and per network, and a wrong password, an unknown address and a suspended account all give the same answer.
- Authentication
- Two-factor authentication
- Authenticator-app codes for staff, required for the roles that can change money, permissions or settings, and available to everyone else. A firm can require it of all its staff.
- Secrets
- API keys stored as hashes, shown once
- A key is displayed once when it is created. The database holds its SHA-256 hash and last four characters — enough to recognise it in a list, not enough to use it.
- Secrets
- Credentials encrypted at rest
- A firm’s own mail-server password is encrypted with AES-256-GCM before it is saved; a tampered value fails to decrypt rather than decrypting to something wrong.
- Audit
- An append-only record of every change
- Each change writes an audit entry in the same transaction as the change itself, so a change without a record cannot happen. The application has no code path that edits or deletes an audit entry.
- Transport
- HTTPS, and pages that refuse to be framed
- Strict transport security on every response, no content-type sniffing, and every page except a firm’s own embeddable form refuses to be shown inside another site.
- Our access
- When we sign in as you, it is recorded
- Support can only see a firm’s data by signing in as one of its people, for a limited time, with a reason. The firm’s audit log records it, and some actions — changing money or permissions — stay refused while we are there.
Found something? Write to [email protected]. The companies that process data on our behalf are listed on the sub-processors page, and the terms that govern it are in the data processing agreement.