LymeHQ · Security

Enforced in the database, not promised in a policy

The protections that matter here are the ones a forgetful engineer cannot skip. This page says where each one lives, names the script that proves it, and is equally specific about what we will not claim.

Demo phase

LymeHQ is in demo and holds no real patient data. Every account is synthetic and every record is invented. The code is written to production standards — the intention is that only environment variables change — but nothing on this page should be read as a statement that a compliance review has happened, because it has not.

01 · Where the rules live

The database decides, not the application

The difference matters because application code can forget.

Most platforms enforce access in the code that answers a request: a function checks who you are and filters the results. That works until someone writes a second function. Here the rules are attached to the tables themselves, so a query that forgets to filter gets nothing rather than everything.

Of 33 tables, 22 carry row-level security — enabled and forced — under 41 policies, plus one policy governing uploaded lab files in storage. Identity resolves through a single database function that reads the signed session token, so every policy is asking the same question about the same person. The remaining tables are not exposed: the permission to read them was never granted to anyone holding a login.

  • A clinician needs two separate things before they see anything, and you hold both.

    An active connection you created, and sharing switched on. The database requires both in the same policy — it is not a check our code could forget, and there is nothing on LymeHQ that lets a clinician request, claim or be assigned a record.

    Proved by npm run check:provider · npm run check:care

  • Turning sharing off cuts every clinician off on their next page load.

    No grace period and no cached render. The provider pages are rendered per request and the database re-evaluates the rule on every query.

    Proved by npm run check:provider — its selftest feeds the assertion a cached render

  • Researchers never receive an individual record.

    Not filtered out — absent. There is no policy that would give a research account a row of anyone’s health data, so the answer to every such query is nothing.

    Proved by npm run check:research · npm run db:rls-test

  • Every sharing choice starts off, and the record of your choices is written by the database.

    Eight toggles, all of them false by column default, with a trigger creating the row so that "absent" and "all off" can never be confused. Changing one appends an event that no update or delete can touch.

    Proved by node scripts/check-consent.mjs — a raw SQL update, with no application code in the loop

What a database rule cannot do

A row rule answers may this person read this row. It cannot answer may this data leave — and those are different questions. Sending your record to an AI model, or composing a summary for a clinician, is a disclosure rather than a read, so it is gated separately in code that will not compile if a new route forgets it. Row rules are also blind to columns, which is why the right to write a particular field is granted column by column rather than table by table.

02 · The audit trail

A log that accepts new rows and nothing else

Including from us.

Access to health data is recorded to a log that database triggers refuse to update or delete. The refusal is not a permission setting that an administrator can lift for an afternoon — the triggers throw, and they throw for our own service credentials too. The same mechanism protects the record of your consent decisions.

Where that guarantee stops

Not every entry names a person. Unauthenticated attempts are logged too, and there is no one to name. Denied requests are recorded at the network edge, where there is no database connection to look anyone up, so those rows carry the reason and the path rather than an account.

The log records what the application tells it. A database cannot fire a trigger on a read, so recording an access is something the code does deliberately. There is exactly one function that writes to the log, which means there is one place to check rather than a discipline spread across the codebase — but it is discipline, and we would rather say so than imply the database is watching every query.

03 · The research commons

No group smaller than five ever leaves

A group small enough to count is a group small enough to identify.

Research accounts do not query patient records; they query a pre-computed table of aggregates. Combinations describing fewer than five people are never written into it, the function that releases cells re-applies the same floor rather than trusting the table, and the direct route to the aggregates is revoked so that the function is the only way in. There is no setting that turns the floor off — the one column that looks like a switch is read by nothing, and the release function raises whatever a dataset asks for but will not go below five.

Five, or nothing.

A group small enough to count is a group small enough to identify.

The five-patient floor on released research aggregatesResearch results are released only for groups of five people or more. Groups are drawn in descending size: groups of twelve, eight and six people clear the five-patient floor and are released as aggregate counts, while groups of four, three and two fall below it and are held back. Nothing smaller, rounded or blurred is returned in their place — the cell is simply absent from the result. The floor is enforced in the database rather than in application code, and the sizes shown illustrate how the rule behaves rather than describing any real cohort.PEOPLE MATCHING A QUERY1286432The five-patient floorRELEASEDHELD BACK
  • Released as an aggregate
  • Held back — too few people

A group under five does not come back smaller, or rounded, or blurred. It does not come back at all — the cell is simply absent from the result.

Enforced in the database, so no query can reach around it.

The group sizes on this drawing illustrate how the rule behaves. They are not counts of anyone in the research commons, and none of them describes a real cohort.

We test this floor on every build: a group of fewer than five people is put into the underlying records, and it must appear in no released result. The group sizes drawn here illustrate the rule — they are not counts of anyone real.

Location is reduced to the first three digits of a postal code before it is stored at all, and seventeen sparsely-populated ones collapse further to a placeholder at the point of de-identification. Ages of ninety and over are grouped. Only patients who have switched research sharing on are included. What that means for study design is a separate page.

The honest limit

The aggregate table is rebuilt on a schedule rather than continuously. Your data enters the commons only while research sharing is switched on, and leaves at the next rebuild — not the instant you flip the toggle. Everywhere else on the platform revocation takes effect on the next request; this is the one place it does not, and it is a property of how the aggregates are computed rather than a policy choice.

04 · Compliance

What “HIPAA-track” means, and what it does not

The distinction is deliberate and we hold to it everywhere.

HIPAA-track means the system is being built so that becoming compliant is a matter of signing agreements and flipping environment flags rather than rewriting the data layer: the access controls, the audit trail, the de-identification boundary and the minimum necessary disclosures are all in the code now, on synthetic data. It does not mean the platform is HIPAA compliant. Compliance requires signed agreements with every vendor that could touch health data and an independent security assessment, and neither exists yet. Until they do you will not find a compliance badge anywhere on this site, and the absence is deliberate rather than an oversight.

Two behaviours differ between demo and production and are marked as such in the code: the session inactivity timeout is tracked but not enforced, and the additional sign-in step for administrators warns rather than blocks. Both are switched on by a single flag before any real account exists.

05 · Restraint

What we will not claim

The tempting claims are the false ones, and they are false in ways you cannot check.

  • “HIPAA compliant”

    We hold no signed business associate agreements and no independent security assessment has been done. Until both exist, the badge would be decoration. LymeHQ is built to the HIPAA standard — that is a description of how the code is written, not a certification.

  • “Encrypted end to end”

    It is not architecturally true of this system, and saying it would be theatre. We would rather tell you what is actually enforced and let you check it.

  • “Even we cannot see your data”

    False of almost every platform that says it, and false here. Some operations — cron jobs, webhooks, writing the audit log itself — run with credentials that bypass row-level security by design. What is true is that every request made on your behalf runs as you.

  • “Delete your data any time”

    Deletion is real and it is a request, not a switch: ask us and the record is erased — every row, every file, every stored credential — as one audited operation. What we will not promise is the phrase itself. “Any time” implies instant and self-serve, and this runs when a person runs it; “your data” implies the evidence goes too, and it does not. The consent trail and the access log are append-only and stay, attached to a tombstone that names nobody.

  • Any number we cannot source

    No breach counts, no uptime figures, no compliance scores. A statistic on a security page is the thing people quote, so it ships with a source or it does not ship.

If you find something wrong

Security reports go to security@lymehq.com. LymeHQ is built by one person, so replies come from a person rather than a queue and may take a few days. If you find a page that claims more than this one does, that is a defect and we want to hear about it.