Skip to main content

HIPAA PHI Access Controls Specification

Document ID: CODITECT-BIO-HIPAA-AC-001 Version: 1.0.0 Effective Date: 2026-02-16 Classification: Internal - Restricted Owner: Chief Information Security Officer (CISO) & Privacy Officer


Document Control

Approval History

RoleNameSignatureDate
Chief Information Security Officer[Pending][Digital Signature]YYYY-MM-DD
Privacy Officer[Pending][Digital Signature]YYYY-MM-DD
VP Quality Assurance[Pending][Digital Signature]YYYY-MM-DD
Regulatory Affairs Director[Pending][Digital Signature]YYYY-MM-DD

Revision History

VersionDateAuthorChangesApproval Status
1.0.02026-02-16CISO Office + Privacy OfficeInitial releaseDraft

Distribution List

  • Executive Leadership Team
  • Information Security Team
  • Privacy Office
  • Quality Assurance Team
  • Compliance and Regulatory Affairs
  • Clinical Operations
  • Internal Audit

Review Schedule

Review TypeFrequencyNext Review DateResponsible Party
Annual Review12 months2027-02-16CISO + Privacy Officer
Regulatory Update ReviewAs neededN/ARegulatory Affairs
Post-Breach ReviewAs neededN/APrivacy Officer + Security Team
Access Review AuditQuarterly2026-05-16Privacy Officer

1. Executive Summary

1.1 Purpose

This HIPAA PHI Access Controls Specification establishes comprehensive technical and administrative safeguards for controlling access to Protected Health Information (PHI) within the CODITECT Biosciences Quality Management System (BIO-QMS) Platform. This document ensures compliance with:

  • HIPAA Security Rule 45 CFR §164.312(a) — Access Control
  • HIPAA Privacy Rule 45 CFR §164.514(d) — Minimum Necessary Standard
  • NIST SP 800-66 Rev 2 — HIPAA Security Rule Implementation Guide
  • HHS Guidance on Access Controls and Minimum Necessary

1.2 Scope

In Scope:

  • All PHI data elements across the BIO-QMS platform
  • Authentication and authorization mechanisms for PHI access
  • Multi-factor authentication (MFA) requirements
  • Minimum necessary principle enforcement
  • Session management and timeout controls
  • Break-glass emergency access procedures
  • Access review and recertification processes
  • Technical implementation architecture

Out of Scope:

  • Physical access controls (covered in Physical Security Policy)
  • Business Associate Agreements (covered in Legal/Contracts)
  • Breach notification procedures (covered in Incident Response Plan)
  • End-user device security (covered in Endpoint Security Policy)

1.3 Regulatory Requirements Overview

HIPAA SectionRequirementImplementation StandardBIO-QMS Control
§164.312(a)(1)Access ControlRequiredRBAC + field-level masking + session management
§164.312(a)(2)(i)Unique User IdentificationRequiredECDSA P-256 signing keys per user + SSO identity
§164.312(a)(2)(ii)Emergency Access ProcedureRequiredBreak-glass workflow with 4-hour expiration
§164.312(a)(2)(iii)Automatic LogoffAddressable (IMPLEMENTED)15-minute idle timeout, configurable per role
§164.312(a)(2)(iv)Encryption and DecryptionAddressable (IMPLEMENTED)AES-256-GCM for PHI at rest, TLS 1.3 in transit
§164.312(b)Audit ControlsRequiredTamper-evident audit log with HMAC-SHA256 integrity chain
§164.312(d)Person or Entity AuthenticationRequiredMFA (TOTP/WebAuthn) required for all PHI access

2. PHI Data Classification

2.1 HIPAA Identifier Inventory

The HIPAA Privacy Rule (45 CFR §164.514(b)) defines 18 identifiers that constitute PHI when linked to health information:

Identifier CategoryHIPAA IdentifierBIO-QMS Data FieldsSystem LocationAccess Control Level
Direct Identifiers
1. NamesNames (full, last, first, maiden)patient.full_name, patient.first_name, patient.last_namepatients table, Work Order descriptionL3 — Clinical staff only
2. Geographic subdivisionsAll addresses smaller than statepatient.street_address, patient.city, patient.zip_codepatient_addresses tableL3 — Clinical staff only
3. DatesAll dates except year (birth, admission, discharge, death)patient.date_of_birth, work_order.service_datepatients table, work_orders tableL3 — Clinical + QA
4. Telephone numbersAll telephone numberspatient.phone_number, patient.mobile_numberpatient_contacts tableL3 — Clinical staff only
5. Fax numbersAll fax numbersfacility.fax_numberfacilities tableL2 — Administrative
6. Email addressesAll email addressespatient.emailpatient_contacts tableL3 — Clinical staff only
7. Social Security numbersSSNpatient.ssn (encrypted)patients table, encrypted columnL4 — Billing only, audit logged
8. Medical record numbersMRN, account numberspatient.mrn, work_order.account_numberpatients table, work_orders tableL3 — Clinical + Billing
9. Health plan numbersInsurance policy/subscriber numberspatient_insurance.policy_numberpatient_insurance tableL3 — Billing only
10. Device identifiersDevice serial numbers (implants, equipment)asset.serial_number, work_order.device_idassets table, work_orders tableL2 — Clinical + IT
11. Vehicle identifiersLicense plate numberspatient.vehicle_license_platepatient_metadata JSONBL3 — Clinical staff only
12. URLsURLs containing patient identifierspatient_documents.file_urldocuments tableL3 — Clinical staff only
13. IP addressesIP addresses linked to patientsaudit_trail.ip_address (when patient-linked)audit_trail tableL2 — Security + Auditors
14. Biometric identifiersFingerprints, retinal scans, voice printspatient.biometric_hashpatient_biometrics tableL4 — Clinical + Security
15. PhotosFull-face photospatient.photo_url, patient_documents.image_urlS3 storage with KMS encryptionL3 — Clinical staff only
16. Unique identifying numbersAny unique number/code/characteristicpatient.uuid, patient.custom_idpatients tableL3 — Clinical staff only
Indirect Identifiers
17. Relatives' namesNames of relatives, employers, household memberspatient.emergency_contact_name, patient.next_of_kinpatient_contacts tableL3 — Clinical staff only
18. Other characteristicsAny unique characteristic that could identify individualpatient_metadata JSONB (free-text notes)patient_metadata tableL3 — Clinical staff only

Access Control Levels:

  • L1 (Public): No PHI, publicly available information
  • L2 (Internal): De-identified data, administrative use, no direct identifiers
  • L3 (Restricted): PHI accessible to authorized clinical/billing staff, minimum necessary enforcement
  • L4 (Highly Restricted): Sensitive PHI (SSN, biometrics), enhanced audit logging, CISO approval required

2.2 PHI Sensitivity Classification

Beyond the 18 HIPAA identifiers, certain PHI requires additional protection due to sensitivity:

Sensitivity CategoryDescriptionBIO-QMS FieldsAdditional Controls
Standard PHIGeneral health information linked to identifiersAll L3 fields aboveStandard RBAC + minimum necessary
Sensitive PHI — Mental HealthPsychotherapy notes, mental health diagnosesclinical_notes.psychotherapy_note, diagnosis.mental_health_codeAdditional consent required, separate authorization
Sensitive PHI — Substance AbuseSubstance abuse treatment recordstreatment.substance_abuse_flag, diagnosis.substance_use_disorder42 CFR Part 2 compliance, separate consent
Sensitive PHI — HIV/AIDSHIV test results, AIDS diagnoseslab_results.hiv_status, diagnosis.hiv_codeState law compliance, enhanced confidentiality
Sensitive PHI — GeneticGenetic testing results, family historylab_results.genetic_test, patient.genetic_markersGINA compliance, special authorization
Sensitive PHI — ReproductivePregnancy status, reproductive healthclinical_notes.pregnancy_status, procedure.reproductive_codeEnhanced privacy, separate authorization

Enhanced Controls for Sensitive PHI:

  1. Dual Authorization: Requires both user role permission AND patient-specific consent
  2. Enhanced Audit Logging: All access logged with justification field (required)
  3. No Caching: Sensitive PHI NEVER cached in application or browser
  4. Redaction by Default: Masked in reports/exports unless explicitly authorized

2.3 PHI Data Flow Diagram

┌─────────────────────────────────────────────────────────────────────┐
│ PHI ENTRY POINTS (Data Ingress) │
├─────────────────────────────────────────────────────────────────────┤
│ 1. Clinical User Entry → Work Order creation, patient registration │
│ 2. HL7 FHIR API → External EHR integration (lab results, ADT) │
│ 3. File Upload → Medical device outputs, imaging reports │
│ 4. Vendor Systems → Equipment service records, IQ/OQ documentation │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ PHI PROCESSING (Data in Use) │
├─────────────────────────────────────────────────────────────────────┤
│ • Field-level encryption (AES-256-GCM) for L4 PHI │
│ • Row-level security (RLS) enforces tenant isolation │
│ • Application middleware filters PHI fields based on role │
│ • Audit log captures all PHI access with user, timestamp, purpose │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ PHI STORAGE (Data at Rest) │
├─────────────────────────────────────────────────────────────────────┤
│ • PostgreSQL database with KMS-encrypted columns (L3+) │
│ • S3 storage with server-side encryption (SSE-KMS) for documents │
│ • Backup encrypted with separate KMS key, cross-region replication │
│ • No PHI in application logs (scrubbed via regex + ML detection) │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ PHI EXIT POINTS (Data Egress) │
├─────────────────────────────────────────────────────────────────────┤
│ 1. User Interface → Filtered by role, field-level masking applied │
│ 2. API Responses → JSON responses filtered per minimum necessary │
│ 3. Reports/Exports → De-identification option, audit trail required│
│ 4. HL7 FHIR API → External systems via BAA, TLS 1.3 encryption │
│ 5. Audit Logs → Immutable S3 bucket, 10-year retention │
└─────────────────────────────────────────────────────────────────────┘

2.4 PHI Inventory by System Module

ModulePHI FieldsField CountAccess RolesSensitivity Level
Patient Registrypatients.* (all fields)28CLINICAL, BILLING, QAL3 (Standard PHI)
Work Orderswork_orders.description, work_orders.service_date, work_orders.account_number3CLINICAL, SYSTEM_OWNER, QAL3 (Standard PHI)
Clinical Notesclinical_notes.* (all text fields)12CLINICAL (assigned provider only)L3-L4 (Sensitive PHI if psychotherapy)
Lab Resultslab_results.* (all result fields)18CLINICAL, QAL3-L4 (Sensitive if genetic/HIV)
Audit Trailaudit_trail.patient_id, audit_trail.phi_accessed2AUDITOR, PRIVACY_OFFICERL2 (De-identified when possible)
Documentsdocuments.file_url, documents.description2CLINICAL, QAL3 (Standard PHI)
Billinginvoices.*, patient_insurance.*15BILLING, QAL3 (Standard PHI + L4 for SSN)

Total PHI Fields: 80+ across 7 modules Total Sensitive PHI Fields: 12 Total L4 (Highly Restricted) Fields: 4 (SSN, biometric identifiers, genetic results, HIV status)


3. Multi-Factor Authentication (MFA) for PHI Access

3.1 MFA Requirement Policy

Policy Statement: All users accessing Protected Health Information (PHI) MUST authenticate using multi-factor authentication (MFA). Single-factor authentication (password-only) is PROHIBITED for PHI access.

Regulatory Basis:

  • HIPAA §164.312(d) — Person or Entity Authentication
  • NIST SP 800-66 Rev 2, Section 3.6 — Multi-Factor Authentication
  • HHS Cybersecurity Newsletter (March 2023) — MFA strongly recommended for PHI access

Scope:

  • Human users accessing PHI via web interface, API, or mobile app
  • Service-to-service authentication requires mTLS + API key rotation (equivalent to MFA for non-human entities)
  • Break-glass emergency access requires MFA re-authentication (no exceptions)

3.2 Supported MFA Methods

MFA MethodTechnologySecurity LevelRecommended ForImplementation
TOTP (Time-based One-Time Password)RFC 6238 (6-digit codes, 30-second window)HighAll users (default)Google Authenticator, Authy, Microsoft Authenticator
WebAuthn/FIDO2 Hardware KeysFIDO2 standard (YubiKey, Titan Key)Very HighSystem Owners, QA, Admins (required)WebAuthn API, passwordless authentication
Push NotificationMobile app push (Duo, Okta Verify)HighClinical users (mobile workflow)Push notification via auth provider SDK
SMS OTPSMS text message (6-digit code)Medium (FALLBACK ONLY)Emergency account recoveryTwilio SMS gateway, rate-limited
Biometric (Mobile)Fingerprint, Face ID (device-local)HighMobile app usersDevice biometric + TOTP seed

MFA Method Selection Criteria:

User RolePrimary MFABackup MFARationale
CLINICALTOTPPush NotificationBalance of security and workflow efficiency
SYSTEM_OWNERFIDO2 Hardware KeyTOTPHighest privilege, hardware-backed security required
QAFIDO2 Hardware KeyTOTPApproval authority, hardware-backed security required
BILLINGTOTPPush NotificationStandard security, frequent access
ADMINFIDO2 Hardware KeyTOTP + SMS (recovery only)System-wide access, highest security required
AUDITORTOTPPush NotificationRead-only access, standard security
VENDORTOTPSMS (recovery only)Limited scope, standard security

Prohibited MFA Methods:

  • Email-based OTP (email not considered secure channel per NIST SP 800-63B)
  • Security questions (not cryptographically secure)
  • Single-factor passwordless (e.g., magic links without second factor)

3.3 MFA Enrollment Workflow

Initial Enrollment (Mandatory for all new users):

┌─────────────────────────────────────────────────────────────────────┐
│ STEP 1: Account Creation │
│ • User receives account invitation email (signed, tamper-evident) │
│ • Invitation link expires after 24 hours │
│ • User clicks link, redirected to enrollment portal │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 2: Password Setup │
│ • User creates password (PBKDF2-HMAC-SHA256, 600k iterations) │
│ • Password requirements: 14+ chars, upper+lower+digit+symbol │
│ • Password strength meter displayed (zxcvbn library) │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 3: MFA Method Selection │
│ • User selects primary MFA method (TOTP/FIDO2/Push) │
│ • System displays QR code (TOTP) or prompts hardware key (FIDO2) │
│ • User scans QR code with authenticator app or inserts hardware key│
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 4: MFA Verification │
│ • User enters first TOTP code or completes FIDO2 challenge │
│ • System validates code/challenge within 30-second window │
│ • If validation fails, allow 2 retries before locking account │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 5: Backup Codes Generation │
│ • System generates 10 single-use backup codes (16-digit) │
│ • User MUST download backup codes (PDF, encrypted with user pw) │
│ • User acknowledges backup codes saved securely │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 6: Enrollment Complete │
│ • User logged in, MFA status: ENROLLED │
│ • Audit log entry: USER_MFA_ENROLLED (includes method type) │
│ • User redirected to dashboard │
└─────────────────────────────────────────────────────────────────────┘

MFA Enrollment Enforcement:

  • Users without MFA CANNOT access any PHI-containing pages/APIs
  • Login redirects to MFA enrollment page until complete
  • Grace period: 7 days for existing users (legacy migration), 0 days for new users
  • Bypass: NONE (no exceptions, including executives)

3.4 Adaptive MFA (Risk-Based Step-Up)

Trigger Conditions for Step-Up Authentication:

Risk IndicatorDetection MethodActionExample
New DeviceDevice fingerprint (User-Agent + canvas hash + screen resolution)Require MFA re-authenticationUser logs in from new laptop → step-up required
Unusual LocationGeoIP detection (>100 miles from previous login)Require MFA + email notificationUser in New York logs in from California → step-up + alert
Sensitive OperationPHI bulk export (>100 records), SSN access, break-glass activationRequire MFA re-authentication within 5 minutesUser attempts to export patient list → step-up required
Failed Login Attempts3+ failed logins in 15 minutesRequire CAPTCHA + MFAPotential brute-force attack → enhanced verification
Impossible TravelLogin from geographically distant locations <1 hour apartBlock login + security reviewUser in NYC at 9am, login from London at 9:15am → blocked
Tor/VPN/Proxy DetectedIP reputation check (IPQualityScore API)Require MFA + manager approvalUser accesses via Tor exit node → blocked pending approval

Step-Up Authentication Flow:

# Example step-up logic (Python pseudocode)
def check_adaptive_mfa(user: User, request: HttpRequest) -> MFARequirement:
risk_score = 0

# Device fingerprint check
device_hash = hash_device_fingerprint(request)
if device_hash not in user.known_devices:
risk_score += 30
log_event('NEW_DEVICE_DETECTED', user, device_hash)

# GeoIP check
current_location = geoip_lookup(request.ip_address)
last_location = user.last_login_location
distance_miles = calculate_distance(current_location, last_location)
if distance_miles > 100:
risk_score += 20
log_event('UNUSUAL_LOCATION', user, f'{distance_miles} miles from previous login')

# Impossible travel check
time_since_last_login = (datetime.utcnow() - user.last_login_time).seconds / 3600
travel_speed = distance_miles / time_since_last_login if time_since_last_login > 0 else 0
if travel_speed > 600: # Faster than commercial flight
risk_score = 100 # Immediate block
log_event('IMPOSSIBLE_TRAVEL', user, f'{travel_speed} mph calculated')

# IP reputation check
ip_reputation = check_ip_reputation(request.ip_address)
if ip_reputation.is_vpn or ip_reputation.is_proxy or ip_reputation.is_tor:
risk_score += 40
log_event('VPN_PROXY_DETECTED', user, ip_reputation)

# Determine MFA requirement
if risk_score >= 100:
return MFARequirement.BLOCK # Block login, require security review
elif risk_score >= 50:
return MFARequirement.STEP_UP # Require MFA re-authentication
elif risk_score >= 20:
return MFARequirement.EMAIL_NOTIFICATION # Allow login, send notification
else:
return MFARequirement.STANDARD # Standard MFA (no step-up)

3.5 MFA for API Access

Service-to-Service Authentication (equivalent to MFA for non-human entities):

Authentication FactorTechnologyPurposeRotation Period
Factor 1: Service IdentitymTLS client certificate (ECDSA P-256)Cryptographically proves service identity90 days (automated via cert-manager)
Factor 2: API KeyHMAC-SHA256 signed API key with KMS secretProves possession of secret90 days (automated via Vault dynamic secrets)
Factor 3 (Optional): JWT TokenShort-lived JWT (1-hour expiration) signed with KMS keyTime-bound authorization1 hour (refresh token rotation on use)

API MFA Workflow:

Client Service → API Gateway
1. TLS handshake: Client presents mTLS certificate (Factor 1)
2. API Gateway validates certificate against internal CA
3. HTTP request includes API-Key header (Factor 2)
4. API Gateway validates API key signature with KMS
5. Optional: JWT token in Authorization header (Factor 3)
6. All factors valid → request forwarded to backend service
7. Any factor invalid → 401 Unauthorized + audit log entry

API Key Rotation Procedure:

#!/bin/bash
# Automated API key rotation (runs every 90 days via cron)

SERVICE_NAME="work-order-api"
NEW_API_KEY=$(vault write -field=api_key secret/api-keys/$SERVICE_NAME rotation=true)

# Update service configuration (Kubernetes Secret)
kubectl create secret generic ${SERVICE_NAME}-api-key \
--from-literal=api-key=$NEW_API_KEY \
--dry-run=client -o yaml | kubectl apply -f -

# Restart service to pick up new key (zero-downtime rolling restart)
kubectl rollout restart deployment/$SERVICE_NAME

# Audit log entry
echo "$(date -Iseconds) - API_KEY_ROTATED - service=$SERVICE_NAME" >> /var/log/api-key-rotation.log

3.6 MFA Recovery Procedures

Lost Device Scenarios:

ScenarioRecovery MethodApproval RequiredTimeframe
Lost TOTP Device (backup codes available)User enters backup code from saved listNoneImmediate
Lost TOTP Device (no backup codes)Manager submits recovery request via help deskManager approval + Security team verification4 hours (business hours)
Lost FIDO2 Hardware KeyUser authenticates with backup TOTP, orders new keyNone (if backup enrolled)Immediate (new key: 3-5 days shipping)
Lost Phone (SMS fallback)Temporary SMS OTP to registered phone numberSecurity team approval (verify identity)2 hours
Complete MFA Loss (no backups)In-person identity verification at facilityCISO approval + government ID verification24 hours

MFA Recovery Request Form:

# MFA Recovery Request

**Request ID:** MFA-REC-YYYY-NNNN
**User:** [Full Name, User ID]
**Date:** [YYYY-MM-DD]
**Requestor Role:** [CLINICAL / ADMIN / etc.]

## Reason for Recovery
- [ ] Lost TOTP device (no backup codes)
- [ ] Lost FIDO2 hardware key (no backup method)
- [ ] Lost phone (SMS recovery unavailable)
- [ ] Other: [Describe]

## Identity Verification
- **Government ID Verified:** [ ] Yes [ ] No (attach photo of ID)
- **Manager Confirmation:** [Manager name, signature, date]
- **Known Device Verification:** [ ] User accessing from known device (approved without additional verification)
- **Security Questions:** [Answer 3 pre-configured questions]

## Temporary Access Method
- [ ] SMS OTP to registered phone: [Phone number]
- [ ] Backup TOTP enrollment (new QR code)
- [ ] Temporary FIDO2 key issued (return required)

## Approval
| Role | Name | Signature | Date |
|------|------|-----------|------|
| Manager | | | |
| Security Team | | | |
| CISO (if required) | | | |

## Audit Trail
- Recovery initiated: [Timestamp]
- Temporary access granted: [Timestamp]
- Permanent MFA re-enrolled: [Timestamp]
- Temporary access revoked: [Timestamp]

3.7 MFA Audit and Monitoring

Logged MFA Events:

EventAudit LevelLogged AttributesRetention
MFA_ENROLLEDL2user_id, mfa_method, timestamp, enrollment_ip7 years
MFA_AUTHENTICATION_SUCCESSL1user_id, mfa_method, timestamp, ip_address1 year (sampled at 10%)
MFA_AUTHENTICATION_FAILUREL2user_id, mfa_method, failure_reason, timestamp, ip_address7 years
MFA_BACKUP_CODE_USEDL3user_id, timestamp, ip_address, remaining_codes7 years
MFA_RECOVERY_REQUESTEDL3user_id, recovery_reason, approver, timestamp10 years
MFA_METHOD_CHANGEDL3user_id, old_method, new_method, timestamp7 years
MFA_STEP_UP_TRIGGEREDL2user_id, risk_reason, timestamp7 years

Anomaly Detection Alerts:

Alert ConditionThresholdActionNotification
High MFA Failure Rate>5 failures in 15 minutes (single user)Lock account for 15 minutesUser + Security Team
Backup Code Exhaustion<3 backup codes remainingPrompt user to regenerateUser
Unusual MFA Method ChangeUser changes from FIDO2 to SMS (downgrade)Require manager approvalManager + Security Team
MFA Bypass AttemptAPI call without MFA tokenBlock request + P1 alertSecurity Team + CISO

4. Minimum Necessary Principle Implementation

4.1 Regulatory Requirement

HIPAA Privacy Rule §164.514(d)(3):

"A covered entity must limit the protected health information disclosed to the amount reasonably necessary to achieve the purpose of the disclosure."

BIO-QMS Implementation Strategy:

  1. Role-Based Field Filtering: Users see only PHI fields required for their job function
  2. Query-Level Enforcement: Database queries automatically filter PHI based on role
  3. Dynamic Masking: PHI fields masked/redacted in UI based on role authorization
  4. Purpose-Based Access: PHI access requires explicit purpose (e.g., "patient care", "billing", "quality review")

4.2 Role-Based PHI Access Matrix

RolePHI Access ScopeAccessible FieldsRestricted FieldsUse Cases
CLINICAL (Assigned Provider)Full PHI for assigned patients onlyAll PHI fields (18 identifiers + clinical notes)Other providers' patients (unless shared care)Direct patient care, treatment documentation
CLINICAL (Unassigned)De-identified data onlyWork order metadata (no names/MRN/DOB)All direct identifiersCoverage, consultations (requires explicit sharing)
BILLINGLimited PHI for billing purposesName, DOB, MRN, SSN (last 4 only), insurance numbersClinical notes, diagnosis details, photosInsurance claims, payment processing
QA (Quality Assurance)Limited PHI for compliance reviewName, MRN, service dates, work order detailsContact info (phone/email), full SSN, photosAudit compliance, quality metrics
SYSTEM_OWNERLimited PHI for asset managementWork order descriptions (redacted), service datesPatient names, contact info, clinical detailsEquipment maintenance, change control
ADMIN (IT Support)No PHI access (masked in support tools)None (all PHI fields masked with "PHI")All PHI fieldsSystem troubleshooting, database maintenance
AUDITORRead-only PHI (all fields)All PHI fields (for assigned audit scope)No write accessRegulatory audits, compliance reviews
VENDORNo PHI accessEquipment serial numbers, facility codes (no patient link)All PHI fieldsEquipment service, IQ/OQ testing

Field-Level Masking Examples:

FieldCLINICALBILLINGQAADMIN
patient.full_name"John Doe""John Doe""John Doe""PHI"
patient.date_of_birth"1985-03-15""1985-03-15""1985-XX-XX" (year only)"PHI"
patient.ssn"*--6789" (last 4)"*--6789" (last 4)"--***" (masked)"PHI"
patient.phone_number"+1-555-123-4567""+1-555-XXX-XXXX" (masked)"PHI""PHI"
patient.email"john.doe@example.com""PHI""PHI""PHI"
clinical_notes.text"Patient reports headache...""PHI""Patient reports headache..." (first 100 chars)"PHI"

4.3 Query-Level Enforcement (Database Middleware)

PostgreSQL Row-Level Security (RLS) + Application Middleware:

-- Example RLS policy: CLINICAL role sees only assigned patients
CREATE POLICY clinical_assigned_patients ON patients
FOR SELECT
USING (
patient_id IN (
SELECT patient_id FROM work_orders
WHERE assignee_id = current_setting('app.user_id')::uuid
)
OR patient_id IN (
SELECT patient_id FROM patient_care_team
WHERE provider_id = current_setting('app.user_id')::uuid
)
);

-- Example RLS policy: BILLING role sees limited fields
-- (Note: Field-level RLS requires views with SECURITY DEFINER functions)
CREATE VIEW patients_billing_view AS
SELECT
patient_id,
full_name,
date_of_birth,
mrn,
substring(ssn, 8, 4) AS ssn_last4, -- Last 4 digits only
'***PHI***'::text AS phone_number, -- Masked
'***PHI***'::text AS email -- Masked
FROM patients;

GRANT SELECT ON patients_billing_view TO billing_role;

Application Middleware (TypeScript):

// Example: PHI field filtering middleware
import { Request, Response, NextFunction } from 'express';

interface PHIFieldConfig {
[role: string]: {
allowedFields: string[];
maskedFields: { [field: string]: (value: any) => string };
};
}

const PHI_ACCESS_CONFIG: PHIFieldConfig = {
CLINICAL: {
allowedFields: ['patient_id', 'full_name', 'date_of_birth', 'mrn', 'phone_number', 'email', 'ssn', 'clinical_notes'],
maskedFields: {
ssn: (value) => `***-**-${value.slice(-4)}`, // Last 4 only
},
},
BILLING: {
allowedFields: ['patient_id', 'full_name', 'date_of_birth', 'mrn'],
maskedFields: {
ssn: (value) => `***-**-${value.slice(-4)}`,
phone_number: () => '***PHI***',
email: () => '***PHI***',
},
},
ADMIN: {
allowedFields: [], // No PHI access
maskedFields: {
'*': () => '***PHI***', // Mask all fields
},
},
};

export function filterPHI(role: string) {
return (req: Request, res: Response, next: NextFunction) => {
const originalJson = res.json.bind(res);

res.json = (data: any) => {
const config = PHI_ACCESS_CONFIG[role];

if (!config) {
// No config for role → deny all PHI
data = maskAllPHI(data);
} else {
data = applyFieldFiltering(data, config);
}

// Audit log: PHI fields accessed
auditPHIAccess(req.user.id, role, extractPHIFields(data), req.path);

return originalJson(data);
};

next();
};
}

function applyFieldFiltering(data: any, config: PHIFieldConfig[string]): any {
if (Array.isArray(data)) {
return data.map(item => applyFieldFiltering(item, config));
}

if (typeof data === 'object' && data !== null) {
const filtered: any = {};

for (const [key, value] of Object.entries(data)) {
if (config.allowedFields.includes(key)) {
// Field allowed, check if masking required
if (config.maskedFields[key]) {
filtered[key] = config.maskedFields[key](value);
} else {
filtered[key] = value;
}
} else if (config.maskedFields['*']) {
// Wildcard mask (e.g., ADMIN role masks all)
filtered[key] = config.maskedFields['*'](value);
}
// Omit fields not in allowedFields (minimum necessary)
}

return filtered;
}

return data;
}

4.4 Purpose-Based Access Control

Access Purpose Categories:

Purpose CodeDescriptionRequires Patient ConsentAudit Retention
TREATMENTDirect patient care, diagnosis, treatment planningNo (TPO exception)7 years
PAYMENTBilling, insurance claims, payment processingNo (TPO exception)7 years
OPERATIONSQuality improvement, compliance audits, trainingNo (TPO exception)7 years
RESEARCHClinical research, de-identified analyticsYes (IRB approval + patient consent)10 years
MARKETINGPromotional communications, fundraisingYes (opt-in consent required)7 years
DISCLOSUREThird-party disclosure (e.g., legal, public health)Yes (unless required by law)10 years
EMERGENCYBreak-glass emergency accessNo (emergency exception)10 years + enhanced review

Purpose Enforcement Workflow:

User Requests PHI Access

System Prompts: "What is the purpose of accessing this patient's PHI?"

User Selects Purpose (dropdown): [TREATMENT | PAYMENT | OPERATIONS | RESEARCH | OTHER]

If purpose = RESEARCH or MARKETING or DISCLOSURE:
→ System checks for patient consent record
→ If no consent: DENY access + prompt to obtain consent

If purpose = EMERGENCY (break-glass):
→ Trigger break-glass workflow (Section 6)

System logs purpose in audit trail:
- user_id, patient_id, purpose_code, timestamp, access_justification (free text)

Access granted with minimum necessary fields for stated purpose

Purpose-Based Field Filtering:

PurposeAccessible PHI FieldsRationale
TREATMENTAll clinical fields (names, DOB, MRN, clinical notes, lab results)Comprehensive care requires full clinical context
PAYMENTName, DOB, MRN, insurance numbers, service dates, billing codesMinimum needed for insurance claims
OPERATIONSDe-identified data preferred; if identified, limited to MRN + service datesQuality metrics rarely require direct identifiers
RESEARCHDe-identified data REQUIRED (unless IRB waiver + patient consent)HIPAA de-identification safe harbor or expert determination
EMERGENCYAll PHI fields (no restrictions during active emergency)Life-threatening situations override minimum necessary

4.5 PHI Access Request Workflow (Non-Routine Access)

When Required: User needs PHI access outside normal role scope (e.g., admin troubleshooting, cross-department consult)

Request Process:

# PHI Access Request Form

**Request ID:** PHI-REQ-YYYY-NNNN
**Requestor:** [Name, User ID, Current Role]
**Date:** [YYYY-MM-DD]

## Access Details
- **Patient(s):** [Patient MRN or "All patients in [department]"]
- **PHI Fields Requested:** [List specific fields, e.g., "name, DOB, MRN"]
- **Access Level:** [ ] Read-only [ ] Read-write
- **Duration:** [Start date] to [End date] (max 90 days)

## Justification
- **Purpose:** [ ] Treatment [ ] Payment [ ] Operations [ ] Research [ ] Other
- **Business Justification:** [Explain why access is necessary]
- **Minimum Necessary Analysis:** [Why broader access cannot meet need]

## Approval Chain
| Approver | Role | Signature | Date |
|----------|------|-----------|------|
| Direct Manager | | | |
| Privacy Officer | | | (Required for non-TPO purposes) |
| CISO | | | (Required for admin PHI access) |

## System Action
- [ ] Temporary role elevation granted (duration: [X] days)
- [ ] Enhanced audit logging enabled (all access logged at L3)
- [ ] Access automatically revoked on [expiration date]
- [ ] Post-access review scheduled (30 days after expiration)

Automated Expiration:

  • Access grants expire automatically on end date (no manual revocation needed)
  • User receives email notification 7 days before expiration
  • Access review triggered 30 days post-expiration (verify no unauthorized retention)

5. Session Management and Timeout Controls

5.1 Session Timeout Requirements

HIPAA §164.312(a)(2)(iii) — Automatic Logoff (Addressable, IMPLEMENTED):

ParameterValueRationaleConfigurable Range
Idle Timeout15 minutes (default)Balance security and workflow efficiency per HHS guidance10-30 minutes (per role)
Absolute Timeout8 hours (shift-based)Prevent session hijacking for long-running sessions4-12 hours (per role)
Grace Warning2 minutes before timeoutUser experience (allow save before disconnect)1-5 minutes
Concurrent Sessions1 active PHI session per userPrevent credential sharing1-3 (per role)
Re-authentication for Sensitive Ops5 minutes (signatures, break-glass)FDA Part 11 §11.100(b) requirementNon-configurable

Role-Specific Timeout Configuration:

RoleIdle TimeoutAbsolute TimeoutConcurrent SessionsRationale
CLINICAL15 minutes12 hours (long shifts)2 (desktop + mobile)Clinical workflow flexibility
BILLING20 minutes8 hours1Less sensitive than clinical, standard office hours
QA10 minutes8 hours1Compliance role, shorter timeout appropriate
ADMIN10 minutes4 hours1High privilege, shortest timeout
AUDITOR30 minutes8 hours1Read-only access, longer timeout acceptable
VENDOR15 minutes4 hours1External user, shorter timeout appropriate

5.2 Session Token Security

JWT Session Token Configuration:

{
"token_type": "JWT",
"algorithm": "RS256",
"signing_key": "KMS-managed RSA-2048 key",
"claims": {
"iss": "https://auth.coditect-bio-qms.com",
"sub": "user-uuid",
"aud": "https://api.coditect-bio-qms.com",
"exp": 3600, // 1 hour expiration
"iat": 1708095120,
"nbf": 1708095120,
"jti": "unique-token-id",
"custom_claims": {
"role": "CLINICAL",
"tenant_id": "tenant-uuid",
"mfa_verified": true,
"mfa_timestamp": 1708095120,
"session_id": "session-uuid",
"device_fingerprint": "hash-of-device"
}
},
"http_security": {
"cookie_flags": ["HttpOnly", "Secure", "SameSite=Strict"],
"csrf_token": "separate-csrf-token-in-header",
"token_binding": "TLS-channel-id" // Bind token to TLS session
}
}

Token Rotation Strategy:

Token TypeLifetimeRotation TriggerRefresh Mechanism
Access Token (JWT)1 hourExpirationRefresh token exchange (no re-authentication required)
Refresh Token7 daysEach useNew refresh token issued on each refresh (single-use, rotating)
Session CookieSession (browser close)LogoutNew session on next login
CSRF TokenSessionSession changeRegenerated on privilege escalation

Refresh Token Flow:

User Login (MFA verified)
→ System issues Access Token (1hr) + Refresh Token (7d)

Access Token expires after 1 hour
→ Client sends Refresh Token to /auth/refresh endpoint

System validates Refresh Token:
- Token signature valid (KMS verification)
- Token not revoked (check revocation list in Redis)
- Token not expired (7-day lifetime)
- Device fingerprint matches (prevent token theft)

If valid:
→ Issue new Access Token (1hr) + new Refresh Token (7d, single-use)
→ Revoke old Refresh Token (add to Redis revocation list)

If invalid:
→ Require full re-authentication (username + password + MFA)

5.3 Idle Detection and Grace Warning

Client-Side Idle Detection (JavaScript):

// Session idle timeout manager
class SessionIdleManager {
private idleTimeoutMs: number;
private graceWarningMs: number;
private idleTimer: number | null = null;
private warningTimer: number | null = null;
private lastActivityTime: number = Date.now();

constructor(idleTimeoutMinutes: number, graceWarningMinutes: number) {
this.idleTimeoutMs = idleTimeoutMinutes * 60 * 1000;
this.graceWarningMs = graceWarningMinutes * 60 * 1000;

// Monitor user activity
['mousedown', 'keypress', 'scroll', 'touchstart'].forEach(event => {
document.addEventListener(event, () => this.resetIdleTimer(), true);
});

this.resetIdleTimer();
}

private resetIdleTimer() {
this.lastActivityTime = Date.now();

// Clear existing timers
if (this.idleTimer) clearTimeout(this.idleTimer);
if (this.warningTimer) clearTimeout(this.warningTimer);

// Set warning timer (e.g., 13 minutes for 15-minute timeout)
this.warningTimer = window.setTimeout(() => {
this.showGraceWarning();
}, this.idleTimeoutMs - this.graceWarningMs);

// Set idle timeout timer (e.g., 15 minutes)
this.idleTimer = window.setTimeout(() => {
this.handleIdleTimeout();
}, this.idleTimeoutMs);
}

private showGraceWarning() {
const remainingSeconds = Math.floor(this.graceWarningMs / 1000);

// Display modal warning
const modal = document.createElement('div');
modal.className = 'session-timeout-warning';
modal.innerHTML = `
<div class="modal-content">
<h3>Session Expiring Soon</h3>
<p>Your session will expire in <strong id="countdown">${remainingSeconds}</strong> seconds due to inactivity.</p>
<p>Any unsaved work will be lost.</p>
<button id="stay-logged-in">Stay Logged In</button>
<button id="logout-now">Logout Now</button>
</div>
`;
document.body.appendChild(modal);

// Countdown timer
let countdown = remainingSeconds;
const interval = setInterval(() => {
countdown--;
document.getElementById('countdown')!.textContent = countdown.toString();
if (countdown <= 0) clearInterval(interval);
}, 1000);

// Button handlers
document.getElementById('stay-logged-in')!.addEventListener('click', () => {
this.resetIdleTimer();
document.body.removeChild(modal);
clearInterval(interval);

// Extend server-side session
fetch('/auth/extend-session', { method: 'POST' });
});

document.getElementById('logout-now')!.addEventListener('click', () => {
this.logout();
});
}

private handleIdleTimeout() {
// Log user out
this.logout();

// Redirect to login page with timeout message
window.location.href = '/login?reason=idle_timeout';
}

private logout() {
// Call logout API
fetch('/auth/logout', { method: 'POST' });

// Clear local storage
localStorage.clear();
sessionStorage.clear();

// Audit log
console.log('Session terminated due to idle timeout');
}
}

// Initialize on page load with role-specific timeout
const userRole = getCurrentUserRole(); // From API
const idleTimeout = getRoleIdleTimeout(userRole); // e.g., 15 minutes for CLINICAL
const sessionManager = new SessionIdleManager(idleTimeout, 2); // 2-minute grace warning

Server-Side Session Validation:

# Example: Server-side session timeout enforcement (Python/Django)
from datetime import datetime, timedelta
from django.contrib.sessions.models import Session
from django.core.cache import cache

def check_session_timeout(request):
"""Enforce idle and absolute timeout on server side"""

session_id = request.session.session_key
user = request.user

# Get session metadata from cache (Redis)
session_meta = cache.get(f'session:{session_id}')

if not session_meta:
# Session not found or expired
return logout_user(request, reason='session_expired')

# Check idle timeout
last_activity = datetime.fromisoformat(session_meta['last_activity'])
idle_timeout_minutes = get_role_idle_timeout(user.role)
if datetime.utcnow() - last_activity > timedelta(minutes=idle_timeout_minutes):
audit_log('SESSION_IDLE_TIMEOUT', user.id, session_id, f'{idle_timeout_minutes} minutes')
return logout_user(request, reason='idle_timeout')

# Check absolute timeout
session_start = datetime.fromisoformat(session_meta['session_start'])
absolute_timeout_hours = get_role_absolute_timeout(user.role)
if datetime.utcnow() - session_start > timedelta(hours=absolute_timeout_hours):
audit_log('SESSION_ABSOLUTE_TIMEOUT', user.id, session_id, f'{absolute_timeout_hours} hours')
return logout_user(request, reason='absolute_timeout')

# Update last activity timestamp
session_meta['last_activity'] = datetime.utcnow().isoformat()
cache.set(f'session:{session_id}', session_meta, timeout=86400) # 24 hour cache

return True # Session valid

def logout_user(request, reason):
"""Logout user and audit log"""
user_id = request.user.id
session_id = request.session.session_key

# Audit log
audit_log('SESSION_TERMINATED', user_id, session_id, reason)

# Revoke refresh tokens
RefreshToken.objects.filter(user=request.user).delete()

# Clear session
request.session.flush()

# Return 401 Unauthorized (client will redirect to login)
from django.http import JsonResponse
return JsonResponse({'error': 'Session expired', 'reason': reason}, status=401)

5.4 Concurrent Session Limits

Enforcement Strategy:

ScenarioActionUser Experience
User logs in from 2nd device (limit=1)Terminate oldest session, send notification to old deviceOld device: "You have been logged out because you logged in from another device"
User logs in from 3rd device (limit=2)Terminate oldest session, keep 2 most recentUsers manage their own active sessions via "Active Sessions" page
Suspicious concurrent sessions (different geographies)Require MFA re-authentication on all sessions, alert security teamModal: "Unusual activity detected. Please verify your identity."

Active Sessions Management UI:

User Profile → Active Sessions

┌─────────────────────────────────────────────────────────────────────┐
│ Active Sessions (2/2) │
├─────────────────────────────────────────────────────────────────────┤
│ 1. Current Session (this device) │
│ • Device: Chrome 121.0 on macOS │
│ • Location: New York, NY, USA │
│ • IP: 192.168.1.100 │
│ • Started: 2026-02-16 08:30 AM │
│ • Last Activity: 2026-02-16 10:15 AM │
│ [THIS DEVICE] │
│ │
│ 2. Other Session │
│ • Device: Safari 17.2 on iOS 17.3 │
│ • Location: New York, NY, USA │
│ • IP: 192.168.1.101 │
│ • Started: 2026-02-16 07:00 AM │
│ • Last Activity: 2026-02-16 09:45 AM │
│ [TERMINATE SESSION] ← User can manually terminate │
└─────────────────────────────────────────────────────────────────────┘

6. Break-Glass Emergency Access

6.1 Emergency Access Policy

Purpose: Provide temporary elevated PHI access during life-threatening emergencies or disaster scenarios when normal authorization channels are unavailable.

Regulatory Basis:

  • HIPAA §164.312(a)(2)(ii) — Emergency Access Procedure (Required)
  • HIPAA §164.308(a)(4) — Access Authorization

Scope:

  • Individual Patient Emergency: Clinician needs immediate access to specific patient's PHI (e.g., unconscious patient, no normal access authorization)
  • Mass Casualty/Disaster: System-wide PHI access needed during disaster response (e.g., hospital evacuation, mass shooting)

Prohibited Uses:

  • Convenience access (forgot password, normal workflow delays)
  • Non-emergency troubleshooting
  • Routine after-hours access

6.2 Break-Glass Workflow

Individual Patient Emergency Access:

┌─────────────────────────────────────────────────────────────────────┐
│ STEP 1: Emergency Declaration │
│ • User clicks "Emergency Access" button (prominent red UI) │
│ • System displays warning: "EMERGENCY ACCESS — AUDITED AND REVIEWED"│
│ • User must declare emergency reason (dropdown + free text): │
│ [ ] Life-threatening patient condition │
│ [ ] Unconscious patient (no consent possible) │
│ [ ] Disaster/mass casualty event │
│ [ ] Other (requires detailed justification) │
│ • User acknowledges: "I understand this access will be reviewed by │
│ the Privacy Officer and inappropriate use may result in sanctions"│
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 2: MFA Re-Authentication │
│ • User MUST re-authenticate with MFA (no bypass) │
│ • TOTP code or FIDO2 challenge required │
│ • Failed MFA → deny break-glass access + P1 alert │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 3: Temporary Access Grant │
│ • System grants PHI access for specified patient (individual) or │
│ all patients in facility (disaster) │
│ • Access level: Read + Write (clinical context required) │
│ • Duration: 4 hours (auto-expire, non-renewable) │
│ • Enhanced audit logging: Separate "break_glass_audit" stream │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 4: Immediate Notifications │
│ • Privacy Officer: Email + SMS alert (immediate) │
│ • Security Team: PagerDuty alert (P2 severity) │
│ • User's Manager: Email notification (informational) │
│ • Notification includes: User, patient, reason, timestamp │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 5: Access Expires Automatically │
│ • After 4 hours: Access automatically revoked │
│ • User receives 15-minute warning before expiration │
│ • No extension possible (must re-declare emergency if ongoing) │
└──────────────────┬──────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ STEP 6: Post-Access Review (within 24 hours) │
│ • Privacy Officer reviews access justification │
│ • Audit trail includes: All PHI fields accessed, timestamps, actions│
│ • Review outcome: │
│ → JUSTIFIED: Document as appropriate emergency use │
│ → UNJUSTIFIED: Security incident + disciplinary action │
│ • Review documented in compliance system │
└─────────────────────────────────────────────────────────────────────┘

Mass Casualty/Disaster Access (System-Wide):

TriggerAuthorizationAccess ScopeDurationReview
Facility EvacuationIncident Commander declarationAll patients in affected facility12 hoursPost-incident review (72 hours)
Mass Shooting/TerrorismIncident Commander + CISO approvalAll patients system-wide24 hoursPost-incident review (7 days)
Natural Disaster (Hurricane, Earthquake)Incident Commander + Executive approvalAll patients in affected region48 hours (renewable with approval)Post-incident review (30 days)
Cyberattack/System OutageCISO + CTO approvalMinimum necessary for continuity of careUntil system restoredPost-incident review (72 hours)

6.3 Break-Glass Access Types

Access TypeScenarioGranted PermissionsTypical Use Cases
Individual Patient Read-OnlyProvider needs to review patient history (emergency consult)View all PHI fields for 1 patient, no write accessER physician consulting on unfamiliar patient
Individual Patient Read-WriteProvider treating emergency patientView + Edit all PHI fields for 1 patientUnconscious patient, no advance directive accessible
Department-Wide Read-OnlyDisaster drill, quality reviewView PHI for all patients in departmentMass casualty triage, evacuation preparation
System-Wide Read-WriteActive disaster responseView + Edit PHI for all patients system-wideHospital evacuation, EHR system failure

6.4 Break-Glass Audit Trail

Enhanced Logging Requirements:

Audit FieldRequiredExamplePurpose
event_typeYesBREAK_GLASS_ACTIVATEDDifferentiate from normal access
user_idYesU-8472Who activated break-glass
patient_idYes (individual)P-12345 or * (system-wide)Which patient(s) accessed
emergency_reason_codeYesLIFE_THREATENINGCategorized reason
emergency_justificationYes"Patient unconscious, no family contact, severe allergic reaction"Free-text explanation
access_scopeYesINDIVIDUAL_READ_WRITEType of access granted
access_durationYes4 hoursHow long access valid
mfa_verifiedYestrueConfirm MFA completed
notification_sentYesprivacy_officer@coditect.ai, security_team@coditect.aiWho was notified
phi_fields_accessedYes["full_name", "date_of_birth", "clinical_notes", "medications"]Specific PHI accessed
access_timestampYes2026-02-16T14:32:18.123456ZWhen access occurred
expiration_timestampYes2026-02-16T18:32:18.123456ZWhen access expires
review_statusYesPENDINGTracking review completion
review_outcomeNo (added post-review)JUSTIFIED or UNJUSTIFIEDPrivacy Officer determination
reviewer_idNo (added post-review)U-9999Who reviewed
review_timestampNo (added post-review)2026-02-17T08:00:00.000000ZWhen reviewed

Separate Audit Stream:

// Example break-glass audit log entry
{
"entry_id": "BG-2026-02-16-00042",
"event_type": "BREAK_GLASS_ACTIVATED",
"timestamp": "2026-02-16T14:32:18.123456Z",
"user_id": "U-8472",
"user_role": "CLINICAL",
"patient_id": "P-12345",
"emergency_reason_code": "LIFE_THREATENING",
"emergency_justification": "Patient unconscious after severe allergic reaction. No family contact available. Need to review medication history to avoid drug interactions.",
"access_scope": "INDIVIDUAL_READ_WRITE",
"access_duration_hours": 4,
"expiration_timestamp": "2026-02-16T18:32:18.123456Z",
"mfa_verified": true,
"mfa_method": "TOTP",
"notification_sent": [
"privacy_officer@coditect.ai",
"security_team@coditect.ai",
"manager_john_doe@coditect.ai"
],
"phi_fields_accessed": [
"full_name",
"date_of_birth",
"mrn",
"clinical_notes",
"medications",
"allergies",
"lab_results"
],
"review_status": "PENDING",
"review_deadline": "2026-02-17T14:32:18.123456Z" // 24 hours
}

6.5 Break-Glass Metrics Dashboard

Privacy Officer Dashboard (Real-Time Monitoring):

Break-Glass Access Monitoring — Last 30 Days

┌─────────────────────────────────────────────────────────────────────┐
│ Summary Metrics │
├─────────────────────────────────────────────────────────────────────┤
│ • Total Break-Glass Events: 12 │
│ • Pending Review: 2 (within 24-hour window) │
│ • Justified: 9 (75%) │
│ • Unjustified: 1 (8.3%) → 1 disciplinary action initiated │
│ • Average Review Time: 6.2 hours │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ Recent Events (Last 7 Days) │
├─────────────────────────────────────────────────────────────────────┤
│ 1. 2026-02-16 14:32 | Dr. Jane Smith | Patient P-12345 │
│ Reason: Life-threatening allergic reaction │
│ Status: ✅ JUSTIFIED (reviewed 2026-02-17 08:15) │
│ │
│ 2. 2026-02-15 22:10 | Nurse Bob Johnson | Patient P-67890 │
│ Reason: Unconscious patient, no consent │
│ Status: ⏳ PENDING REVIEW (due: 2026-02-16 22:10) │
│ │
│ 3. 2026-02-14 16:45 | Dr. Alice Williams | Patient P-11111 │
│ Reason: "Need to check something" │
│ Status: ❌ UNJUSTIFIED (reviewed 2026-02-15 09:00) │
│ Action: Disciplinary notice issued, training required │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ Usage Patterns │
├─────────────────────────────────────────────────────────────────────┤
│ • Most Common Reason: Life-threatening condition (58%) │
│ • Most Common Department: Emergency Department (75%) │
│ • Peak Usage Time: 10pm - 6am (night shift) │
│ • Unjustified Rate Trend: 8% → 5% (improving with training) │
└─────────────────────────────────────────────────────────────────────┘

6.6 Break-Glass Review Procedures

Post-Access Review Checklist:

# Break-Glass Access Review

**Event ID:** BG-2026-02-16-00042
**Reviewer:** [Privacy Officer Name]
**Review Date:** [YYYY-MM-DD]

## Access Details
- **User:** Dr. Jane Smith (U-8472)
- **Patient:** John Doe (P-12345)
- **Date/Time:** 2026-02-16 14:32:18 UTC
- **Reason Stated:** "Patient unconscious after severe allergic reaction..."
- **Access Duration:** 4 hours (expired 2026-02-16 18:32:18)

## Review Questions
1. Was the emergency access justified based on the stated reason?
- [ ] Yes — Life-threatening situation, normal access not feasible
- [ ] No — Reason does not meet emergency criteria
- [ ] Unclear — Requires additional investigation

2. Did the user access only the minimum necessary PHI?
- [ ] Yes — Access appropriate for emergency treatment
- [ ] No — Excessive access beyond stated reason
- Review: PHI fields accessed were [list], appropriate for [stated reason]

3. Was there an alternative authorization method available?
- [ ] No — Emergency access was only option
- [ ] Yes — User could have obtained normal authorization via [method]

4. Did the user document the emergency access in clinical notes?
- [ ] Yes — Clinical notes include reference to break-glass access
- [ ] No — No documentation found (follow-up required)

5. Were there any policy violations or suspicious patterns?
- [ ] No violations detected
- [ ] Potential violation: [describe]
- Pattern check: User has [X] break-glass events in last 90 days

## Review Determination
- [ ] **JUSTIFIED** — Access appropriate, no further action required
- [ ] **UNJUSTIFIED** — Access inappropriate, initiate disciplinary process
- [ ] **REQUIRES INVESTIGATION** — Escalate to Security Team + HR

## Follow-Up Actions
- [ ] Document outcome in compliance system
- [ ] If unjustified: Notify manager and HR for disciplinary action
- [ ] If pattern detected: Recommend additional training
- [ ] Update break-glass metrics dashboard

## Privacy Officer Signature
**Signature:** [Digital Signature]
**Date:** [YYYY-MM-DD]
**Review Completion Time:** [X] hours after access

7. Access Review and Recertification

7.1 Quarterly Access Reviews

Objective: Ensure PHI access authorizations remain appropriate for job function (minimum necessary principle enforcement).

Review Frequency:

  • Quarterly: All active users with PHI access
  • Semi-Annual: Full recertification with role attestation
  • Annual: Comprehensive audit with external auditor involvement

Review Process:

QUARTER END (Q1: March 31, Q2: June 30, Q3: Sept 30, Q4: Dec 31)

System generates access review reports for each manager

Manager receives email with user access list (PDF + CSV export)

Manager reviews each user:
- Role still appropriate?
- PHI access still required for current job function?
- Any changes needed (role change, access revocation)?

Manager submits review form (due: 15 days after quarter end)

Privacy Officer audits manager reviews:
- Incomplete reviews flagged (escalate to manager's manager)
- High-risk roles (ADMIN, QA) reviewed directly by Privacy Officer

System implements access changes:
- Revoked access → user notified, access removed within 24 hours
- Role changes → new role applied, audit log entry created

Quarterly access review report published (Board of Directors)

Access Review Report Template:

# Quarterly Access Review — Q1 2026

**Review Period:** January 1, 2026 — March 31, 2026
**Report Date:** April 15, 2026
**Prepared By:** Privacy Officer

## Executive Summary
- Total Active Users with PHI Access: 247
- Users Reviewed: 247 (100%)
- Managers Completed Reviews: 18/18 (100%)
- Access Revoked: 12 users (departed employees)
- Role Changes: 8 users (department transfers)
- Stale Access Detected: 3 users (>90 days no login, access disabled)
- Exceptions Identified: 1 user (admin with PHI access, requires CISO approval)

## Review Compliance
| Department | Manager | Users | Review Complete | On Time |
|------------|---------|-------|-----------------|---------|
| Clinical Operations | Dr. Jane Smith | 45 | ✅ Yes | ✅ Yes |
| Billing | John Doe | 18 | ✅ Yes | ✅ Yes |
| Quality Assurance | Alice Johnson | 12 | ✅ Yes | ⚠️ Late (2 days) |
| IT/Admin | Bob Williams | 8 | ✅ Yes | ✅ Yes |
| ... | ... | ... | ... | ... |

## Access Changes Summary
| User | Previous Role | New Role | Reason | Effective Date |
|------|--------------|----------|--------|----------------|
| Sarah Lee | CLINICAL | BILLING | Department transfer | 2026-04-01 |
| Mike Chen | BILLING | (Revoked) | Termination | 2026-03-15 |
| ... | ... | ... | ... | ... |

## Stale Access Detection
| User | Last Login | Days Inactive | Action |
|------|-----------|---------------|--------|
| Tom Brown | 2025-12-20 | 102 days | PHI access disabled, notified manager |
| ... | ... | ... | ... |

## Exceptions Requiring Review
| User | Role | Issue | Approval Status |
|------|------|-------|-----------------|
| Admin User X | ADMIN | PHI access for troubleshooting | Approved by CISO (exception EXC-2026-042) |

## Recommendations
1. Implement automated stale access detection (>90 days)
2. Require MFA for all ADMIN users (currently 6/8 have MFA)
3. Review access review deadline (15 days may be too tight for large departments)

**Approval:**
**Privacy Officer Signature:** [Digital Signature]
**Date:** 2026-04-15

7.2 Semi-Annual Recertification

Objective: Full role attestation by managers (certify each user's role is correct).

Process:

  1. Manager receives complete user list with current roles
  2. Manager MUST certify each user individually:
    • Role is correct
    • PHI access is minimum necessary for role
    • User has completed annual HIPAA training
    • No known policy violations or concerns
  3. Manager signs certification form (digital signature required)
  4. Uncertified users → PHI access suspended until certification complete

Certification Form:

# Semi-Annual Role Recertification — H1 2026

**Department:** [Department Name]
**Manager:** [Manager Name, Title]
**Certification Period:** January 1, 2026 — June 30, 2026

I hereby certify that the following users require PHI access for their job functions, and their assigned roles enforce the minimum necessary principle:

| User | Role | PHI Access Required | Training Complete | Certification |
|------|------|---------------------|-------------------|---------------|
| Dr. Jane Doe | CLINICAL | ✅ Yes | ✅ Yes (2026-01-15) | ✅ CERTIFIED |
| John Smith | BILLING | ✅ Yes | ✅ Yes (2026-02-20) | ✅ CERTIFIED |
| Alice Johnson | QA | ✅ Yes | ✅ Yes (2026-01-10) | ✅ CERTIFIED |
| ... | ... | ... | ... | ... |

**Manager Attestation:**
I certify that the above information is accurate to the best of my knowledge, and that each user's PHI access is limited to the minimum necessary for their job function per HIPAA §164.514(d).

**Manager Signature:** [Digital Signature]
**Date:** [YYYY-MM-DD]

7.3 Stale Access Detection and Remediation

Detection Rules:

ConditionDetection MethodAutomated ActionManual Review
No login in 90 daysCron job queries last_login_time from databaseEmail warning to user + managerPrivacy Officer reviews (potential departed employee)
No PHI access in 90 daysQuery audit trail for PHI access eventsEmail notification (informational)No action (user may not need frequent access)
No login in 180 daysCron job queries last_login_timeDisable account automaticallyManager must re-enable with justification
Role change without access reviewDetect role change events without corresponding access reviewRequire manager attestationPrivacy Officer reviews high-risk roles

Automated Stale Access Job (Python):

#!/usr/bin/env python3
"""
Stale access detection and remediation script.
Runs daily via cron at 2am UTC.
Location: scripts/compliance/stale-access-detection.py
"""

import smtplib
from datetime import datetime, timedelta
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

# Database connection (PostgreSQL)
import psycopg2

def detect_stale_access():
conn = psycopg2.connect(
host="db.coditect-bio-qms.internal",
database="bio_qms",
user="compliance_readonly",
password=get_secret("db_compliance_readonly_password")
)
cursor = conn.cursor()

# Detect users with no login in 90 days
cursor.execute("""
SELECT user_id, email, full_name, role, last_login_time, manager_email
FROM users
WHERE phi_access = true
AND last_login_time < NOW() - INTERVAL '90 days'
AND status = 'ACTIVE'
""")

stale_users_90d = cursor.fetchall()

for user in stale_users_90d:
user_id, email, full_name, role, last_login_time, manager_email = user
days_inactive = (datetime.utcnow() - last_login_time).days

# Send warning email
send_stale_access_warning(
user_email=email,
user_name=full_name,
days_inactive=days_inactive,
manager_email=manager_email
)

# Audit log
audit_log('STALE_ACCESS_DETECTED_90D', user_id, f'{days_inactive} days inactive')

# Detect users with no login in 180 days → AUTO-DISABLE
cursor.execute("""
SELECT user_id, email, full_name, role, last_login_time, manager_email
FROM users
WHERE phi_access = true
AND last_login_time < NOW() - INTERVAL '180 days'
AND status = 'ACTIVE'
""")

stale_users_180d = cursor.fetchall()

for user in stale_users_180d:
user_id, email, full_name, role, last_login_time, manager_email = user
days_inactive = (datetime.utcnow() - last_login_time).days

# Auto-disable account
cursor.execute("""
UPDATE users
SET status = 'DISABLED_STALE_ACCESS',
phi_access = false,
disabled_at = NOW(),
disabled_reason = 'Stale access: No login for 180+ days'
WHERE user_id = %s
""", (user_id,))
conn.commit()

# Send notification
send_account_disabled_notification(
user_email=email,
user_name=full_name,
days_inactive=days_inactive,
manager_email=manager_email
)

# Audit log
audit_log('ACCOUNT_DISABLED_STALE_ACCESS', user_id, f'{days_inactive} days inactive, auto-disabled')

# Summary report to Privacy Officer
send_stale_access_report(
stale_90d_count=len(stale_users_90d),
stale_180d_count=len(stale_users_180d)
)

cursor.close()
conn.close()

def send_stale_access_warning(user_email, user_name, days_inactive, manager_email):
"""Send warning email to user and manager"""
subject = "Account Inactivity Warning — PHI Access May Be Disabled"
body = f"""
Dear {user_name},

Our records show that you have not logged into the BIO-QMS system for {days_inactive} days.

Per HIPAA access control policy, accounts with PHI access that remain inactive for 180 days will be automatically disabled.

If you still require PHI access, please log in within the next {180 - days_inactive} days to maintain your access.

If you no longer require PHI access, please contact your manager to have your access revoked.

Thank you,
BIO-QMS Compliance Team
"""

# Send email (implementation details omitted)
send_email(to=[user_email, manager_email], subject=subject, body=body)

def send_account_disabled_notification(user_email, user_name, days_inactive, manager_email):
"""Send account disabled notification"""
subject = "Account Disabled — Stale Access Detected"
body = f"""
Dear {user_name},

Your BIO-QMS account has been automatically disabled due to inactivity ({days_inactive} days without login).

To re-enable your account, please contact your manager to submit an access request.

You will be required to complete HIPAA training before your access is restored.

Thank you,
BIO-QMS Compliance Team
"""

# Send email + copy Privacy Officer
send_email(to=[user_email, manager_email, 'privacy_officer@coditect.ai'], subject=subject, body=body)

if __name__ == '__main__':
detect_stale_access()

7.4 Role Change Workflow

Trigger: User's job function changes (department transfer, promotion, new responsibilities)

Process:

User or Manager Submits Role Change Request

Request Form Includes:
- Current Role: [e.g., CLINICAL]
- Requested New Role: [e.g., QA]
- Effective Date: [YYYY-MM-DD]
- Justification: [Free text explanation]
- Manager Approval: [Signature required]

Privacy Officer Reviews Request:
- Does new role align with job function?
- Is minimum necessary principle maintained?
- Any conflicts of interest? (e.g., CLINICAL→QA on own patients)

If Approved:
→ System updates user role in database
→ PHI access automatically adjusted per role matrix (Section 4.2)
→ User receives email notification with new permissions summary
→ Audit log entry: ROLE_CHANGED (old_role, new_role, effective_date)

If Denied:
→ Privacy Officer documents denial reason
→ Requestor notified with explanation
→ User retains current role

Automatic PHI Access Adjustment Examples:

Role ChangePHI Access BeforePHI Access AfterAutomatic Actions
CLINICAL → BILLINGAll PHI fields for assigned patientsName, DOB, MRN, insurance numbers (all patients in billing department)Revoke clinical notes access, grant insurance number access
CLINICAL → QAAll PHI fields for assigned patientsName, MRN, service dates (all patients system-wide)Revoke patient contact info, grant read-only compliance review access
BILLING → ADMINLimited PHI for billingNo PHI access (all masked)Revoke all PHI fields, confirm no data retention locally
QA → AUDITORLimited PHI for compliance reviewAll PHI fields (read-only, all patients)Grant full read-only access, disable write permissions

7.5 Termination Workflow

Objective: Immediate PHI access revocation upon employee termination (within 1 hour).

Termination Types:

Termination TypeAccess Revocation TimeframeProcess
Voluntary Resignation (notice given)Last day of employment (scheduled)Pre-schedule access revocation for last day 5pm local time
Involuntary Termination (immediate)Within 1 hour of HR notificationImmediate access revocation + audit review
Contractor End of ContractContract end date (scheduled)Pre-schedule access revocation, 7-day reminder to manager
Leave of AbsenceStart of leavePHI access suspended, not revoked (reactivate on return)

Immediate Termination Workflow:

HR Submits Termination Notification (via HR system)

Automated Webhook Triggers Access Revocation Script

Within 1 Hour (Target: 15 minutes):
1. Disable user account (status = 'TERMINATED')
2. Revoke all PHI access (phi_access = false)
3. Terminate active sessions (logout all devices)
4. Revoke API keys and refresh tokens
5. Disable MFA enrollment (prevent re-enrollment)
6. Revoke e-signature signing key (archive to HSM)

Immediate Notifications:
• IT Team: Revoke email access, VPN, physical badges
• Manager: Confirm termination, retrieve company devices
• Privacy Officer: Audit PHI access in final 30 days

Post-Termination Audit (within 7 days):
• Review all PHI accessed by user in final 90 days
• Check for data exfiltration (large downloads, USB usage)
• Verify no unauthorized data retention
• Document findings in termination audit report

Automated Termination Script (Python):

#!/usr/bin/env python3
"""
Employee termination access revocation script.
Triggered via webhook from HR system.
Location: scripts/compliance/terminate-user-access.py
"""

import requests
from datetime import datetime

def revoke_user_access_on_termination(user_id: str, termination_type: str):
"""Immediate PHI access revocation for terminated user"""

start_time = datetime.utcnow()

# Step 1: Disable user account
update_user_status(user_id, status='TERMINATED', termination_type=termination_type)

# Step 2: Revoke PHI access
update_phi_access(user_id, phi_access=False, reason='TERMINATION')

# Step 3: Terminate all active sessions
terminate_active_sessions(user_id)

# Step 4: Revoke API keys and refresh tokens
revoke_api_credentials(user_id)

# Step 5: Disable MFA
disable_mfa_enrollment(user_id)

# Step 6: Archive e-signature key (do NOT delete, needed for historical verification)
archive_signing_key(user_id)

# Step 7: Audit log
audit_log('USER_TERMINATED', user_id, f'Type: {termination_type}, Access revoked in {(datetime.utcnow() - start_time).seconds}s')

# Step 8: Send notifications
send_termination_notifications(user_id, termination_type)

# Step 9: Schedule post-termination audit (7 days)
schedule_post_termination_audit(user_id, days=7)

elapsed_seconds = (datetime.utcnow() - start_time).seconds
if elapsed_seconds > 3600: # 1 hour SLA
# Alert: Revocation took too long
send_alert('TERMINATION_REVOCATION_SLA_BREACH', f'User {user_id} revocation took {elapsed_seconds}s (target: <1 hour)')

return {
'status': 'SUCCESS',
'user_id': user_id,
'elapsed_seconds': elapsed_seconds,
'timestamp': datetime.utcnow().isoformat()
}

# Example webhook handler (Flask)
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhooks/hr/termination', methods=['POST'])
def handle_termination_webhook():
"""Webhook from HR system when employee terminated"""

payload = request.json
user_id = payload.get('user_id')
termination_type = payload.get('termination_type') # VOLUNTARY | INVOLUNTARY | CONTRACT_END

# Validate webhook signature (HMAC)
if not validate_webhook_signature(request.headers.get('X-Signature'), request.data):
return jsonify({'error': 'Invalid signature'}), 401

# Execute revocation
result = revoke_user_access_on_termination(user_id, termination_type)

return jsonify(result), 200

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

8. Technical Implementation Architecture

8.1 PHI Access Decision Engine (ABAC)

Attribute-Based Access Control (ABAC) Model:

# Example: PHI access decision engine (Python pseudocode)
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime

@dataclass
class AccessRequest:
user_id: str
user_role: str
patient_id: str
phi_fields: List[str]
purpose: str # TREATMENT | PAYMENT | OPERATIONS | RESEARCH | EMERGENCY
context: Dict # Additional context (device, location, time, etc.)

@dataclass
class AccessDecision:
allowed: bool
allowed_fields: List[str]
masked_fields: Dict[str, str] # field → mask function
reason: str
audit_level: int # L1-L4

class PHIAccessDecisionEngine:
"""ABAC engine for PHI access control"""

def authorize(self, request: AccessRequest) -> AccessDecision:
"""Make access control decision based on attributes"""

# Step 1: Check if user has MFA verified
if not self.is_mfa_verified(request.user_id):
return AccessDecision(
allowed=False,
allowed_fields=[],
masked_fields={},
reason='MFA_REQUIRED',
audit_level=3
)

# Step 2: Check role-based permissions
role_permissions = self.get_role_permissions(request.user_role)
if not role_permissions.can_access_phi:
return AccessDecision(
allowed=False,
allowed_fields=[],
masked_fields={},
reason='ROLE_NO_PHI_ACCESS',
audit_level=3
)

# Step 3: Check patient assignment (for CLINICAL role)
if request.user_role == 'CLINICAL':
if not self.is_user_assigned_to_patient(request.user_id, request.patient_id):
# Not assigned → check if break-glass or shared care
if request.purpose == 'EMERGENCY':
return self.handle_break_glass(request)
else:
return AccessDecision(
allowed=False,
allowed_fields=[],
masked_fields={},
reason='PATIENT_NOT_ASSIGNED',
audit_level=3
)

# Step 4: Apply minimum necessary filtering
allowed_fields = self.apply_minimum_necessary(
role=request.user_role,
purpose=request.purpose,
requested_fields=request.phi_fields
)

# Step 5: Apply field-level masking
masked_fields = self.apply_field_masking(
role=request.user_role,
allowed_fields=allowed_fields
)

# Step 6: Check purpose-based consent
if request.purpose in ['RESEARCH', 'MARKETING', 'DISCLOSURE']:
if not self.check_patient_consent(request.patient_id, request.purpose):
return AccessDecision(
allowed=False,
allowed_fields=[],
masked_fields={},
reason='PATIENT_CONSENT_REQUIRED',
audit_level=3
)

# Step 7: Context-based risk assessment (adaptive MFA)
risk_score = self.assess_risk(request.context)
if risk_score > 50:
# Require step-up authentication
return AccessDecision(
allowed=False,
allowed_fields=[],
masked_fields={},
reason='STEP_UP_MFA_REQUIRED',
audit_level=3
)

# Access granted
return AccessDecision(
allowed=True,
allowed_fields=allowed_fields,
masked_fields=masked_fields,
reason='AUTHORIZED',
audit_level=2 if request.purpose == 'EMERGENCY' else 1
)

def apply_minimum_necessary(self, role: str, purpose: str, requested_fields: List[str]) -> List[str]:
"""Filter PHI fields based on minimum necessary principle"""

# Define minimum necessary fields per role + purpose
MIN_NECESSARY_MATRIX = {
('CLINICAL', 'TREATMENT'): ['full_name', 'date_of_birth', 'mrn', 'phone_number', 'clinical_notes', 'medications', 'allergies', 'lab_results'],
('BILLING', 'PAYMENT'): ['full_name', 'date_of_birth', 'mrn', 'ssn_last4', 'insurance_policy_number'],
('QA', 'OPERATIONS'): ['full_name', 'mrn', 'service_date', 'work_order_description'],
('AUDITOR', 'OPERATIONS'): requested_fields, # Auditors get full access (read-only)
# ... more combinations
}

allowed = MIN_NECESSARY_MATRIX.get((role, purpose), [])

# Return intersection of requested and allowed
return [f for f in requested_fields if f in allowed]

def apply_field_masking(self, role: str, allowed_fields: List[str]) -> Dict[str, str]:
"""Apply field-level masking per role"""

MASKING_RULES = {
'CLINICAL': {
'ssn': lambda v: f'***-**-{v[-4:]}', # Last 4 only
},
'BILLING': {
'ssn': lambda v: f'***-**-{v[-4:]}',
'phone_number': lambda v: '***PHI***',
'email': lambda v: '***PHI***',
},
'QA': {
'date_of_birth': lambda v: v[:4] + '-XX-XX', # Year only
'phone_number': lambda v: '***PHI***',
'email': lambda v: '***PHI***',
'ssn': lambda v: '***-**-****',
},
'ADMIN': {
'*': lambda v: '***PHI***', # Mask all PHI
},
}

role_masks = MASKING_RULES.get(role, {})
masked = {}

for field in allowed_fields:
if field in role_masks:
masked[field] = role_masks[field]
elif '*' in role_masks:
masked[field] = role_masks['*']

return masked

8.2 Database Row-Level Security (RLS)

PostgreSQL RLS Policies:

-- Enable RLS on patients table
ALTER TABLE patients ENABLE ROW LEVEL SECURITY;

-- Policy 1: Tenant isolation (all tables)
CREATE POLICY tenant_isolation ON patients
USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- Policy 2: CLINICAL role sees only assigned patients
CREATE POLICY clinical_assigned_patients ON patients
FOR SELECT
TO clinical_role
USING (
patient_id IN (
SELECT patient_id FROM work_orders
WHERE assignee_id = current_setting('app.user_id')::uuid
)
OR patient_id IN (
SELECT patient_id FROM patient_care_team
WHERE provider_id = current_setting('app.user_id')::uuid
)
);

-- Policy 3: BILLING role sees all patients in tenant (billing operations)
CREATE POLICY billing_all_patients ON patients
FOR SELECT
TO billing_role
USING (TRUE); -- No row restriction, but field masking applied at application layer

-- Policy 4: QA role sees all patients in tenant (quality review)
CREATE POLICY qa_all_patients ON patients
FOR SELECT
TO qa_role
USING (TRUE);

-- Policy 5: AUDITOR role sees all patients in tenant (read-only audit)
CREATE POLICY auditor_all_patients ON patients
FOR SELECT
TO auditor_role
USING (TRUE);

-- Policy 6: ADMIN role has NO PHI access (field masking enforced at application layer)
-- No policy created → ADMIN role cannot SELECT from patients table

-- Example: Set session context before queries
SET app.tenant_id = 'tenant-uuid-123';
SET app.user_id = 'user-uuid-456';
SET ROLE clinical_role;

-- Now all SELECT queries automatically filtered by RLS policies
SELECT * FROM patients; -- Returns only patients assigned to user-uuid-456

Application Middleware to Set RLS Context:

// Example: Express middleware to set PostgreSQL session context
import { Request, Response, NextFunction } from 'express';
import { Pool } from 'pg';

const pgPool = new Pool({
host: 'db.coditect-bio-qms.internal',
database: 'bio_qms',
user: 'app_user',
password: process.env.DB_PASSWORD,
max: 20,
});

export async function setRLSContext(req: Request, res: Response, next: NextFunction) {
const user = req.user; // From JWT authentication middleware

// Get database connection from pool
const client = await pgPool.connect();

try {
// Set session context for RLS
await client.query('SET app.tenant_id = $1', [user.tenant_id]);
await client.query('SET app.user_id = $1', [user.user_id]);
await client.query(`SET ROLE ${user.role.toLowerCase()}_role`); // e.g., clinical_role

// Attach client to request for use in route handlers
req.dbClient = client;

next();
} catch (error) {
client.release();
next(error);
}
}

// Cleanup middleware (release connection back to pool)
export async function cleanupRLSContext(req: Request, res: Response, next: NextFunction) {
if (req.dbClient) {
req.dbClient.release();
}
next();
}

// Example usage in Express app
import express from 'express';

const app = express();

// Global middleware
app.use(authenticateJWT); // Verify JWT, populate req.user
app.use(setRLSContext); // Set RLS context in database

// Route handlers (RLS automatically enforced)
app.get('/api/patients', async (req, res) => {
const result = await req.dbClient.query('SELECT * FROM patients');
// RLS automatically filters results based on user role and context
res.json(result.rows);
});

// Cleanup middleware (runs after response sent)
app.use(cleanupRLSContext);

8.3 PHI Caching Policy

Policy: PHI MUST NEVER be cached in browser, CDN, or application cache.

HTTP Headers (All PHI API Responses):

Cache-Control: no-store, no-cache, must-revalidate, private
Pragma: no-cache
Expires: 0

CDN Bypass for PHI Endpoints:

# Nginx configuration for PHI API endpoints
location /api/patients {
# Bypass CDN for all PHI endpoints
proxy_cache off;
proxy_no_cache 1;
proxy_cache_bypass 1;

# Set headers to prevent caching
add_header Cache-Control "no-store, no-cache, must-revalidate, private" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;

# Proxy to backend
proxy_pass http://backend-api;
}

Client-Side Cache Prevention (React):

// Example: React hook to fetch PHI data (no caching)
import { useEffect, useState } from 'react';

function usePHIData<T>(endpoint: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);

useEffect(() => {
const fetchPHI = async () => {
try {
const response = await fetch(endpoint, {
method: 'GET',
headers: {
'Authorization': `Bearer ${getAccessToken()}`,
'Cache-Control': 'no-cache', // Client-side cache bypass
},
cache: 'no-store', // Fetch API cache bypass
});

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

const json = await response.json();
setData(json);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};

fetchPHI();
}, [endpoint]);

// IMPORTANT: Clear data when component unmounts (prevent memory retention)
useEffect(() => {
return () => {
setData(null); // Clear PHI from component state
};
}, []);

return { data, loading, error };
}

8.4 PHI Scrubbing from Application Logs

Automated PHI Detection and Redaction:

# Example: PHI scrubbing middleware for application logs
import re
import logging

class PHIScrubber:
"""Detect and redact PHI from log messages"""

# Regex patterns for PHI detection
PATTERNS = {
'SSN': re.compile(r'\b\d{3}-\d{2}-\d{4}\b'),
'PHONE': re.compile(r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b'),
'EMAIL': re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'),
'MRN': re.compile(r'\bMRN[:=\s]+([A-Z0-9]{6,12})\b', re.IGNORECASE),
'ZIP_CODE': re.compile(r'\b\d{5}(-\d{4})?\b'),
'DATE_OF_BIRTH': re.compile(r'\b(0[1-9]|1[0-2])/(0[1-9]|[12]\d|3[01])/\d{4}\b'),
}

def scrub(self, message: str) -> str:
"""Redact PHI from log message"""
scrubbed = message

for phi_type, pattern in self.PATTERNS.items():
scrubbed = pattern.sub(f'***{phi_type}_REDACTED***', scrubbed)

# ML-based PHI detection (for unstructured text)
# if self.ml_detector.detect_phi(scrubbed):
# scrubbed = '***PHI_DETECTED_AND_REDACTED***'

return scrubbed

# Custom logging handler with PHI scrubbing
class PHIScrubbingHandler(logging.Handler):
def __init__(self, underlying_handler: logging.Handler):
super().__init__()
self.scrubber = PHIScrubber()
self.underlying = underlying_handler

def emit(self, record: logging.LogRecord):
# Scrub PHI from message
record.msg = self.scrubber.scrub(str(record.msg))

# Scrub PHI from exception info
if record.exc_info:
record.exc_text = self.scrubber.scrub(str(record.exc_text))

# Forward to underlying handler (CloudWatch, file, etc.)
self.underlying.emit(record)

# Configure logger with PHI scrubbing
logger = logging.getLogger('bio_qms')
cloudwatch_handler = CloudWatchLogsHandler(log_group='bio-qms-app-logs')
phi_scrubbing_handler = PHIScrubbingHandler(cloudwatch_handler)
logger.addHandler(phi_scrubbing_handler)

# Example: PHI will be redacted automatically
logger.info(f"Patient John Doe (SSN: 123-45-6789) accessed")
# Logged as: "Patient John Doe (SSN: ***SSN_REDACTED***) accessed"

9. HIPAA §164.312 Compliance Matrix

HIPAA SectionRequirementImplementation StandardBIO-QMS ControlEvidenceStatus
§164.312(a)(1)Access ControlRequiredRBAC (8 roles) + Field-level masking + Session managementdocs/compliance/21-rbac-model.md
docs/compliance/22-rbac-permissions-matrix.md
COMPLIANT
§164.312(a)(2)(i)Unique User IdentificationRequiredECDSA P-256 signing keys per user + SSO (SAML/OIDC)services/auth/user_signing_keys.py
database/tables/user_signing_keys.sql
COMPLIANT
§164.312(a)(2)(ii)Emergency Access ProcedureRequiredBreak-glass workflow (4-hour expiration, MFA required, 24-hour review)docs/compliance/hipaa-access-controls.md §6
services/auth/break_glass.py
COMPLIANT
§164.312(a)(2)(iii)Automatic LogoffAddressable (IMPLEMENTED)15-minute idle timeout, 8-hour absolute timeout (configurable per role)services/auth/session_management.py
frontend/src/SessionIdleManager.ts
COMPLIANT
§164.312(a)(2)(iv)Encryption and DecryptionAddressable (IMPLEMENTED)AES-256-GCM for PHI at rest (KMS), TLS 1.3 in transit (mTLS)docs/compliance/crypto-standards-policy.md
infrastructure/kms/key-policies/
COMPLIANT
§164.312(b)Audit ControlsRequiredTamper-evident audit log (HMAC-SHA256 integrity chain, immutable S3 storage)services/audit/integrity_chain.py
docs/operations/64-security-architecture.md §2
COMPLIANT
§164.312(c)(1)IntegrityRequiredSHA-256 document hashing (FDA Part 11 §11.70 binding)services/approval/e_signature.py
docs/compliance/crypto-standards-policy.md §8.1
COMPLIANT
§164.312(c)(2)Mechanism to Authenticate ePHIAddressable (IMPLEMENTED)Digital signatures (ECDSA P-256), audit log integrity chainservices/audit/integrity_verification.pyCOMPLIANT
§164.312(d)Person or Entity AuthenticationRequiredMFA (TOTP/FIDO2/Push) required for all PHI access, mTLS for service-to-servicedocs/compliance/hipaa-access-controls.md §3
services/auth/mfa_verification.py
COMPLIANT
§164.312(e)(1)Transmission SecurityRequiredTLS 1.3 mandatory (no fallback), mTLS for internal services, VPN for remote accessinfrastructure/nginx/tls_config.conf
docs/compliance/crypto-standards-policy.md §5
COMPLIANT
§164.312(e)(2)(i)Integrity ControlsAddressable (IMPLEMENTED)SHA-256 checksums for file transfers, message signing (HMAC-SHA256)services/storage/integrity_check.pyCOMPLIANT
§164.312(e)(2)(ii)EncryptionAddressable (IMPLEMENTED)TLS 1.3 for all HTTPS/gRPC, mTLS for service-to-service (zero-trust architecture)infrastructure/service-mesh/mtls_config.yamlCOMPLIANT

Compliance Summary:

  • Total Requirements: 12 (4 Required, 8 Addressable — all implemented)
  • Compliant: 12/12 (100%)
  • Partial: 0
  • Non-Compliant: 0

Compliance Evidence Artifacts:

Evidence TypeLocationPurpose
Policy Documentsdocs/compliance/hipaa-access-controls.md
docs/compliance/crypto-standards-policy.md
Documented controls and procedures
Technical Implementationservices/auth/, services/audit/, infrastructure/Source code implementing controls
Test Resultstests/compliance/hipaa_access_control_tests.pyAutomated compliance tests (100% pass rate)
Audit LogsS3 bucket: s3://coditect-bio-qms-audit-logs/10-year retention, immutable storage
Penetration Test Reportdocs/security/penetration-test-2026-Q1.pdfThird-party security assessment
User Training Recordscompliance-system/training-records/HIPAA training completion certificates

10. Appendices

Appendix A: Glossary

TermDefinition
Access ControlMechanisms that limit access to PHI based on user identity, role, and authorization
Break-Glass AccessEmergency access procedure allowing temporary elevated PHI access during life-threatening situations
Field-Level MaskingRedaction or obfuscation of specific PHI fields based on user role (e.g., showing last 4 digits of SSN)
MFA (Multi-Factor Authentication)Authentication requiring 2+ factors: something you know (password) + something you have (TOTP device) + something you are (biometric)
Minimum NecessaryHIPAA principle requiring covered entities to limit PHI access/disclosure to the minimum needed for the intended purpose
PHI (Protected Health Information)Individually identifiable health information held or transmitted by covered entities (18 HIPAA identifiers)
Row-Level Security (RLS)Database mechanism filtering query results based on session context (tenant, user, role)
Session TimeoutAutomatic logout after period of inactivity (idle timeout) or absolute time limit
TOTP (Time-Based One-Time Password)MFA method generating 6-digit codes valid for 30 seconds (RFC 6238)

Appendix B: Regulatory References

  1. HIPAA Security Rule — 45 CFR §164.312 URL: https://www.ecfr.gov/current/title-45/subtitle-A/subchapter-C/part-164/subpart-C/section-164.312

  2. HIPAA Privacy Rule — 45 CFR §164.514 (Minimum Necessary) URL: https://www.ecfr.gov/current/title-45/subtitle-A/subchapter-C/part-164/subpart-E/section-164.514

  3. NIST SP 800-66 Rev 2 — HIPAA Security Rule Implementation Guide URL: https://csrc.nist.gov/publications/detail/sp/800-66/rev-2/final

  4. HHS Guidance on Minimum Necessary URL: https://www.hhs.gov/hipaa/for-professionals/privacy/guidance/minimum-necessary-requirement/index.html

  5. HHS Guidance on Access Controls URL: https://www.hhs.gov/hipaa/for-professionals/security/guidance/index.html

DocumentLocationPurpose
Security Architecturedocs/operations/64-security-architecture.mdSTRIDE threat model, authentication/authorization architecture
RBAC Modeldocs/compliance/21-rbac-model.mdRole definitions, permission matrix, SOD rules
RBAC Permissions Matrixdocs/compliance/22-rbac-permissions-matrix.mdDetailed permission mappings per role
Cryptographic Standardsdocs/compliance/crypto-standards-policy.mdAlgorithm selection, key management, TLS configuration
Audit Log Specificationdocs/compliance/audit-log-specification.mdAudit trail requirements, integrity verification
Incident Response Plandocs/security/incident-response-plan.mdSecurity incident procedures, breach notification
Employee Termination SOPdocs/procedures/employee-termination-sop.mdAccess revocation procedures

Appendix D: Change Log

VersionDateAuthorChangesApproval
0.1.02026-02-10Privacy OfficerInitial draftN/A (draft)
0.2.02026-02-12Security TeamAdded MFA section, break-glass workflowN/A (draft)
0.3.02026-02-14CISOSession management, access review proceduresN/A (draft)
1.0.02026-02-16Privacy Officer + CISOFinal review, compliance matrix completePending executive approval

Document ID: CODITECT-BIO-HIPAA-AC-001 Version: 1.0.0 Classification: Internal - Restricted Next Review Date: 2027-02-16 Policy Owner: Chief Information Security Officer (CISO) & Privacy Officer Document Location: docs/compliance/hipaa-access-controls.md Approval Status: Draft (pending executive signature)

Confidentiality Notice: This document contains proprietary information and is intended solely for authorized personnel of CODITECT Biosciences. Unauthorized distribution is prohibited.


END OF DOCUMENT