Security
Data handling posture
Synthetic data only, by design
The Lab is designed for synthetic or properly de-identified FHIR only. Do not paste real PHI into any tool. There are no user accounts, no tracking cookies, and nothing about you to breach.
Data handling by tool
| Tool | What happens to what you paste |
|---|---|
| Validator | Pasted payloads are POSTed to our API and forwarded to our hosted validation sidecar (validator.imonfhir.com, behind Cloudflare Tunnel + Access). Processed transiently in memory with a 1 MB body cap. Payload contents are never written to our database and never logged by our application. Synthetic or de-identified FHIR only. |
| Payload Washer | Runs entirely in your browser. Source payloads are never uploaded to ImOnFHIR servers. It produces synthetic test data — it is not a HIPAA de-identification method. Provider and facility identity, including names, NPIs and addresses, passes through unchanged, and clinical notes are cleared rather than replaced. You must already be authorized to view any source you paste. |
| Payload Compare | Client-side. Makes no network calls for the comparison itself. |
| Library | Static published content. Nothing is submitted or collected. |
Availability note: which tools are switched on differs between the stable and preview channels, and changes as tools ship. What does not change is the handling described above — it is a property of how each tool is built, not of where it is enabled. A tool that runs in your browser does so on every channel.
We test the client-side claim, not just assert it
“Runs in your browser” is easy to write and hard to trust. So it is enforced by an automated network-silence test that runs in CI on every change. It watches every request the browser makes across the whole page lifetime and fails the build if a tool reaches somewhere it should not.
- Payload Washer and Payload Compare must reach no third party beyond the anonymous counters described below, and must make no call to our own API at all while washing or diffing. They still load their own page assets, as any page does — what they never do is send your payload anywhere.
- Validator is held to a narrower bar, because it genuinely does send your payload to us: no third-party requests, and payload traffic confined to our own
/api/validator/*endpoints.
This sits on top of a Content-Security-Policy that already blocks scripts from opening connections to other origins. The two layers cover different gaps: the policy stops connections outright, while the test also catches the cases a policy cannot express — an image-based beacon to an allowed host, or a tool quietly calling our own backend when it claims not to. The test includes a deliberate self-check that stages such a beacon and confirms the detector still sees it, so these results cannot pass by accident.
Scope, stated plainly: the test observes the browser. It proves the Washer and Compare send your payload nowhere — not to us, not to anyone else — and that the Validator sends it only to us. It does not — and cannot — describe what happens after your payload reaches our API; that server-side path is the row above, and the anonymous counters below are excluded by design.
What our servers reach, and what is in it
The section above is about your browser. This one is the other half: the hosts our servers contact while handling a request. It is the part a browser-side test cannot see, so it is written down instead. Six rows below: two are our own infrastructure; three are public registries; and one is the committed endpoint register (15 hosts, listed in full).
| Destination | Why we call it | Carries your payload? |
|---|---|---|
| validator.imonfhir.com | Our own validation sidecar, behind Cloudflare Tunnel + Access. This is where a Validator run is actually performed. Preview deployments use validator-preview.imonfhir.com — the same architecture on a separate, dedicated stack; production servers never contact it. | Yes |
| fhir.imonfhir.com | Our own FHIR server, for the hosted Lab tools that project synthetic fixture data into an expiring workspace. Preview deployments use fhir-preview.imonfhir.com — same architecture, separate stack; production servers never contact it. | No — synthetic fixtures only |
| packages.fhir.org | The official FHIR package registry. We download the published implementation guides your payload is validated against, and cache them. | No — we send a package name and version |
| npiregistry.cms.hhs.gov | The public CMS NPPES registry, for provider NPI lookups. Currently enabled on the preview channel only. | No — we send an NPI, which is public provider data |
| packages2.fhir.org | HL7's cross-IG index (XIG), for the corpus census on /developers/packages. A nightly CI job runs four pre-committed filtered queries plus the index's own stats page, keeps the COUNTS and our catalog's share, and discards the records; the served application reads those committed bytes and contacts it never. | No — an unauthenticated GET with four fixed filter values |
| api.medplum.com · bulk-data.smarthealthit.org · cts.nlm.nih.gov · hapi.fhir.org · hts.heliossoftware.com · jade.phast.fr · lforms-fhir.nlm.nih.gov · ontology.nhs.uk · r4.ontoserver.csiro.au · r5.ontoserver.csiro.au · server.fire.ly · spark.incendi.no · test.fhir.org · tx.fhir.org · wildfhir4.wildfhir.org | Public FHIR sandboxes and terminology servers in the committed endpoint register. A nightly CI job asks each for /metadata, and separately for /metadata?mode=terminology to record which code systems it declares; both results are committed and the served application reads those bytes and contacts none of them. A reader may also trigger a single live re-check — that route asks for /metadata ONLY, and resolves the host from the committed target list by id, never from anything the caller sends. Preview channel only while the register is gated. | No — unauthenticated, anonymous GETs sending nothing but the request |
The honest boundary, since the row above claims something about a machine and not just about code: only the Validator sends your payload anywhere, and the only place it goes is our own sidecar. Within our application we can state flatly that payload contents are never written to our database and never logged — that is a property of code you could audit. What we do not claim is perfect knowledge of every log line the third-party validation engine inside that sidecar may emit while processing a malformed resource. Treat the Validator as “your payload reaches a machine we run,” which is why the synthetic-data-only rule is the one that actually matters.
Anonymous usage metrics
We count coarse, anonymous events — for example, “a validation ran” — via Vercel Analytics. No cookies, no user identifiers, and never anything about or derived from payload contents.
The public pages — everything outside the Lab — additionally use Google Analytics 4, configured cookieless: client_storage is ‘none’, so no _ga cookie is written and no persistent identifier is kept. Google Signals and ad personalization are off, and the Content-Security-Policy names no advertising endpoint at all — so an ad tag would be refused by your browser rather than merely switched off in a settings page we ask you to trust.
Google is not loaded on any Lab tool. The Validator, Washer, Compare and Workbook pages never contact it, and neither does /licenses. That is a property of where the script is mounted, not a rule we remember to follow — the network-silence test described above still classifies any Google request as third-party egress, so it fails the build if one ever appears on a payload tool. The URL we report is rebuilt from a list of known public routes with the query string and fragment stripped, so a page address can carry nothing back.
The compromise, stated rather than buried: cookieless means we cannot tell a returning visitor from a new one, so our visitor counts are inflated and our session numbers are unreliable. We would rather have worse numbers than a tracking cookie. Full detail on our privacy page.
Subprocessors & infrastructure
| Provider | Role |
|---|---|
| Vercel | Application hosting, edge/network, anonymous analytics |
| Google Analytics 4 on the public pages only — cookieless, no advertising features. Not loaded on any Lab tool. | |
| Neon | PostgreSQL database — synthetic lab data only |
| Cloudflare | DNS; Tunnel + Access in front of the validation sidecar |
| DigitalOcean | VPS hosting the validation sidecar |
| GitHub | Source hosting, CI, dependency scanning |
| Google Workspace | Email for imonfhir.com contact addresses |
| UptimeRobot | Uptime monitoring of public endpoints |
Vulnerability disclosure
Report security concerns to security@imonfhir.com — we aim to acknowledge reports within two business days. For general questions, use contact@imonfhir.com. A machine-readable contact is published at /.well-known/security.txt. Please demonstrate vulnerabilities against synthetic data only, and give us a reasonable window to investigate before public disclosure.
Going through a security review?
Send your security team the short version — we made them a page. It has the exact domains to allow, the data-handling summary, and a one-click way to reach us.
Shareable link: imonfhir.com/security/review