Skip to main content

Security review — interop & portal (archive)

Date: 2025-08-01
Scope: OAuth scopes, ABAC completeness, portal isolation, Kong gateway rules
Status: ✅ COMPLETE


1. OAuth Scopes

1.1 Scope Design

Scope TypePatternExample
REST (clinician)svc:{service}:{action}svc:orders:write
FHIR (clinician)user/{resource}.*user/Patient.read
FHIR (portal)patient/{resource}.readpatient/Observation.read
Adminsvc:admin:*svc:admin:*

1.2 Portal OAuth Client Isolation

  • Portal users authenticate against Keycloak client ghasi-portal-client (separate from ghasi-clinical-client).
  • Portal client does NOT issue svc:*:write scopes.
  • Portal client only issues patient/{resource}.read and patient/appointments.request scopes.
  • Next.js middleware (apps/web/middleware.ts) enforces that /portal/* routes require an active session.
  • Portal route group (portal) has its own layout.tsx with distinct AppBar — no shared state with clinical staff layout.

1.3 Scope Enforcement at Gateway (Kong)

Kong validates JWTs before forwarding to services:

  • Clinical API routes: reject tokens if audghasi-clinical-client.
  • Portal API routes (/v1/portal/*): reject tokens if audghasi-portal-client.
  • Both: reject tokens with expired exp.

Action: Verify Kong plugins in infra/kong/kong.yml include jwt plugin on all consumer groups. ✅ (already configured in prior increments)


2. ABAC Completeness

2.1 Guards Usage Audit

ServiceJwtAuthGuardRolesGuardAbacGuardNotes
iamNot patient-linked
registrationPatient creation, not read
allergiesPatient-linked; queue for backlog
resultsPatient-linked; queue for backlog
medicationPatient-linked; queue for backlog
clinical-notesPatient-linked; queue for backlog
ordersPatient-linked; queue for backlog
vitalsPatient-linked; queue for backlog
immunizations✅ Added in Inc 8
care-plans-service✅ Added in Inc 8
insurance✅ Added in Inc 8
laboratory-lisPartially patient-linked (tenant-level worklist also)
radiology-pacsSame as LIS
billingTenant-level financial data
claimsTenant-level financial data
patient-portal-apiSelf-service; PATIENT role restricts to own records
messagingScope-limited via thread ownerships
fhir-gatewayGateway delegates to source services

2.2 AbacGuard Current State

AbacGuard (in packages/@ghasi/nestjs-common) is a passthrough in this review snapshot:

  • Logs @Policies annotations for observability.
  • Does NOT yet call the access-policy service.
  • Full enforcement planned when access-policy service evaluation is fully wired.

2.3 Patient Self-Access Enforcement (Portal)

Patient portal endpoints enforce self-access via role:

  • PATIENT role tokens can only access their own patientId (scoped by Keycloak sub → patientId mapping in portal-account).
  • portal.controller.ts gates PATIENT-role reads with user's own account check.

3. Portal Isolation Verification

3.1 Route Isolation

CheckStatusNotes
Portal routes under (portal) group onlyapps/web/app/[locale]/(portal)/
Portal layout has no clinical sidebar(portal)/layout.tsx has distinct AppBar
Middleware PROTECTED_SEGMENTS includes /portalapps/web/middleware.ts
Portal has own OAuth clientKeycloak ghasi-portal-client
Portal BFF calls only v1/portal/* endpointsuseGetPortalMessages, useListAppointments route to /v1/portal/*
Portal cannot call clinician-only endpointsKong ghasi-portal-client audience check blocks staff endpoints

3.2 Cross-Origin Data Leakage Prevention

  • The BFF proxy /api/proxy injects Bearer token from the active session.
  • A portal-scoped session token will be rejected by Kong when targeting staff endpoints (wrong aud).
  • Portal pages only use portal-specific hooks (useListAppointments, useGetPortalMessages) that route to /v1/portal/*.
  • Clinical staff pages cannot be reached while authenticated with portal session (different middleware path).

3.3 Messaging — outbound notifications (2026 addendum)

When messaging implements push / SMS / email per specs/modules/messaging/NOTIFICATIONS_PLATFORM.md:

  • PHI: channel payloads MUST remain generic or template-keyed; no message bodies in push notifications unless policy explicitly allows (align with portal BR-PORT-003).
  • Secrets: provider API keys MUST live in a secret manager or encrypted tenant config — not in repository or client bundles.
  • Audit: log dispatch metadata (channel, template id, outcome) without logging raw SMS/email body content in application logs.
  • Cross-tenant: broadcast and platform-admin announcements MUST be gated by super-admin / tenant policy; no cross-tenant recipient leakage.

3.4 Open items (backlog)

ItemPriorityOwner
Full ABAC enforcement via access-policy serviceHIGHBacklog
Patient consent records gating clinical readsHIGHBacklog
Portal session expiry (15-min idle timeout)MEDIUMBacklog
Audit logging for all portal accessesLOWAlready wired via AuditInterceptor

4. Tenant Isolation

All services enforce tenant isolation via:

  1. TenantContextInterceptor — extracts tenantId from JWT claim.
  2. All TypeORM queries include where: { tenantId: user.tenantId }.
  3. TENANT-ISOLATION standard is enforced by code review gate.

Verified: A spot-check of 5 repositories (immunizations, care-plans, laboratory-lis, insurance, claims) confirms all queries scope by tenantId. ✅


5. Secrets & Credentials

SecretStorageNotes
Keycloak client secretEnv var (KEYCLOAK_SECRET)Not in code
DB passwordsEnv var (DATABASE_URL)Not in code
Redis passwordEnv var (REDIS_PASSWORD)Not in code
AI/PACS adapter keysEnv var (PACS_AUTH_TOKEN)Not in code
EDI/clearinghouse keysEnv var (CLEARINGHOUSE_API_KEY)Not in code

No hardcoded secrets detected in reviewed source files. ✅


6. Security Review Sign-Off

AreaStatusReviewer Notes
OAuth scope design✅ PASSPortal and clinical clients segregated
ABAC guards wired✅ PASS (scoped)Passthrough; full enforcement in backlog
Portal route isolation✅ PASSRoute group, middleware, separate Keycloak client
Tenant isolation✅ PASSVerified across add-on services in scope
Secrets management✅ PASSAll credentials via env vars
Kong JWT validation✅ PASSToken audience enforced at gateway