Skip to main content

Billing Service — Security Model

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

1. Trust boundaries​

2. AuthN​

  • Inbound: Bearer JWT issued by identity-service; validated via JWKS (IDENTITY_JWKS_URL); iss, aud=billing-service, tid, sub required.
  • Outbound: mTLS for service-to-service; service account JWT for terminology / fhir-gateway / tenant-service.
  • Patient portal traffic enters through patient-portal-service BFF; billing sees a downstream service JWT with patient scope claims.

3. AuthZ — RBAC / ABAC matrix​

ScopeRoles (default)PurposeABAC
billing:charge:readbilling_clerk, cashier, supervisor, auditorRead chargestid match
billing:charge:writebilling_clerk, system (event handler)Capture chargestid + facility match
billing:charge:reversesupervisorReverse a posted chargethreshold amount per tenant config
billing:account:readbilling_clerk, cashier, supervisor, patient (self via portal)Read accounts + agingPatient sees only patient_id == self
billing:invoice:readbilling_clerk, cashier, supervisor, patient (self)Read invoicesPatient scope filter
billing:invoice:issuebilling_clerkIssue a draft invoicefacility match
billing:invoice:voidsupervisorVoid an invoicereason code required
billing:payment:postcashier, system (remittance)Post paymentIdempotency enforced
billing:refund:requestcashierRequest refundany
billing:refund:approvesupervisor, finance_managerApprove refunddual-approval if amount > tenant threshold
billing:adjustment:postbiller, system (remittance)Apply adjustmentreason code + facility match
billing:statement:runbilling_adminStart batch statementfacility match
billing:pricelist:managetenant_adminManage price listsfacility match
billing:gl:exportfinance_managerGL exporttenant match

4. Licensing gate​

Before any route handler executes, LicensingGuard calls tenant-service.checkEntitlement('billing'). If disabled → 403 MODULE_NOT_ACTIVE. Result cached per tenant for 60 s.

5. Data classification​

Field classExamplesStorage
PHIpatient_id linkage, encounter contextEncrypted at rest (pgcrypto disk + KMS); RLS by tenant_id
FinancialAmounts, ledger entriesEncrypted at rest; integrity-protected by append-only trigger
PayerCoverage refs passed in (no member IDs stored here)References only, not raw PII
SecretsPayment gateway creds, JWT keysKMS or HashiCorp Vault; never in env files
PCICard dataNever stored — adapter tokens only

6. Encryption​

  • At rest: Postgres with LUKS/EBS volume encryption; column-level AES-GCM via pgcrypto for external_ref, reference, PII audit fields.
  • In transit: TLS 1.3 on all links; mTLS inside service mesh.
  • KMS: AWS KMS / Vault Transit for data keys; annual rotation.

7. Audit events (emitted to audit-service)​

EventWhen
audit.billing.charge.capturedCharge posted
audit.billing.charge.reversedCharge reversed
audit.billing.invoice.issuedInvoice issued
audit.billing.invoice.voidedInvoice voided
audit.billing.payment.postedPayment posted
audit.billing.refund.requested/approved/rejected/postedRefund lifecycle
audit.billing.adjustment.appliedAdjustment posted
audit.billing.pricelist.publishedPrice list change
audit.billing.gl.exportExport run
audit.billing.access.unauthorized403 / cross-tenant attempt

All audit events include actor_id, tenant_id, correlation_id, request_id, ip_hash, and a minimal resource_ref (no payloads with PII).

8. GDPR participation​

RightBilling behaviour
AccessPatient can retrieve their invoices + payments via portal; export as FHIR Bundle
RectificationCharge reversal + new charge; ledger immutability preserved
ErasureRestricted — financial records retained per legal hold (commonly 10 y). Personal identifiers pseudonymised; monetary records retained
PortabilityFHIR Bundle (Account + Invoices + Payments) export
Objection / restrictionFlag on account; block new charges

9. Tenant isolation tests (mandatory)​

  • tenant-isolation.spec.ts must attempt cross-tenant read/write on every table; every attempt must return empty / 403 / RLS deny.
  • PostgreSQL app.tenant_id session variable must be set by every request handler before any query.

10. Key security controls​

ControlImplementation
Input validationZod schemas on every DTO, rejects unknown fields
SQL injectionDrizzle parameterised queries only; no string concat
XSS in PDFsServer-side rendering with escaping; user-supplied text sanitised
CSRFState-changing endpoints require Origin header match and Bearer auth (no cookies)
Rate limitingKong: 100 rps per client per route; POST /payments 20 rps per tenant
Bot protectionKong bot detector on patient-facing routes
Data loss preventionNo PAN/CVV storage; static analysis blocks logs of sensitive fields
Supply chainnpm audit --production; trivy image scan in CI
Secret rotation90 d for service JWT signing key; 30 d for gateway adapter tokens