healthcare-phi-compliance
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHealthcare PHI/PII Compliance Patterns
医疗领域PHI/PII合规模式
Patterns for protecting patient data, clinician data, and financial data in healthcare applications. Applicable to HIPAA (US), DISHA (India), GDPR (EU), and general healthcare data protection.
用于在医疗应用中保护患者数据、临床医生数据和财务数据的模式,适用于HIPAA(美国)、DISHA(印度)、GDPR(欧盟)以及通用医疗数据保护场景。
When to Use
何时使用
- Building any feature that touches patient records
- Implementing access control or authentication for clinical systems
- Designing database schemas for healthcare data
- Building APIs that return patient or clinician data
- Implementing audit trails or logging
- Reviewing code for data exposure vulnerabilities
- Setting up Row-Level Security (RLS) for multi-tenant healthcare systems
- 构建任何涉及患者记录的功能
- 为临床系统实现访问控制或身份认证
- 设计医疗数据的数据库 schema
- 构建返回患者或临床医生数据的API
- 实现审计追踪或日志功能
- 审查代码中的数据暴露漏洞
- 为多租户医疗系统设置Row-Level Security(RLS)
How It Works
实现原理
Healthcare data protection operates on three layers: classification (what is sensitive), access control (who can see it), and audit (who did see it).
医疗数据保护基于三层体系运行:分类(哪些是敏感数据)、访问控制(谁有权查看数据)、审计(谁实际访问过数据)。
Data Classification
数据分类
PHI (Protected Health Information) — any data that can identify a patient AND relates to their health: patient name, date of birth, address, phone, email, national ID numbers (SSN, Aadhaar, NHS number), medical record numbers, diagnoses, medications, lab results, imaging, insurance policy and claim details, appointment and admission records, or any combination of the above.
PII (Non-patient-sensitive data) in healthcare systems: clinician/staff personal details, doctor fee structures and payout amounts, employee salary and bank details, vendor payment information.
PHI(受保护健康信息) —— 任何可识别患者身份且与其健康相关的数据:患者姓名、出生日期、地址、电话、邮箱、国家身份编号(SSN、Aadhaar、NHS编号)、医疗记录编号、诊断结果、用药信息、实验室检查结果、影像资料、保险政策和理赔详情、预约和住院记录,或上述信息的任意组合。
PII(非患者敏感数据) 指医疗系统中的如下数据:临床医生/员工个人信息、医生收费结构和薪酬发放金额、员工薪资和银行信息、供应商付款信息。
Access Control: Row-Level Security
访问控制:行级安全
sql
ALTER TABLE patients ENABLE ROW LEVEL SECURITY;
-- Scope access by facility
CREATE POLICY "staff_read_own_facility"
ON patients FOR SELECT TO authenticated
USING (facility_id IN (
SELECT facility_id FROM staff_assignments
WHERE user_id = auth.uid() AND role IN ('doctor','nurse','lab_tech','admin')
));
-- Audit log: insert-only (tamper-proof)
CREATE POLICY "audit_insert_only" ON audit_log FOR INSERT
TO authenticated WITH CHECK (user_id = auth.uid());
CREATE POLICY "audit_no_modify" ON audit_log FOR UPDATE USING (false);
CREATE POLICY "audit_no_delete" ON audit_log FOR DELETE USING (false);sql
ALTER TABLE patients ENABLE ROW LEVEL SECURITY;
-- Scope access by facility
CREATE POLICY "staff_read_own_facility"
ON patients FOR SELECT TO authenticated
USING (facility_id IN (
SELECT facility_id FROM staff_assignments
WHERE user_id = auth.uid() AND role IN ('doctor','nurse','lab_tech','admin')
));
-- Audit log: insert-only (tamper-proof)
CREATE POLICY "audit_insert_only" ON audit_log FOR INSERT
TO authenticated WITH CHECK (user_id = auth.uid());
CREATE POLICY "audit_no_modify" ON audit_log FOR UPDATE USING (false);
CREATE POLICY "audit_no_delete" ON audit_log FOR DELETE USING (false);Audit Trail
审计追踪
Every PHI access or modification must be logged:
typescript
interface AuditEntry {
timestamp: string;
user_id: string;
patient_id: string;
action: 'create' | 'read' | 'update' | 'delete' | 'print' | 'export';
resource_type: string;
resource_id: string;
changes?: { before: object; after: object };
ip_address: string;
session_id: string;
}所有PHI的访问或修改操作都必须被记录:
typescript
interface AuditEntry {
timestamp: string;
user_id: string;
patient_id: string;
action: 'create' | 'read' | 'update' | 'delete' | 'print' | 'export';
resource_type: string;
resource_id: string;
changes?: { before: object; after: object };
ip_address: string;
session_id: string;
}Common Leak Vectors
常见泄露路径
Error messages: Never include patient-identifying data in error messages thrown to the client. Log details server-side only.
Console output: Never log full patient objects. Use opaque internal record IDs (UUIDs) — not medical record numbers, national IDs, or names.
URL parameters: Never put patient-identifying data in query strings or path segments that could appear in logs or browser history. Use opaque UUIDs only.
Browser storage: Never store PHI in localStorage or sessionStorage. Keep PHI in memory only, fetch on demand.
Service role keys: Never use the service_role key in client-side code. Always use the anon/publishable key and let RLS enforce access.
Logs and monitoring: Never log full patient records. Use opaque record IDs only (not medical record numbers). Sanitize stack traces before sending to error tracking services.
错误消息: 绝对不要在返回给客户端的错误消息中包含可识别患者身份的数据,仅在服务端记录详细日志。
控制台输出: 绝对不要打印完整的患者对象,使用不透明的内部记录ID(UUID)—— 不要使用医疗记录编号、国家身份编号或姓名。
URL参数: 绝对不要在查询字符串或路径片段中放置可识别患者身份的数据,这类内容可能出现在日志或浏览器历史中,仅使用不透明的UUID。
浏览器存储: 绝对不要在localStorage或sessionStorage中存储PHI,仅将PHI保存在内存中,按需请求获取。
服务角色密钥: 绝对不要在客户端代码中使用service_role密钥,始终使用anon/可发布密钥,通过RLS执行访问控制。
日志与监控: 绝对不要记录完整的患者记录,仅使用不透明的记录ID(不要使用医疗记录编号),在将内容发送到错误追踪服务前清理栈追踪信息。
Database Schema Tagging
数据库Schema标记
Mark PHI/PII columns at the schema level:
sql
COMMENT ON COLUMN patients.name IS 'PHI: patient_name';
COMMENT ON COLUMN patients.dob IS 'PHI: date_of_birth';
COMMENT ON COLUMN patients.aadhaar IS 'PHI: national_id';
COMMENT ON COLUMN doctor_payouts.amount IS 'PII: financial';在Schema层面标记PHI/PII字段:
sql
COMMENT ON COLUMN patients.name IS 'PHI: patient_name';
COMMENT ON COLUMN patients.dob IS 'PHI: date_of_birth';
COMMENT ON COLUMN patients.aadhaar IS 'PHI: national_id';
COMMENT ON COLUMN doctor_payouts.amount IS 'PII: financial';Deployment Checklist
部署检查清单
Before every deployment:
- No PHI in error messages or stack traces
- No PHI in console.log/console.error
- No PHI in URL parameters
- No PHI in browser storage
- No service_role key in client code
- RLS enabled on all PHI/PII tables
- Audit trail for all data modifications
- Session timeout configured
- API authentication on all PHI endpoints
- Cross-facility data isolation verified
每次部署前确认:
- 错误消息或栈追踪中不含PHI
- console.log/console.error中不含PHI
- URL参数中不含PHI
- 浏览器存储中不含PHI
- 客户端代码中没有service_role密钥
- 所有PHI/PII表都已启用RLS
- 所有数据修改都有审计追踪
- 已配置会话超时
- 所有PHI接口都已开启API身份认证
- 已验证跨机构数据隔离能力
Examples
示例
Example 1: Safe vs Unsafe Error Handling
示例1:安全与不安全的错误处理对比
typescript
// BAD — leaks PHI in error
throw new Error(`Patient ${patient.name} not found in ${patient.facility}`);
// GOOD — generic error, details logged server-side with opaque IDs only
logger.error('Patient lookup failed', { recordId: patient.id, facilityId });
throw new Error('Record not found');typescript
// BAD — leaks PHI in error
throw new Error(`Patient ${patient.name} not found in ${patient.facility}`);
// GOOD — generic error, details logged server-side with opaque IDs only
logger.error('Patient lookup failed', { recordId: patient.id, facilityId });
throw new Error('Record not found');Example 2: RLS Policy for Multi-Facility Isolation
示例2:多机构隔离的RLS策略
sql
-- Doctor at Facility A cannot see Facility B patients
CREATE POLICY "facility_isolation"
ON patients FOR SELECT TO authenticated
USING (facility_id IN (
SELECT facility_id FROM staff_assignments WHERE user_id = auth.uid()
));
-- Test: login as doctor-facility-a, query facility-b patients
-- Expected: 0 rows returnedsql
-- Doctor at Facility A cannot see Facility B patients
CREATE POLICY "facility_isolation"
ON patients FOR SELECT TO authenticated
USING (facility_id IN (
SELECT facility_id FROM staff_assignments WHERE user_id = auth.uid()
));
-- Test: login as doctor-facility-a, query facility-b patients
-- Expected: 0 rows returnedExample 3: Safe Logging
示例3:安全日志打印
typescript
// BAD — logs identifiable patient data
console.log('Processing patient:', patient);
// GOOD — logs only opaque internal record ID
console.log('Processing record:', patient.id);
// Note: even patient.id should be an opaque UUID, not a medical record numbertypescript
// BAD — logs identifiable patient data
console.log('Processing patient:', patient);
// GOOD — logs only opaque internal record ID
console.log('Processing record:', patient.id);
// Note: even patient.id should be an opaque UUID, not a medical record number