# Security and Access-Control Plan v0.3 **Scope:** Defines roles, permissions, environment policy, secrets handling, encryption strategy, audit logging, and repository rules for the ACCESS Core Database project. --- ## 1. Phase 1 Security Boundaries | Rule | Enforcement | |---|---| | Synthetic/sample data only | No real patient data, PHI, MBI, or CMS credentials in GitHub or local dev | | No real Cognito submissions or Healthie patient records | Test/preview links and synthetic payloads only | | No secrets, API keys, or credentials in repository | `.env` files excluded via `.gitignore`; secrets in AWS Secrets Manager in prod | | Contractor (Nathan) access limited to repo, synthetic data, and test endpoints | GitHub Write access; no production credentials or PHI | | Future production environment designed separately | Formal security, compliance, access-control, backup, retention, incident-response | --- ## 2. Role & Permission Matrix ### Phase 1 (Synthetic Data Development) | Role | System | Database Access | Cognito | Healthie | CMS APIs | S3 Documents | |---|---|---|---|---|---|---| | **Ling / Owner** | Repo owner/admin | Full (synthetic) | Admin | Admin | Admin | Full | | **Nathan / Contractor** | GitHub Write | Read + synthetic insert | Preview/test form only | No access | No access | No access | | **Initial tester (handoff)** | GitHub Read/Triage | Read (synthetic) | Read-only | No access | No access | No access | ### Production (Future) | Role | Intake Data | Clinical Data | Identity/PHI | CMS Data | Documents | Curation Queue | Offshore Review | Audit Logs | |---|---|---|---|---|---|---|---|---| | **Ling / Owner** | Full | Full | Full | Full | Full | Full | Full | Full | | **Dr. Quinn / Physician** | Read | Read + review + approve | Read (assigned) | Read | Read | Read | Read | Read | | **US Clinical Reviewer** | Read | Read + review | Read (assigned) | Read | Read | Read | — | Read | | **Data Assistant** | Read (assigned) | Draft only (assigned) | Masked (no PHI) | No | Read (assigned) | Write (drafts only) | — | Read (own actions) | | **Offshore Reviewer** | No | Pseudonymous only | No (pseudonymous ID) | No | No (de-identified) | No | Write (notes only) | Read (own actions) | | **Patient** | Own only | Own only | Own only | No | Own uploads only | No | No | No | | **Admin/Finance** | No | No | No (masked) | Payment files only | No | No | No | Read (non-clinical) | ### Key Principles - **Minimum necessary access:** Each role sees only what is required for their task. - **Offshore reviewers:** Pseudonymous case IDs only. No PHI, MBI, SSN, raw CMS data, or unredacted documents. - **Data assistants:** See task-assigned records only. Draft observations are not final clinical truth. - **No local download by default** for PHI in production unless specifically approved and logged. --- ## 3. Environment Separation | Policy | Development | Staging | Production | |---|---|---|---| | **Data type** | Synthetic only | Synthetic / anonymized | Real (encrypted) | | **VPC** | Shared or separate | Separate | Separate | | **RDS** | Single-AZ, no encryption required | Single-AZ, KMS encrypted | Multi-AZ, KMS encrypted | | **S3 encryption** | SSE-S3 (optional) | SSE-KMS | SSE-KMS | | **Secrets** | Local `.env` (no real keys) | Secrets Manager | Secrets Manager | | **Backup** | None required | Daily automated | Continuous + PITR | | **Audit logging** | Minimal | Full | Full + CloudTrail | --- ## 4. Secrets Handling | Secret | Phase 1 | Production | |---|---|---| | Cognito API key | Not used (synthetic) | AWS Secrets Manager, auto-rotate quarterly | | Healthie API key | Not used | AWS Secrets Manager | | CMS API key | Not used | AWS Secrets Manager | | Database password | Local `.env` | Secrets Manager, auto-rotate | | KMS keys | Not used | KMS, annual rotation | | Webhook signing secret | Not used | Secrets Manager | **Rules:** - Secrets retrieved at runtime via IAM-authorized API calls (Fargate task role). - Never stored in environment variables in container image or Dockerfile. - `.env.example` provided in repo with placeholder values; actual `.env` excluded via `.gitignore`. - Secret rotation events logged to CloudTrail. --- ## 5. Encryption Strategy | Data Category | At Rest | In Transit | Notes | |---|---|---|---| | **PHI fields in DB** | App-level AES-256-GCM, key from AWS KMS | TLS 1.2+ | Name, DOB, address, phone, email, MBI | | **RDS database** | RDS encryption with KMS | TLS 1.2+ | Full instance encryption | | **S3 documents** | SSE-KMS | TLS 1.2+ (presigned URLs) | Lab PDFs, imaging reports, consent forms | | **SQS messages** | KMS encryption | TLS 1.2+ | Webhook payloads, queue messages | | **CloudWatch logs** | KMS encryption | TLS 1.2+ | Application and audit logs | | **EBS volumes** | EBS encryption with KMS | — | Fargate task storage | | **Non-PHI clinical data** | — (RDS encryption) | TLS 1.2+ | Lab values, PROM scores, observations | **App-level encryption design:** ``` Plaintext → AES-256-GCM (data key from KMS) → ciphertext + IV + auth tag → stored in DB column ``` - Data keys cached in application memory (short-lived). - KMS master key rotated annually. - Decryption only for authorized roles (verified via RBAC before decrypt). --- ## 6. Audit Logging ### Audit Event Schema ```sql CREATE TABLE audit_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id), action VARCHAR(50) NOT NULL, -- create, update, approve, reject, export, view, delete, lock, submit_to_cms target_table VARCHAR(100) NOT NULL, target_record_id UUID, changed_fields JSONB, -- PHI fields show [REDACTED] old_value_hash VARCHAR(64), -- SHA-256 of old value (for PHI fields) new_value_hash VARCHAR(64), -- SHA-256 of new value (for PHI fields) ip_address TEXT, user_agent TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); ``` ### Implementation The writer above is implemented in `access_core/governance/audit.py`: signals on an explicit registry of PHI and decision models append `governance.AuditEvent` rows (changed field names + SHA-256 row hashes, never raw values), with the acting user/IP/user-agent captured from the request. Append-only is enforced at the Django layer — `AuditEventAdmin` is fully read-only with add/delete disabled, and no code path updates or deletes audit rows. Per repository policy no operator is granted direct database access, so no DB-level trigger is used; if that policy ever changes, re-evaluate adding one. ### CMS Payload Storage & Synthetic MBI Gate `ALLOW_SYNTHETIC_MBI` (default `false`; enabled in dev/local/test) controls two behaviors toward the CMS API: when disabled, a record with no real cmsMBI identifier fails closed at FHIR build time instead of sending a PHI-derived synthetic pseudonym (`access_core/services/fhir_builder.py`), and Patient-level fields (name/birthDate/telecom/address) are redacted from stored `CMSApiRequest`/`CMSApiResponse` payloads — the MBI is retained for traceability. The wire payload sent to CMS is never altered; redaction applies only to what we persist. ### Audit Rules - **Append-only:** `audit_events` cannot be updated or deleted. - **Every action logged:** create, edit, approve, reject, export, view (for PHI), role change. - **PHI redaction:** `changed_fields` contains `[REDACTED]` for PHI fields; hash stored instead of plaintext. - **Immutable clinical values:** Corrections create new events rather than silently overwriting prior values. - **Log retention:** Defined by compliance policy (not yet finalized). --- ## 7. Repository Rules | Rule | Enforcement | |---|---| | `.env` excluded from git | `.gitignore` | | No PHI, MBI, or real patient data in any file | Manual review + pre-commit hooks | | No credentials, API keys, or secrets in code | Pre-commit secrets scanner (e.g., gitleaks) | | Private repository only | GitHub private repo setting | | Branch protection on `main` | Required PR review for merge | | Audit trail for all commits | GitHub commit history + signed commits | --- ## 8. Network Security | Control | Implementation | |---|---| | **VPC isolation** | Private subnets for Fargate, RDS. Public subnet for ALB only. | | **VPC endpoints** | S3, Secrets Manager, KMS, SQS via PrivateLink (no NAT gateway needed) | | **Security groups** | Least-privilege: ALB → Fargate (8000), Fargate → RDS (5432), Fargate → SQS/S3/KMS/Secrets Manager (VPC endpoints) | | **No public IPs** | Fargate tasks and RDS have no public IP addresses | | **TLS termination** | ALB terminates TLS; all internal communication also TLS | | **CloudTrail** | All AWS API calls logged for compliance and security monitoring | --- ## 9. Compliance Checklist (Open Items) | Item | Status | Notes | |---|---|---| | HIPAA compliance plan | Pending | Requires legal/compliance review | | Data retention and deletion policy | Not defined | Required per CMS Agreement §15.02 | | Business Associate Agreement (BAA) with AWS | Pending | Required for HIPAA | | Incident response plan | Not defined | Required for production | | De-identification standard (Safe Harbor / Expert Determination) | Not defined | For offshore review and public reporting | | Cell-size suppression threshold | Not defined | For public reporting (suggested: n < 10) | | Data breach notification policy | Not defined | HIPAA requires 60-day notification | | Access review schedule | Not defined | Suggested: quarterly access review | | PHI access logging granularity | Pending legal input | Does every view need audit logging? |