Bring your own auth. We own the part FHIR never solved: who can see which patient, under what consent, for how long — designed to be enforced on every read and write, with an AuditEvent for each.
You already have an identity provider you trust. What you don't have is a layer that knows a clinician is assigned to this patient, in this tenant, under a consent that's still live — and refuses the query otherwise.
Clerk, Auth0, Cognito, WorkOS — keep whatever issues your tokens and manages users. bonfire reads the verified identity; it never tries to replace your IdP.
Patient–clinician assignment, tenancy isolation, minimum-necessary scoping, and agent permissions. The decisions FHIR's data model leaves entirely up to you.
Every clinical primitive runs through the same authorization gate going in and coming out. There's no GraphQL door, no $export door, no history door that skips it.
FHIR gives you security-labels and a Consent resource — but no engine that enforces them. The model describes intent; nothing acts on it.
So you end up writing the authorization layer by hand — and any path you forget becomes a leak.
// the resource has the right label… { "resourceType": "Observation", "meta": { "security": [ { "code": "R" } // restricted ] } } // …but who actually enforces it? GET /Observation?patient=123 // masked ✓ GET /Observation/$everything // ? POST /graphql { observations {…} } // ? GET /Observation/o-9/_history // ?
Define clinical access once. bonfire enforces it on reads, writes, exports, history, and agent calls — there is no path that bypasses the policy.
clinical.access.policy({ // tenancy: a request can only ever touch one tenant's data tenant: (ctx) => ctx.orgId, // patient scope: clinician must be assigned to the patient canReadPatient: (ctx, patientId) => clinical.assignments.exists({ clinician: ctx.userId, patientId }), // minimum necessary: scope which clinical kinds a role may read minimumNecessary: { front_desk: ["appointments", "demographics"], clinician: ["notes", "assessments", "observations"], }, // consent as a live engine — not just a stored resource consent: (ctx, patientId) => clinical.consent.active({ patientId, purpose: "treatment" }), // time-bound break-glass: emergency access that expires + is flagged breakGlass: { ttlMinutes: 60, requiresReason: true }, });
Every clause runs on both directions. A write that violates tenancy or minimum-necessary is refused the same way a read is — the policy doesn't care which verb you used.
In FHIR, a Consent resource is a document you store. In bonfire it's a live engine: the policy evaluates active consent at query time, and a withdrawn or expired consent closes the door immediately — across every path.
The honest trade-off: FHIR's Consent resource is genuinely expressive — bonfire reads that model rather than inventing a parallel one. What we add is the engine that evaluates it on every request.
Emergencies need access that normal scoping would deny. Break-glass grants it — but it's time-bound, reason-required, and loud: every break-glass access writes an AuditEvent flagged as such, and the grant expires on its own.
// withdraw — closes every path on the next request await clinical.consent.withdraw({ patientId, purpose: "research", }); // emergency access, scoped + expiring await clinical.access.breakGlass({ patientId, reason: "ED admission, charts needed", }); // → audited, flagged, expires in 60m
Azure's FHIR service doesn't auto-write AuditEvents; you have to build that yourself. In bonfire, the audit record is part of the operation. There's no code path that touches a patient and forgets to log it.
Every read, write, export, and agent call emits an AuditEvent with actor, patient, purpose, and outcome — without you wiring it.
Writes carry provenance back to their source. You can answer "where did this value come from" without reverse-engineering it.
Agent reads are audited too — including which records an agent pulled to build its context. Not just "an agent ran."
Because audit is part of the gate, the side doors that leak elsewhere (GraphQL, export, history) are logged here like any other read.
An agent calling a clinical tool is just another caller through the same gate — patient- and tenant-scoped automatically, reads audited (including what it read), and writes are propose-only by default. A human approves before anything touches the record.
Pairs with the custom MCP builder — the same policy that gates your SDK and HTTP endpoints gates your agent tools.
clinical.agent.policy({ // inherits tenant + patient scope from access.policy scope: "per-patient", // agents never write directly — they propose canWrite: "propose-only", // every read is logged with the records touched auditReads: true, // every answer must cite its source record requireCitations: true, }); // agent context is permission-aware + cited const ctx = await clinical.agent.sessionPrep({ patientId, windowDays: 90, include: ["recentNotes", "assessments", "tasks"], });
| Capability | FHIR data model alone | HealthLake | bonfireDB |
|---|---|---|---|
| Patient–clinician scoping | Your job to build | IAM gates whole datastore | Built-in, on read + write |
| Minimum-necessary | Schema only | None | Role-scoped policy |
| Consent enforcement | Resource, no engine | Resource, no engine | Live engine, per request |
| Masking via export/history/GraphQL | Can leak | Coarse / N/A | Same gate, no bypass |
| AuditEvent on every op | Manual | Partial | Automatic |
| Agent writes | Unconstrained | Unconstrained | Propose-only by default |
bonfireDB is early-stage; this page describes product design and positioning. Comparisons reflect each system's stated authorization model, not a benchmark.
Wiring this into a vibe-coded app? How to vibe-code a HIPAA-sensitive app covers where the access gate, consent engine, and audit trail have to live so the parts you generate don't leak PHI.
Bring your auth. Let bonfire own the patient scoping, consent engine, and audit trail — enforced on every path, by default.
Clinical authorization decides who can see which patient, under what consent, and for how long. FHIR® gives you security-labels and a Consent resource but no engine that enforces them, so bonfireDB is designed to own that layer above the FHIR data model, enforced on every read and write.
You define a policy once with patient-clinician assignment, tenancy isolation, and minimum-necessary role scoping. bonfireDB is designed to evaluate it on reads, writes, exports, history, and agent calls so there's no side door (GraphQL, $export, or history) that bypasses the gate.
Yes, by design. Every read, write, export, and agent call is intended to emit a FHIR AuditEvent with actor, patient, purpose, and outcome, because the audit record is part of the operation rather than something you wire up. Unlike services where you build auditing yourself, there's no path that touches a patient and forgets to log it.
As of 2026 (verify current), HealthLake's IAM gates the whole datastore, not an individual patient or field, and consent is a stored resource with no enforcement engine. bonfireDB is designed for built-in patient-clinician scoping, role-based minimum-necessary, and a live consent engine evaluated per request.
Agents are first-class subjects of the same policy: patient- and tenant-scoped automatically, every read audited with the records touched, answers cited to their source, and writes propose-only by default so a clinician signs before anything touches the record.