Skip to content

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.

Security — how one firm is kept from another · Veilux