Terminology Service — Security Model
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 13 security-compliance-tenancy · 02 DDD
1. Authentication
| Route prefix | Mechanism | Notes |
|---|---|---|
GET /v1/terminology/* | Keycloak JWT (Authorization: Bearer) | All clinical service consumers; patient-facing via portal BFF |
POST/PUT/DELETE /v1/terminology/concepts* | JWT + role guard (platform:admin or tenant:admin) | Concept CRUD requires elevated role |
GET /fhir/R4/* | JWT (same Keycloak realm) | FHIR operation surface; proxied from interop-service |
GET/POST /internal/terminology/* | Optional X-Terminology-Internal-Token header OR network mesh trust | Service-to-service calls from within the cluster |
Anonymous access is never permitted on any route (FR-TERM-010).
2. Authorization — RBAC Matrix
| Role | Allowed operations |
|---|---|
platform:admin | Create/update/deactivate global concepts; trigger imports; manage all value sets and concept maps |
tenant:admin | Create/update/deactivate tenant-scoped concepts only; cannot touch global concepts |
clinical:* (any clinical role) | Read-only: search, lookup, validate, expand, CDS queries |
service:internal (machine token) | Internal routes: all read operations + import (if enabled) |
3. Tenant Isolation
Terminology-service uses a dual-layer isolation model:
-
PostgreSQL RLS policy:
conceptstable has RLS enabled. The policyconcepts_tenant_isolationensures a query withapp.tenant_id = 'TENANT_A'can only see global concepts (tenant_id IS NULL) and concepts scoped toTENANT_A. Cross-tenant concept visibility is impossible at the database layer. -
Application-layer scope: All query handlers extract
tenantIdfrom the JWTtidclaim and pass it to the repository. The internal route enforcestenantId = NULL(global only) when no JWT is present.
4. Data Classification
| Data | Classification | Notes |
|---|---|---|
| Global terminology concepts | Public reference data (no PHI) | SNOMED, LOINC, RxNorm, ICD-10 codes are publicly known |
| Tenant-scoped custom concepts | Organizational reference data | May contain internal facility coding; not PHI |
| Drug interactions / contraindications | Licensed clinical knowledge | Not PHI; licensed data — never committed to repo |
| Query logs (what was searched/looked up) | Operational data | No PHI; service does not log the clinical context of the calling service |
Terminology-service never handles or stores PHI. Concept codes are identifiers; clinical context (which patient has which condition) is never stored here.
5. Licensed Data Handling
Licensed terminology content (LOINC CSV, SNOMED RF2, RxNorm RRF) must never be committed to the source repository. The ETL pipeline loads licensed data into the PostgreSQL database at deployment time from a secured, access-controlled object storage bucket.
| System | License | Repository policy |
|---|---|---|
| LOINC | Free (Regenstrief) | No raw CSV in repo |
| SNOMED CT | SNOMED NRC/affiliate license | No RF2 files in repo |
| RxNorm | NLM public domain | No RRF files in repo |
| Drug interaction data | Commercial license (Multum/DrugBank) | No interaction data in repo |
| ICD-10-CM | HHS / MoPH adaptation | No raw coding data in repo |
6. Import Endpoint Security
POST /internal/terminology/import is:
- Disabled in production by default (
IMPORT_ENABLED=false). - Accessible only via the internal network route (not exposed through Kong).
- Callable only by authenticated platform operators with
platform:adminrole. - Guarded by the optional
X-Terminology-Internal-Tokenheader whenINTERNAL_TOKENenv var is set.
7. Audit Events
| Event | Trigger | Destination |
|---|---|---|
concept.created | Admin creates a concept | NATS → audit-service |
concept.deactivated | Admin deactivates a concept | NATS → audit-service |
dataset.updated | Bulk import completes | NATS → audit-service |
| Auth failure | Invalid JWT or insufficient role | OpenTelemetry → security dashboard |
Query operations (search, lookup, validate, expand, CDS) do not produce audit events — they are high-volume read-only operations. The calling clinical service is responsible for auditing its clinical decisions.
8. GDPR Participation
Terminology-service is out of scope for GDPR from a data subject perspective. It holds no personal data. No data subject rights (access, erasure, portability) apply to this service's data.