Clinical authorization & audit

The current core enforces practice, resource-type, role, and purpose policy with Postgres RLS, governed proposals, and append-only audit receipts. Patient assignment, live consent, break-glass, and every-surface enforcement below are the target contract — not shipped capabilities yet.

The distinction that matters

Authentication isn’t the hard part. Clinical authorization is.

You already have an identity provider you trust. The target clinical layer must also know whether a clinician is assigned to this patient, in this practice, for an allowed purpose, under consent that is still live — and refuse the query otherwise.

Bring your own auth

Clerk, Auth0, Cognito, WorkOS — keep whatever issues your tokens and manages users. bonfire reads the verified identity; it never tries to replace your IdP.

Current core

Practice isolation, resource-type/role/purpose policy, Postgres RLS, propose-only governance, and audit receipts.

Target contract

Patient assignment, consent, minimum-necessary fields, and the same decision on reads, writes, export, history, search, and agent calls.

FHIR is a data model, not an access-control system

FHIR gives you security-labels and a Consent resource — but no engine that enforces them. The model describes intent; nothing acts on it.

  • Security-label masking leaks through the side doors — GraphQL, $export, and history can return what the read API hid.
  • Masking is usually a read concern; the write path is often left ungated — so a blind PUT can clobber a field a concurrent writer just changed (a lost update), and nothing checked whether the caller was allowed to write that patient at all.
  • Consent is a schema with no enforcement engine — storing a Consent resource doesn’t stop a single query.
  • HealthLake has no per-resource ABAC: IAM gates the whole datastore, not a patient or a field.

So you end up writing the authorization layer by hand — and any path you forget becomes a leak.

without an engine
// 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     // ?
One gate, every path

Authorization is a function of the request, not a property of the response

Define clinical access once. bonfire is designed to enforce it on reads, writes, exports, history, and agent calls — no path that bypasses the policy.

policy.ts
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 },
});

Target API: every clause runs in both directions. The current policy does not yet carry patient assignment or consent attributes, so this example is a contract for the next authorization slice.

Target: consent that does something

In FHIR, a Consent resource is a document you store. The Bonfire target is a live engine that evaluates active consent at query time so a withdrawn or expired consent closes every governed path. This is not in the current core.

  • Consent is evaluated per request, not assumed because a resource exists.
  • Withdraw consent and the next read fails closed — including $export and history.
  • Purpose-of-use is part of the decision (treatment vs. research vs. billing).

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.

Target: time-bound break-glass

Emergencies need access that normal scoping would deny. The planned break-glass contract is time-bound, reason-required, and loud: every access produces a flagged audit receipt, and the grant expires on its own.

consent & break-glass
// 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
Audit you don’t have to remember to write

Current receipts, with every-surface AuditEvent coverage as the target

The current core writes append-only, hash-chained receipts for implemented operations, and implemented search/context reads return citations to canonical records. Automatic FHIR AuditEvent and Provenance coverage across every future surface is the target contract.

Audit receipts today

The core has append-only, hash-chained audit receipts for implemented operations. Automatic FHIR AuditEvent coverage across every future surface is the target.

Target: Provenance

The target attaches FHIR Provenance to governed writes so source lineage is queryable. This is not yet implemented in the current core.

Citations today; read audit next

Implemented search/context packets cite the canonical records used. Recording the full pulled-record set in a standardized AuditEvent is target work.

Target: no bypass

GraphQL, Bundle export, and complete history-surface enforcement are not shipped. The target routes them through the same policy and audit gate.

Agents are first-class subjects of the policy

The current MCP surface is immutable and propose-only: an agent drafts, and a human approves before commit. It inherits current practice/resource/purpose policy. Automatic patient/consent scoping and complete read-audit coverage are the next contract.

  • Current tools are practice/resource/purpose-scoped; patient/consent scope is planned.
  • canWrite: “propose-only” — the agent drafts, a clinician signs.
  • Search/context results carry citations; complete every-read audit coverage is target work.

Pairs with the agent context tools. Today the MCP allowlist uses the current practice/resource/purpose policy; one shared policy across SDK, full HTTP, exports, and agents is the target.

target pseudocode · agent.policy.ts
// Target API — patient scope and complete read audit are not shipped
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"],
});
Side by side

What “enforced” actually means

CapabilityFHIR data model aloneHealthLakebonfireDB target
Patient–clinician scopingYour job to buildIAM gates whole datastoreBuilt-in, on read + write
Minimum-necessarySchema onlyNoneRole-scoped policy
Consent enforcementResource, no engineResource, no engineLive engine, per request
Masking via export/history/GraphQLCan leakCoarse / N/ASame gate, no bypass
AuditEvent on every opManualPartialAutomatic
Agent writesUnconstrainedUnconstrainedPropose-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.

Where this fits

Authorization is the spine the rest of the backend runs through

App-native primitives

Target: every typed clinical function runs through one access policy.

Explore →

Agent context tools

Core: practice-scoped tools with citations and propose-only writes; full read audit is target work.

Explore →

Cited search

Core: policy-filtered search results cite canonical records. Patient/minimum-necessary filtering is target work.

Explore →

FHIR underneath

Target: planned Bundle export flows through the same policy and audit gate.

Explore →

Wiring this into an app you’re building with AI tools? 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.

You build the app. Bonfire is the clinical data layer underneath.

Bring your auth. Help us build the next layer: patient scoping, live consent, and audit coverage enforced across every governed path.

FAQ

Frequently asked questions

What is clinical authorization and why doesn’t FHIR handle it?

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.

How does bonfireDB do per-patient access control (ABAC)?

Per-patient assignment and consent enforcement are not shipped yet. The current core is practice-scoped with resource-type, role, and purpose decisions plus Postgres RLS. The target adds patient assignment, consent, minimum-necessary fields, and one decision across every surface.

Does bonfireDB log an audit trail automatically?

The current core writes append-only, hash-chained audit receipts for implemented operations. Automatic FHIR AuditEvent and Provenance coverage across every read, write, export, history, and agent surface is the target contract.

How is bonfireDB different from HealthLake for access control?

As of 2026, HealthLake’s IAM gates the whole datastore, not one 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.

How does bonfireDB constrain what AI agents can read and write?

The current MCP allowlist uses practice/resource/purpose policy, returns cited search/context results, and makes writes propose-only so a human approves before commit. Automatic patient/consent scoping and complete every-read audit coverage are target work.