Document Service — Security Model
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 13 security-compliance-tenancy · 14 compliance-security-extended
1. RBAC / ABAC Matrix
| Operation | Minimum Role | License Required | Additional Conditions |
|---|---|---|---|
| List / search documents | CLINICIAN | ehr.documents | Patient must be accessible to caller's facility |
| Download / view document | CLINICIAN | ehr.documents | ABAC check per document sensitivity |
| Generate document | CLINICIAN | ehr.documents | Context resource must be accessible |
| Upload scanned document | CLINICIAN | ehr.documents | Virus scan mandatory |
| Create / edit template draft | DOCUMENT_AUTHOR | ehr.documents.designer | Tenant-scoped |
| Publish template version | DOCUMENT_AUTHOR | ehr.documents.designer | — |
| Fork platform template | DOCUMENT_AUTHOR | ehr.documents.designer | — |
| Retire template | TENANT_ADMIN | ehr.documents | — |
| Enqueue async bulk generation | TENANT_ADMIN | ehr.documents.bulk | — |
| Edit platform reference template | Nobody | — | Returns 403 PLATFORM_TEMPLATE_IMMUTABLE |
| Access another tenant's documents | Nobody | — | RLS + CROSS_TENANT deny |
2. Tenant Isolation
| Control | Implementation |
|---|---|
| Row-Level Security | PostgreSQL RLS on all tables; tenant_id = current_setting('app.current_tenant_id') |
| Object storage isolation | All objects stored under /{tenantId}/ prefix; presigned URL generator validates tenantId claim |
| JWT claim enforcement | tenantId extracted from JWT; never from request body alone |
| FHIR resources | DocumentReference created with tenant-scoped patient; cross-patient access checked by FHIR gateway |
3. Encryption
| Data class | Encryption | Notes |
|---|---|---|
| PDF artifacts (object storage) | AES-256 SSE-S3 or SSE-KMS | Encryption at rest; per-tenant KMS key where configured |
| Quarantined files | AES-256 SSE-S3 | Retained per policy; access restricted |
document_template_versions.definition (JSONB) | At rest: PostgreSQL TDE | No PHI in template definitions |
Render job context JSONB | At rest: PostgreSQL TDE | Contains resource IDs only, not PHI content |
| Transit (all) | TLS 1.2+ | All HTTP and NATS connections |
| Presigned URLs | Short-lived (default 15 min TTL) | No credentials embedded; time-bounded HMAC signature |
4. Virus Scanning
| Stage | Action |
|---|---|
| Upload initiation | File staged to quarantine prefix |
| Finalization | ClamAV scan before any storage or FHIR registration |
| Virus detected | File moved to quarantine-infected/; document.upload.quarantined.v1 event emitted; caller receives 422 |
| Clean | File moved to /{tenantId}/documents/; DocumentReference created |
| Scan timeout | Upload rejected; caller retries; quarantine object cleaned up after TTL |
5. Audit Events
All document access events are emitted to NATS and persisted by audit-service with 7-year retention.
| Trigger | Action logged |
|---|---|
| Document downloaded | action=download, actorId, tenantId, documentReferenceId, patientId |
| Document viewed in UI | action=view |
| Document printed / exported | action=print / action=export |
| Document generated | action=generate, templateVersionId, inputSnapshotHash |
| Document uploaded | action=upload, uploadId |
| Template published | action=template_publish |
| Platform template fork | action=template_fork |
| Virus quarantine | action=quarantine, uploadId, reason |
6. GDPR Participation
| Aspect | Notes |
|---|---|
| Personal data | PDFs contain PHI; stored in tenant-scoped object storage |
| Data subject rights | Patient document list available via GET /v1/documents?patientId=...; deletion follows legal hold policy |
| Retention | Minimum 7 years for clinical documents; legal hold overrides deletion |
| Data residency | Object storage and PostgreSQL in tenant's designated region |
| Accounting of disclosures | Audit record on every download/export; reportable for HIPAA accounting |
| Purpose limitation | Documents used only for clinical care and audit; not shared with analytics or AI training without explicit consent |
7. Security Hardening
| Control | Detail |
|---|---|
| Virus scan mandatory | No upload bypasses scan; quarantine on detection |
| Presigned URL TTL | Default 15 min; configurable per tenant with minimum 5 min |
| No raw storage paths exposed | Clients receive presigned URLs; internal S3 paths never returned in API |
| Input validation | Zod on all DTOs; max file size configurable per tenant |
| Server-side rendering only | No PHI in client-side or third-party rendering pipelines |
| Rate limiting | Kong rate limits on generate and upload endpoints to prevent abuse |