医療向け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はメモリ内のみに保持し、必要に応じて取得する。
サービスロールキー: クライアントサイドコードでservice_roleキーを使用しないこと。常に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が保存されていないこと
- クライアントコードにservice_roleキーが含まれていないこと
- すべてのPHI/PIIテーブルでRLSが有効になっていること
- すべてのデータ変更に対する監査証跡が設定されていること
- セッションタイムアウトが設定されていること
- すべてのPHIエンドポイントでAPI認証が行われていること
- 施設間のデータ分離が検証されていること
例
例1:安全なエラーハンドリングと安全でないエラーハンドリング
// 悪い例 — エラーにPHIが漏洩
throw new Error(`患者 ${patient.name} が ${patient.facility} に見つかりません`);
// 良い例 — 汎用エラー、詳細はサーバー側に不透明なIDのみで記録
logger.error('患者検索失敗', { recordId: patient.id, facilityId });
throw new Error('レコードが見つかりません');
例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()
));
-- テスト:施設Aの医師としてログインし、施設Bの患者をクエリ
-- 期待結果:0行が返される
例3:安全なログ出力
// 悪い例 — 患者を特定できるデータをログ出力
console.log('患者を処理中:', patient);
// 良い例 — 不透明な内部レコードIDのみをログ出力
console.log('レコードを処理中:', patient.id);
// 注意:patient.idは医療記録番号ではなく、不透明なUUIDであるべきです