Data security
The engineering answer, concrete enough to argue with.
The HIPAA statement is about obligations. This is about mechanisms. It is written for the engineer or security reviewer on your side, and it is deliberately specific enough that they can tell us we are wrong.
One database, and the boundary inside it#
Every tenant lives in one Postgres database, separated by row-level policies rather than by separate databases. That is a real trade: it is cheaper and simpler to operate, and it means the isolation is only as good as the policies.
So the policies are treated as the product, not as configuration. The rule that matters: a query must prove its tenant from its own filters. A policy that checks a column the query never filters on does not refuse the query, it silently returns the rows that pass, which arrives as a perfectly ordinary success. That failure is invisible to any test that only checks for errors, so it is tested by asking for another tenant's data as an ordinary user and requiring a refusal.
Sign-in and sessions#
Authentication is handled by the database platform's own identity service. Passwords are never stored by us in any form we could read. Sessions are held in the browser as a token with a short life and a refresh, and signing out ends them.
Releasing a report is a second gate: it requires re-authentication at the moment of signing, and a stale proof fails rather than being accepted. That is the point of an electronic signature, and a signature that never fails is decoration.
Roles are enforced by the server, never by the interface#
Hiding a button is a courtesy to the person using the product. It is not a control, because the request behind the button can still be made. Every permission check therefore exists twice: once in the interface so the product is comprehensible, and once on the server where it is the actual boundary.
Two rules follow, and both exist because their absence is a common and quiet failure. A role is never writable by the account it describes, so nobody can promote themselves by editing their own record. And a permission is read from the server's view of the account rather than from anything the client sent with the request.
Encryption, and the claim that is not made#
Traffic between your browser and the service is encrypted in transit. Data at rest is encrypted by the database and storage platforms.
LabFlow is not end-to-end encrypted, and it cannot be. A laboratory system has to search results, evaluate quality-control rules across runs, and produce reports, all of which require the server to read the data. A vendor claiming both end-to-end encryption and server-side clinical logic is claiming two things that do not fit together, and it is worth asking which one is true.
Keys and secrets#
No credential is committed to source, and no server-side key is ever shipped in the browser bundle. Keys that a browser must carry are restricted to the origins allowed to use them, so a copied key does not work from anywhere else. Keys are rotatable without a code change, which is the property that decides whether a rotation actually happens.
Backups, and the honest state of them#
Backups are whatever the database platform's free tier provides, on its schedule, with its retention. There is no independent backup under our own control, no tested restore procedure, and no stated recovery point or recovery time.
For a laboratory this is a material limitation and it should be weighed as one. A laboratory that needs a tested restore with a stated objective does not have it here, and adding it costs money, which is the reason it does not exist rather than an oversight.
What leaves in a log or an error report#
An error report carries the class of the error, the route pattern it happened on, and whether it was handled. It does not carry the request body, the query string, breadcrumb payloads or user context beyond an opaque identifier.
That is done at the reporting boundary with an allowed-list rather than a blocked-list, and the difference is the whole point: a blocked-list ships the one field nobody thought of, which during an incident is usually the entire response body somebody logged in a hurry.
The audit trail#
Every clinical action writes a row: who, what, when, from where, in the same transaction as the change itself, so a change cannot exist without its record. No application role holds update or delete on that table.
Reads are recorded too where the record is a patient's. That is unusual and it is deliberate: a question about who looked at a record is the one an investigation asks first, and a trail that only records writes cannot answer it.
Kept for six years. Amendments never overwrite: a corrected result is a new version, and the value a clinician originally acted on stays readable.
Dependencies#
The product runs on a small number of well-used packages, kept at current stable versions, with an inventory in the repository recording what each one is for. New dependencies are added rarely and deliberately, because every one of them is a party you did not choose and cannot see.
Reporting something you have found#
Send it through the contact page with enough detail to reproduce it. You will get an acknowledgement from a person, an assessment, and a note when it is fixed. There is no bug bounty and no payment, which is stated so nobody spends a week expecting one.
Please do not test against a live tenant holding real patient records. Ask and a test tenant will be created; that request is welcome and it is the fastest route.
What is not in place#
No independent penetration test. No bug bounty. No SOC 2 or ISO certification. No security operations centre and no on-call rotation, so out of hours there may be nobody awake to answer. No formal secure-development lifecycle beyond the review that happens on every change. No tested disaster recovery, per clause 06.
These are listed rather than omitted because a security page that lists only controls is telling you about the author's marketing, not about their system.