All work

Platforms & Products

The White-Label Telehealth Portal

One codebase, many clinics, and a rule that a tenant may differ in its branding and in nothing else.

Architected and builtNext.jsPostgreSQLWebRTC / TURNOpenBao TransitAnsibleHIPAA Security Rule
  • One core codebase. A clinic differs in branding and in nothing else, so a security fix reaches all of them in one deploy.
  • No patient data on the media host. Compromising the relay gets you a busy relay and nothing to read.
  • One clinic per machine, so a bad day is one clinic's bad day.

The problem

Health records are the other end of the sensitivity scale from a password, and they behave differently. A password is rotated in a minute. A diagnosis is permanent, it belongs to somebody who did not choose to be in your database, and it is worth more on a criminal market than a card number because it cannot be cancelled.

The temptation in white-label work is to fork per client. Six months later you have six codebases, five of them behind on patches, and no idea which. So the rule here is narrow and enforced: a tenant may differ in brand and theme. Not in logic, not in schema, not in the security model.

Where the video actually goes

Realtime media runs on its own host with its own relay infrastructure, deliberately separate from the application tier. A video relay is a machine whose whole job is to accept connections from strangers on the internet, which is exactly the profile you do not want on the same box as the patient record.

So the media host carries no patient data. It moves packets between two parties who have already been authenticated somewhere else. Compromising it gets you a busy relay and nothing to read.

One clinic, one machine

Same call as the tax platform, and for the same reason. A shared database holding several clinics' patients means one authorization mistake exposes all of them, and the notification obligation is per patient, per state. Physical separation costs more and it means a bad day is one clinic's bad day.

Standing up a new clinic is one command: its own key namespace, its own service credentials, its own policies. Nothing is copied by hand, because anything copied by hand eventually gets copied wrong.

tenant provisioning, per clinic:

  transit/<tenant>/            isolated key namespace
    keys/portal                field encryption
    keys/documents             blob encryption
    keys/esign                 signature material

  policy/<tenant>-portal       read + encrypt/decrypt, that namespace only
  auth/approle/<tenant>-portal service credentials, per service

  result: a credential stolen from one clinic decrypts
          nothing belonging to any other clinic.
What provisioning a tenant creates, in one idempotent run. Every clinic gets its own key namespace and its own credentials, so a credential lifted from one tenant opens nothing belonging to another. Re-running it changes nothing, which is what makes it safe to run when you are not sure whether it was run.

Consent is a record, not a checkbox

Intake, consent packets and visit documentation are signed through the same self-hosted signature service the tax platform uses, so the consent artifact and its audit trail stay inside the tenant boundary. When somebody asks two years from now what a patient agreed to and when, the answer is a sealed document with a hash chain behind it, not a row in a table that anyone with database access could have written yesterday.

Engineering decisions

The calls I made, and what each one cost.

One core repository. Tenants differ in brand and theme only.

A fork per client is how you end up with five codebases behind on patches and no register of which. The constraint is annoying exactly once per feature request, and it is the reason a security fix reaches every clinic in one deploy.

Media on its own host, carrying no patient data.

A relay accepts connections from anyone. That belongs on a machine where the worst case is a busy relay, not on the machine holding the records.

Per-tenant key namespaces, not one key with tenant tags.

A tag is a field somebody can be tricked into changing. A separate namespace with its own credential is not. The isolation should survive a bug in my own code, because eventually there will be one.

More systems

I'm looking for Incident Commander and SRE roles.

If your team is drowning in toil, alert noise, or incidents that never quite close. That is the work I do.

Get in touch