Skip to main content

Patient Chart Service — Security Model

Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 13 Security/Compliance/Tenancy · 14 Extended compliance

1. AuthN / AuthZ

All requests carry a Bearer JWT from identity-service. The JWT tid claim identifies the active tenant; downstream RLS uses set_config('app.tenant_id', tid). Scopes ride in the scope claim.

2. RBAC / ABAC matrix

ScopeAllowsTypical roles
chart:readRead banner / summary / timeline / specific aggregates (except sensitive segments)provider, nurse, pharmacist, care-coordinator, medical-assistant
chart:problems:writeCreate / update / resolve / inactivate problemsprovider, resident (cosign gate may apply), advanced-practice
chart:problems:adminEntered-in-errorprovider-admin / HIM lead
chart:allergies:writeCreate / update allergies + NKA/NKDAprovider, nurse, pharmacist
chart:allergies:adminEntered-in-errorprovider-admin
chart:allergies:advisoryCall internal advisory (service-to-service)orders-service, medication-service
chart:vitals:writeRecord / correct vitalsnurse, medical-assistant, provider
chart:notes:writeCreate / edit draft / addendaprovider, resident
chart:notes:signSign own notes; request cosignprovider, resident
chart:notes:cosignAttest cosignattending / advanced-practice
chart:notes:adminEntered-in-errorHIM
chart:exportSnapshot exportprovider, care-coordinator (policy-gated)
chart:breakglassInvoke break-glassany clinician with reason
chart:sensitive:{segment}Read a sensitive segment (e.g., mental-health, sexual-health)role-bound per policy

ABAC rules:

  • Own-encounter preference: Some edit actions require the actor be on the encounter's participant list or the patient's current care-team.
  • Role cosign policy: SignNote from role=resident routes to cosign unless policy disables it for the template / specialty.
  • Sensitive segment policy: Evaluated via ConsentPolicyClient per patient + segment + actor; result determines 200 vs 403 vs 200-with-redaction.

3. Tenant isolation

  • Row Level Security on every table with tenant_id.
  • All repository queries pass tenantId; a CI check fails the build if any repository method lacks the tenant predicate in SQL.
  • test/integration/tenant-isolation.spec.ts is a mandatory test (per TEMPLATE).

4. Encryption

ClassAt restIn transit
Class A (IDs, tenant)Disk-levelTLS 1.2+
Class B (codings, category)Disk-level + TDETLS
Class C (narrative: note bodies, free-text notes, addenda, reasons)Disk-level + application-level KMS envelope for note body bytes if kms.enabled=trueTLS

KMS envelope keying: per-tenant KEK in AWS KMS / platform KMS; DEKs cached in-memory with 15-min TTL.

5. Audit events

Every mutation and sensitive read emits an audit.clinical.* event through AuditPublisher:

TriggerAudit event
Any aggregate create/update/lifecycleaudit.clinical.mutation
Break-glass invocationaudit.clinical.breakglass
Sensitive segment accessaudit.clinical.sensitive_access
Snapshot exportaudit.clinical.snapshot_exported
Failed authorizationaudit.auth.denied
AI acceptanceaudit.clinical.ai_accepted
Note read (first time per user)audit.clinical.note_read

All audit events include tenantId, actorId, patientId, correlationId, outcome, purposeOfUse (where provided).

6. Break-glass

  • Clinician calls POST /v1/chart/{patientId}/breakglass with reason + duration.
  • Service evaluates policy (eligibility, max duration per role), creates a ChartAccess record, emits breakglass.invoked.v1, and issues a short-lived elevated scope (via identity-service refresh exchange) for the requested duration (bounded to 240 minutes).
  • Every subsequent sensitive access during the elevation is also audited.

7. Sensitive segments

SegmentDefault policy
mental-healthRequire chart:sensitive:mental-health scope OR break-glass
sexual-healthRequire chart:sensitive:sexual-health scope OR break-glass; redact from summary widgets by default
geneticExplicit consent required; default deny
addictionRequire segment scope

Policy evaluation delegated to ConsentPolicyClient backed by tenant-service + config-service.

8. GDPR / local privacy

RightHandling
AccessGET /v1/chart/{patientId}/... with patient-scoped token (patient-portal-service proxy)
RectificationPATCH on product REST with audit
ErasureParticipate in platform GDPR saga: anonymize author display fields, retain clinical content per retention policy (jurisdictional); do not delete clinical record without policy flag
Portability/v1/chart/{patientId}/snapshot/export?format=json — FHIR-bundle-compatible export
RestrictionBlock access via policy engine; retain record

9. Data residency

Deployment is configured per tenant to pin Postgres and object-storage to an in-country region. Cross-region reads are forbidden at the data path; cross-region reporting goes through anonymized aggregates in population-health-service.

10. Threat model (summary)

ThreatMitigation
Cross-tenant data leakageRLS + scope guard + integration test
Unauthorized break-glass abuseAudit all + SIEM alert + supervisor review queue
Signed-note tamperingDB constraint + content hash + KMS envelope on note body bytes
AI injection via inputGateway refuses uncompliant content; service validates output before write; contentHash stored
Enumeration of problems/allergies via id scanULIDs + tenant scope on all queries; 404 for cross-tenant id
Stolen refresh / break-glass sessionShort-lived elevated scope, 240-min max; revocation channel

11. Open Questions

  • Sensitive segment taxonomy — platform-wide fixed list vs per-tenant extensible? Default: fixed list + tenant opt-in extension.
  • Rotation cadence for per-tenant KEK? Platform baseline is annual; chart notes may need 180d — decision deferred.