Security

The controls a clinical operations buyer actually asks for.

Tenant isolation, role-based access, append-only audit history, secure patient links, and PHI-conscious notifications are built into Rosiflow's core. This page sets out the security model and the joint operational + legal posture each clinic adopts before going live with PHI.

Security model

How Rosiflow handles protected health information.

Rosiflow ships the technical primitives for handling protected health information: row-level security, append-only auditing, role-based access, encrypted transport, and PHI-conscious notification design. HIPAA coverage is a joint operational + legal posture between Rosiflow and the covered-entity clinic. The deployment checklist at the bottom of this page sets out what each side completes before a clinic uploads live PHI.

Tenant isolation

The database is the boundary.

Row-level security on every tenant table
Policies are written against two security-definer helpers, `is_org_member(uid, org_id)` and `has_org_role(uid, org_id, role)`. Neither is callable by the anon role.
Service-role key is server-only
The Supabase service-role key never reaches the browser bundle — enforced by TanStack Start's import-protection plugin at build time.
Clinic A cannot read Clinic B
Through any public surface — server fn, REST endpoint, public route — a user in one org cannot read, write, or count rows in another's data.
Per-document scope discriminator
`document_scope` on chunks is `patient` or `clinic`, both bound to the same `organization_id`. A clinic's SOPs can never bleed into another clinic's patient briefings.
Authorization

Six roles enforced at every layer.

Every clinic operation is gated by one of six named roles. Permissions are enforced at the database through security-definer helpers — not just in UI.

Access matrix · six roles · DB-enforced
RLS active
Surface
Owner
Admin
Provider
Nurse
Intake
Front-desk
Patient record (read)
Patient record (write)
Briefing (acknowledge)
Audit log (view)
Roles / billing
Delete (any record)
Owner
Everything within this org. Cannot cross into another clinic or do platform-level admin (that's superadmin, scoped separately).
Admin
Team, billing, intake templates, automation rules. Cannot remove the last owner.
Provider
Patient operations, briefings, schedule. No access to team / billing / settings.
Nurse
Patient operations, intake, documents. No access to team / billing / settings.
Intake staff
Intake queue, portal-link generation, patient adds. No access to team / billing.
Front desk
Schedule + check-in flow only. Doesn't see patient ops, analytics, or audit.
Audit

Append-only. No delete UI.

Every state-changing server function writes one audit row. Support sessions write rows too — including the close event with the count of attempted writes.

Audit timeline · last 60 min · append-only
No delete UI
9:02
Dr. Park
Briefing acknowledged · M. Thompson
9:14
intake_staff_2
Intake invited · J. Demo
9:31
ocr_service
OCR completed · MRI packet · conf 0.94
9:48
nurse_3
Readiness blocker resolved · imaging
10:05
rosiflow_support
Read-only session opened · 30m TTL
10:09
rosiflow_support
Session closed · 0 writes attempted
Every mutating server fn writes
Each row carries actor_user_id, action, entity_type, entity_id, and a JSON metadata object. State changes have a paper trail.
Six preset filters
/app/audit gives admins one-click access to Team, Patients, Briefings, Automation, Portal, and All. Admin-only.
Sensitive metadata is hashed
Raw IP addresses are hashed before storage. A vitest invariant locks this in by JSON-stringifying the audit metadata and asserting the raw IP never appears.
No admin bypass to delete
There is no delete UI, no scheduled expiry, no maintenance window when audit rows go away. The append-only contract is enforced operationally.
Support access

Read-only impersonation — no exceptions, no toggles.

30-minute hard TTL
Sessions auto-end at the database. There is no extension flow. Auto-terminating sessions are tested via the impersonation block matrix (68 per-operation cases).
Operation matrix blocks writes
Every server fn is typed as read / write / admin / impersonation_control. During an active impersonation, write + admin requests return 403 `impersonation_read_only`.
Persistent banner + email notice
The clinic owner is notified in-app and via email the moment a session starts. An audit-log row records the support engineer's identity + reason.
No write-mode escape hatch
We have removed write impersonation as a category, not just as a permission. There is no env flag, no DB column, no hidden API that toggles it on.
Optional hardening

MFA + IP allowlist for superadmin access (operator-toggled).

For platform-admin operations on top of the read-only impersonation model, three env vars give the operator additional control: SUPERADMIN_REQUIRE_MFA=true checks the Supabase JWT for AAL2 assurance; SUPERADMIN_ALLOWED_IPS takes a comma-separated CIDR / exact-IP list; SUPERADMIN_SECURITY_MODE picks warn (audit + UI hint, never block) or enforce (audit + UI hint + 403 on failed check). Recommended rollout: warn for a week of observation, then enforce. Allowlist contents are never logged — only the SHA-256 hash of the caller IP enters the audit metadata.

AI / OCR safety

Cited, branded, never fabricated.

Brand-only labels
Customer UI shows 'Rosiflow AI', 'Rosiflow AI OCR', 'Rosiflow AI Embeddings'. Upstream model names never appear in rendered HTML.
No fake output
When an AI provider is unconfigured, callers return `config_required`. We never persist a synthetic briefing, OCR transcription, or fabricated embedding vector.
Briefings cite evidence
Every non-trivial claim carries a source: [intake], [referral], [note], [clinic-knowledge]. 'Not documented' is preferred over inference.
Patient evidence wins on conflict
When clinic SOPs and patient records disagree, the briefing flags the divergence and uses the patient evidence. Clinic knowledge is workflow guidance, never patient fact.
No raw vectors over the wire
`match_clinic_chunks` returns chunk content + cosine similarity score — never the embedding column itself. A vitest invariant locks the wire shape.
Review-before-clinical-use banner
Every briefing carries a banner reminding the provider that AI output requires clinical review before action. The provider's acknowledgement is audit-logged.
Notifications

No identifiers in email envelopes.

We never email patient identifiers, MRN, or diagnosis text. The email envelope contains only a portal link or a notification subject like "New intake submitted." The clinical context lives behind the portal link / app session — both require Supabase Auth. Patient portal tokens are stored as SHA-256 hashes on the server; the raw token never enters the database after the initial generation.

Enterprise readiness roadmap

Where Rosiflow's compliance posture is heading.

Business Associate Agreement
Rosiflow signs a BAA with each clinic once the joint operational and legal items below are completed. Compliance teams receive the standard BAA and security questionnaire responses within one business day.
Independent SOC 2 attestation
Infrastructure providers underpinning Rosiflow maintain SOC 2 Type II. Rosiflow's own Type II is on the enterprise readiness roadmap and shared with prospects under NDA once available.
Penetration testing
Third-party penetration tests are scheduled alongside the SOC 2 program. Executive summaries and remediation evidence are available under NDA for procurement review.
HITRUST
HITRUST is evaluated on a per-engagement basis. If your procurement requires HITRUST today, the enterprise team will scope the right path for your timeline.
Full architectural detail, including the impersonation matrix and AI safety invariants, lives in our internal SECURITY_REVIEW.md. We share it under NDA before BAA signing.

Bring your security team.

We walk reviewers through the controls, the gating checklist, and the read-only impersonation matrix. Conservative answers — no hand-waving.