← All articles

Privacy by design, not by retrofit

On a healthcare platform, we didn't add security at the end. It shaped the architecture from day one — and that order of operations is the whole point.

There’s a moment in many projects where someone says “we should probably encrypt that,” and it’s usually too late for the answer to be good. I worked on a secure healthcare platform where that conversation happened before the first line of backend code — and the difference it made changed how I think about security entirely.

Database encryption is where the thinking starts, not ends

The default posture — encrypt the database, call it done — protects data at rest. But it leaves plaintext exposed to anything that can reach the database: application bugs, over-broad queries, internal tooling. For a platform handling highly sensitive healthcare information, that residual exposure was unacceptable.

So the design went further: multiple categories of personal data were encrypted at the application layer, before they ever reached the database. Defense in depth, past the database boundary. Even something with direct database access sees only ciphertext.

The decision has a price, and you pay it everywhere

Application-level encryption isn’t a free upgrade. The difficult part was carrying the security boundary through the whole interaction, not just encrypting a database column.

At intake, the Seeker PWA encrypted the payload with an X25519 provider public key. The API handed the envelope to a BullMQ worker and returned immediately, so decryption and persistence could happen asynchronously. After authentication, the client derived a key-encryption key with Argon2id and kept the decrypted patient private key in volatile runtime memory — never in localStorage or sessionStorage. Real-time patient/provider messaging used the same asymmetric key exchange, while a master key protected persisted records.

The root setup also had to be explicit: a BIP39 mnemonic seeded the provider keypair, and the master password derived the KEK used to protect the provider private key. Tenant onboarding provisioned isolated provider and patient-facing applications, while the central platform database retained deployment metadata rather than patient PHI.

The other cost — and the one that surprises people — is that you lose the ability to query what you encrypted. You can’t WHERE email = ? a ciphertext column. The answer was deterministic SHA-256 hashing: hash the sensitive value on the way in, and equal inputs produce the same hash, so you can do indexable equality lookups without exposing plaintext. The honest trade-off: deterministic hashing leaks equality — identical values produce identical hashes. We accepted that where lookups were genuinely required, with the exposure understood.

Security stops being a module

Once you commit to this, security requirements propagate outward into places that don’t look like “security work”: API contracts, the data model, validation flows, async queues, logging rules (no sensitive data in logs — ever), least-privilege access, and client key lifecycle. Security became a design driver across the backend and frontend, not a checklist at the end.

I participated in implementing and maintaining these mechanisms as part of the backend and full-stack engineering effort, and the lasting shift for me is simple: security is an architectural requirement, decided up front — not a feature added at the end. Retrofitting privacy into a mature system is a crisis. Designing for it from day one is tractable.

It also reframed “done” for me. On sensitive-data work, a feature isn’t done when it works. It’s done when it works and the data it touches is protected by default.

↑↓ navigate · open · esc close