Skip to content

Security

This page summarizes the security posture of subscription-service for operators evaluating the engine for a production deployment. It is a high-level guide — the canonical, version-tracked details live in auth.md and multi-tenancy in the engine repo.

Threat model in one paragraph

Subscription-service sits between an EHR (HL7 v2 or REST source) and downstream subscribers (REST hooks, WebSockets, side-channel sinks). It handles PHI in transit, persists it to HAPI FHIR, and emits notifications. The threats we plan for: a compromised subscriber, a leaked admin credential, a misconfigured tenant boundary, a vulnerable upstream EHR feed, and a malicious or buggy third-party plugin. We do not plan for: a fully compromised host operator (use OS-level controls), a compromised HAPI database (use disk + backup encryption + access control on the DB itself), or a nation-state adversary (out of scope).

Authentication

  • Operator UI + Admin API: OIDC via Keycloak (or any compliant IdP — Auth0, Okta, Azure AD, Google Workspace). NextAuth v5 in the UI; Spring Security on the JVM side. JWT access tokens, short-lived, with refresh.
  • Inbound HL7 v2 MLLP: mutual TLS by default; the engine’s listener rejects plaintext unless explicitly enabled for a dev environment. Per-facility client cert pinning is supported.
  • Outbound subscription delivery: the engine signs every webhook payload with an HMAC-SHA256 over a customer-managed secret, and subscribers verify the signature before processing. Replay protection via timestamp + nonce, configurable window.
  • Plugin authentication helpers: profiles can declare auth: oauth2-client-credentials to fetch tokens automatically; secrets are resolved from env vars, not stored in manifest.yaml.

Full details: Authentication & authorization and Auth testing.

Authorization

  • Roles are OIDC groups mapped to four built-in tiers: viewer, operator, admin, superadmin. The Pro tier adds custom-role registration via the AuthorizationDecider SPI.
  • Tenant scoping is enforced server-side on every admin-API call; clients cannot widen their tenant scope by manipulating the request.
  • Channel-level ACLs restrict which roles can see message bodies (vs. just metadata). The default posture is metadata-only for operator and below; body access requires explicit grant.

Multi-tenancy

Two modes ship in the box:

  1. Single-tenant (default) — one HAPI database, one FHIR base URL, one operator UI. Simplest, recommended for facilities running their own engine.
  2. Multi-tenanttenantId is a first-class column on every persisted resource, and every query is filtered by the caller’s tenant scope at the JPA repository layer. Tested against a 50-tenant fixture. See multi-tenancy.

Multi-tenancy is a configuration toggle, not a fork; the same binary runs both modes.

PHI handling

  • In transit: TLS 1.2+ end-to-end. The engine refuses to start with TLS 1.0/1.1.
  • At rest: PHI is persisted to HAPI’s JPA store. Disk encryption is the operator’s responsibility (we recommend dm-crypt / AWS EBS encryption / GCP CMEK / Azure Disk Encryption — pick one). The engine itself does not encrypt resource fields a second time inside HAPI; doing so breaks FHIR search.
  • In logs: PHI fields are masked by default in the JSON log schema. The mask list is configured per-vendor profile; see Log schema.
  • In metrics: Prometheus metrics carry NO patient-level labels — only facility / tenant / message-type cardinality. This is enforced by the observability schema-stability contract.

Audit

Every state-changing admin action and every PHI access emits a FHIR AuditEvent resource:

  • AuditEvent.recorded — timestamp
  • AuditEvent.agent.who — the principal (OIDC sub)
  • AuditEvent.agent.network — source IP
  • AuditEvent.source.observer — the engine instance
  • AuditEvent.entity — the resource(s) acted on

Audit events are stored in the same HAPI database as the clinical resources and are searchable via the standard FHIR API. Vendor profiles can enrich audit events with vendor-specific attributes (originating practitioner, facility code) via the audit.enrichments block — see Plugin SPI §5.

The audit chain is append-only at the application layer. For tamper-evident audit, configure an external WORM sink (S3 Object Lock, Azure Immutable Blob, GCS Bucket Lock) via a MessageSink plugin filtered to AuditEvent resources.

Plugin trust boundary

Third-party JARs run in the same JVM as the engine. The engine does NOT sandbox plugins — a malicious plugin can read environment variables, open sockets, and access the HAPI connection pool. This is a deliberate trade-off: a sandboxed plugin model (separate process, IPC) would dramatically complicate the SPI for the 99% case of a well-behaved profile.

Operator responsibility: treat a plugin like any other dependency. Verify provenance (signed JAR, published to a trusted Maven repo), review the source if possible, and pin versions. Certified commercial profiles ship with a signed regression-test report; community profiles do not.

The UI extension model is stricter — UI fragments are baked into the published image, not loaded from a directory. A self-built image is FOSS-only; commercial UI extensions are unreachable without the official image and a valid license check.

Reporting a vulnerability

If you think you’ve found a security issue:

  • Email: [email protected] (PGP key on the GitHub org page)
  • Response SLA: acknowledgement within 48 hours, triage within 5 business days, fix or mitigation plan within 30 days for high-severity, 90 days for medium.
  • Disclosure: coordinated. We ask for 90 days before public disclosure on high-severity; we’ll credit you in the release notes unless you ask otherwise.

Please do not open a public GitHub issue for a security report — file a confidential email first.

What this page is not

  • It’s not a SOC 2 or HIPAA attestation. The FOSS engine is a software building block; compliance attestations apply to a deployment (a hosted instance, a managed cloud offering), not to the codebase. The commercial “Cloud” tier ships with HIPAA + SOC 2 attestations covering the hosted environment.
  • It’s not a substitute for a penetration test. We run automated SAST + dependency scanning in CI; we recommend operators run their own pentest before going live with PHI traffic.