Security by design: how we protect the data in our apps

Every app we build stores something that matters to someone: a customer's contact details, a patient's intake form, a business's login to another service. Keeping that safe is not a feature we add at the end. It is part of how the app is built from the first day.
This post describes the practices built into OwnSites, our website platform, which also runs our customers' sites. Everything here is something the code actually does today. Where we do not do something, we say so.
How is data protected on its way to you?
Every page of the app, and every customer site on its own domain, is served over HTTPS with certificates that renew automatically. Browsers are told to always use HTTPS, plain-HTTP requests are redirected, and sign-in cookies are marked secure so they never travel unencrypted.
How is sensitive data stored?
- Sensitive fields are encrypted inside the database. The medical details in patient records (allergies, conditions, medications, insurance, date of birth), clinical notes, appointment reasons and notes, text-message bodies and call records are encrypted field by field. Intake answers are never stored in our database at all: they go only into the signed PDF.
- Many secrets we hold for you are encrypted too, including passwords for third-party portals, API keys for health-record systems, calendar tokens and two-factor secrets.
- Passwords are never stored. We keep a one-way bcrypt hash, and every password must be at least 12 characters.
- Files are private. Uploads are stored privately in Amazon S3 and reached through links the app signs, not public bucket addresses. Signed patient documents go to a separate bucket set up with public access blocked, versioning and a retention lock.
- Logs stay clean. Passwords, tokens, health fields and intake answers are filtered out of our application logs.
Who can get in?
- Two-factor sign-in. Our own admins must use an authenticator app and confirm a fresh code at least once a day. Customers can turn two-factor on in their account settings, and once they do, opening patient records, clinical notes or billing in the dashboard asks for a fresh code.
- Every request is scoped to its owner. Each query is limited to the signed-in customer's own sites, and automated tests check that one customer cannot reach another customer's site.
- API keys are shown once. We store only a fingerprint of each key, any key can be revoked at any time, and admin tools accept only narrowly scoped keys that need two-factor to create.
- Sessions expire. Dashboard sign-in tokens last a day and refresh tokens a week.
How do we keep bots and abuse out?
Sign-ins, contact forms, reviews, newsletter sign-ups, bookings and intake steps are rate-limited per visitor. Public forms carry hidden checks and a signed, time-limited token that stops automated spam, and a CAPTCHA can be switched on for any form type that starts to attract it.
Can you see what happened, and when?
Sensitive admin actions go into an audit log that cannot be edited or deleted. We keep a permanent record of which version of our terms each customer accepted. Every signed intake document comes with a signing certificate showing when it was sent, opened, verified and signed. Site versions are saved, so a change can be rolled back.
Code changes are scanned for security problems as they are written, and every change to our Ruby dependencies is checked against known vulnerabilities.
What we don't claim
We do not hold SOC 2 or ISO certification, and we do not describe OwnSites as "HIPAA compliant". Two-factor is optional for customers rather than forced. Tokens for connected social accounts are not yet field-encrypted. Not every piece of data is field-encrypted: site content and form submissions travel over encrypted connections but are not encrypted field by field. We would rather tell you exactly what is in place than promise something we cannot show.
If you are planning an app that will hold sensitive data, ask whoever builds it the same questions this post answers. Good answers are specific.