医療ヘルスケアPHI/PIIコンプライアンスパターン
医療アプリケーションにおける患者データ、臨床医データ、財務データを保護するためのパターン。HIPAA(米国)、DISHA(インド)、GDPR(EU)、および一般的な医療データ保護に適用可能。
使用するタイミング
- 患者記録に触れるあらゆる機能の構築
- 臨床システム向けのアクセス制御または認証の実装
- 医療データのデータベーススキーマ設計
- 患者データまたは臨床医データを返すAPIの構築
- 監査証跡またはログの実装
- データ露出脆弱性のコードレビュー
- マルチテナント医療システム向けの行レベルセキュリティ(RLS)の設定
仕組み
医療データ保護は3つの層で機能します:分類(何が機密か)、アクセス制御(誰が閲覧できるか)、監査(誰が閲覧したか)。
データ分類
PHI(要保護医療情報) — 患者を識別でき、かつ健康に関連するすべてのデータ:患者名、生年月日、住所、電話番号、メールアドレス、国民ID番号(SSN、Aadhaar、NHS番号)、カルテ番号、診断名、投薬情報、検査結果、画像、保険証券および請求詳細、予約および入院記録、または上記の組み合わせ。
PII(医療システムにおける非患者機密データ):臨床医/スタッフの個人情報、医師の料金体系および支払い額、従業員の給与および銀行口座詳細、ベンダー支払い情報。
アクセス制御:行レベルセキュリティ
ALTER TABLE patients ENABLE ROW LEVEL SECURITY;
-- 施設ごとにアクセス範囲を制限
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')
));
-- 監査ログ:挿入のみ(改ざん防止)
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);
監査証跡
すべてのPHIアクセスまたは変更は記録する必要があります:
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;
}
一般的な漏洩ベクトル
エラーメッセージ: クライアントにスローされるエラーメッセージに患者を特定できるデータを含めない。詳細はサーバー側でのみログに記録する。
コンソール出力: 完全な患者オブジェクトをログに記録しない。不透明な内部レコードID(UUID)を使用する。カルテ番号、国民ID、名前は使用しない。
URLパラメータ: ログやブラウザ履歴に表示される可能性のあるクエリ文字列やパスセグメントに患者を特定できるデータを含めない。不透明なUUIDのみを使用する。
ブラウザストレージ: PHIをlocalStorageやsessionStorageに保存しない。PHIはメモリ内にのみ保持し、オンデマンドで取得する。
サービスロールキー: クライアントサイドコードでサービスロールキーを使用しない。常にanon/publishableキーを使用し、RLSでアクセスを強制する。
ログと監視: 完全な患者レコードをログに記録しない。不透明なレコードIDのみを使用する(カルテ番号は使用しない)。エラー追跡サービスに送信する前にスタックトレースをサニタイズする。
データベーススキーマのタグ付け
スキーマレベルでPHI/PIIカラムをマークする:
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';
デプロイ前チェックリスト
各デプロイ前に行うこと:
- エラーメッセージやスタックトレースにPHIがないこと
- console.log/console.errorにPHIがないこと
- URLパラメータにPHIがないこと
- ブラウザストレージにPHIがないこと
- クライアントコードにサービスロールキーがないこと
- すべてのPHI/PIIテーブルでRLSが有効であること
- すべてのデータ変更に監査証跡があること
- セッションタイムアウトが設定されていること
- すべてのPHIエンドポイントでAPI認証が設定されていること
- クロス施設のデータ分離が検証されていること
使用例
例1:安全なエラーハンドリングと安全でないエラーハンドリング
// 悪い例 — PHIがエラーに漏洩
throw new Error(`Patient ${patient.name} not found in ${patient.facility}`);
// 良い例 — 汎用的なエラー、詳細は不透明なIDでのみサーバー側でログ記録
logger.error('Patient lookup failed', { recordId: patient.id, facilityId });
throw new Error('Record not found');
例2:マルチ施設分離のためのRLSポリシー
-- 施設Aの医師は施設Bの患者を閲覧できない
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()
));
-- テスト:医師-facility-a としてログインし、facility-b の患者をクエリ
-- 期待結果:0行が返される
例3:安全なログ記録
// 悪い例 — 識別可能な患者データをログに記録
console.log('Processing patient:', patient);
// 良い例 — 不透明な内部レコードIDのみをログに記録
console.log('Processing record:', patient.id);
// 注意:patient.idは不透明なUUIDであり、カルテ番号ではないこと