Separation you can explain to your customers
Multi-tenancy here is a property of the data model, not a filter someone has to remember to add. Every record belongs to an account, and every lookup runs through that account.
Tenant isolation
Every contact, deal, appointment, file, comment, form and API key carries an account. Records are never fetched by bare identifier — they are always looked up through the account the signed-in user is currently working in, so an identifier belonging to another account simply is not found.
The rule is enforced through shared helpers rather than repeated by hand in each controller, and cross-account access is covered by a shared test contract every controller is held to.
Roles, and permissions below the role
Four levels — member, admin, owner and account owner — with a single ordering used everywhere, so there is no second opinion about what a role can do. Nobody can promote another person above their own level.
Underneath that sits field-level permission: an admin can require a minimum role for a specific field, such as deal value or a contact's phone number. Below that role the field disappears from forms, list data, the filter builder, the activity timeline, and the parameters the server will accept — not just from the screen.
Secrets and credentials
Integration tokens — Gmail, Calendly, CAPTCHA keys — are encrypted at rest rather than stored as plain columns. API keys are never stored at all: only a keyed digest is kept, so a database dump yields nothing usable, and the key itself is shown once at creation.
Revoking a key stops it authenticating immediately, and every API write is attributed to the key that made it in the record's history.
Uploads
Every uploaded file is checked against a content-type allowlist by inspecting its actual bytes rather than trusting the declared type, capped at 25 MB, and then scanned for malware in the background.
A detection quarantines the file for review rather than silently deleting it, and raises an alert. Files left unattached by an abandoned upload are cleaned up automatically.
The rest of the surface
Account protection
Password complexity requirements, lockout after repeated failed attempts, idle session timeout, and email confirmation before an account becomes usable.
Invitation-only access
There is no open registration. People reach an account only through an invitation from someone already in it, and deactivating a member revokes access on their very next request.
Verified webhooks
Inbound integration callbacks must arrive from the provider's published address range and carry a valid signature, checked in constant time within a short replay window.
Escaped output
Values that customers can supply — including through public forms — are escaped on the way out. Link fields are restricted to http and https so a form cannot plant a script URL.
Content Security Policy
The application ships a content security policy restricting where scripts, styles, fonts, frames and network calls may come from.
Patched dependencies
The dependency set is checked against published advisories on every build, and the build fails on a known vulnerable package.
Questions your security review will ask?
Bring them. We would rather answer the hard ones up front than discover them at procurement.