---
name: healthcare-phi-compliance
description: Protect health and personal data in health apps. Use for access rules, APIs, databases, logs, audits, storage, exports, and security reviews.
origin: Health1 Super Speciality Hospitals, contributed by Dr. Keyur Patel
version: "1.0.0"
---

# Healthcare PHI and PII Safety

Created and shared by **Health1 Super Speciality Hospitals and Dr. Keyur Patel**.

Use these rules to protect patient, staff, and payment data. They support HIPAA, GDPR, and other health privacy laws. Laws differ by place. Check the rules that apply to the project.

## When to Use

Use this skill when work includes:

- Patient or medical records
- Staff or clinician data
- Login or access rules
- Health database tables
- APIs that use health data
- Logs, alerts, or audit records
- Reports, files, print jobs, or exports
- Data shared across sites or tenants
- Backups, test data, or data cleanup

## Core Rules

Protect data in three ways:

1. **Classify it:** Know which data is private.
2. **Control it:** Let only approved users see or change it.
3. **Audit it:** Record who used it and why.

Use the least amount of private data needed. Block access by default.

## Classify the Data

PHI is data that can identify a person and relates to health or care. This can include:

- Name, birth date, address, phone, or email
- SSN, Aadhaar, NHS number, or other state ID
- Medical record, insurance, or claim number
- Visit, admission, or appointment facts
- Diagnosis, medicine, lab, image, or care plan
- A mix of facts that can point to one person

PII is other private data, such as:

- Staff contact details
- Pay, fees, and bank details
- Vendor payment data
- Login, device, or location data

Treat free text, files, photos, voice, and video as private if they may contain these facts.

## Safe Work Steps

For each change:

1. List the private data it reads, writes, sends, or shows.
2. Remove fields that are not needed.
3. Check the user's role, site, and reason for access.
4. Apply the same check on the server and in the database.
5. Log the action with safe IDs.
6. Test allowed and blocked users.
7. Check errors, logs, URLs, files, caches, and backups for leaks.

Never trust a hidden button or client-side check as the only access rule.

## Database Access

Turn on row-level security for every private table. Set rules for read, add, change, and delete. Test each action.

```sql
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')
    )
  );
```

Do not assume a read rule also protects updates. Add separate rules for each action.

Keep service keys on the server. Never place them in browser code, mobile apps, public files, or build output.

For urgent access, use a clear emergency flow. Ask for a reason, limit access, alert the right team, and record the event.

## Audit Records

Log each read, create, change, delete, print, download, and export.

```typescript
interface AuditEntry {
  timestamp: string;
  user_id: string;
  patient_id: string;
  action: 'create' | 'read' | 'update' | 'delete' | 'print' | 'export';
  resource_type: string;
  resource_id: string;
  reason?: string;
  ip_address: string;
  session_id: string;
  result: 'allowed' | 'blocked';
}
```

Use opaque IDs, such as random UUIDs. Do not put names, medical record numbers, diagnoses, or full field values in the audit log.

Make audit records append-only:

```sql
CREATE POLICY "audit_insert_only"
  ON audit_log
  FOR INSERT
  TO authenticated
  WITH CHECK (user_id = auth.uid());

CREATE POLICY "audit_no_update"
  ON audit_log
  FOR UPDATE
  USING (false);

CREATE POLICY "audit_no_delete"
  ON audit_log
  FOR DELETE
  USING (false);
```

Database owners may bypass these rules. Limit owner access and keep audit copies in a protected system.

## Stop Common Leaks

- **Errors:** Send a short, plain error to the user. Keep safe details on the server.
- **Logs:** Never log full records, form bodies, tokens, cookies, or file text.
- **URLs:** Do not put names, diagnoses, or record numbers in paths or query text.
- **Browser storage:** Do not store PHI in localStorage or sessionStorage.
- **Caches:** Mark private pages so shared caches do not save them.
- **Files:** Check file names, image details, PDF details, and hidden fields.
- **Exports:** Check access again. Limit fields, rows, and file life.
- **Email and chat:** Do not send PHI unless the tool and use are approved.
- **Test data:** Use fake data. Do not copy live patient data into test systems.
- **AI tools:** Do not send PHI to an AI service unless the service and use are approved.
- **Alerts:** Remove private values before sending errors to outside tools.
- **Backups:** Encrypt them, limit access, and include them in delete rules.

Use HTTPS while data moves. Use strong encryption while data is stored. Keep keys away from the data they protect.

## Mark Private Columns

Use schema notes so reviews and tools can find private fields:

```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';
```

Labels help, but they do not enforce access.

## Concrete Usage Example

A team adds an API that returns a patient's lab result.

Request:

```http
GET /api/patients/8ec2a8d7/results
```

Safe server flow:

```typescript
const user = await requireUser(request);
const patientId = requireUuid(params.patientId);

const allowed = await canReadPatient({
  userId: user.id,
  patientId,
  facilityId: user.facilityId,
});

if (!allowed) {
  await audit({
    user_id: user.id,
    patient_id: patientId,
    action: 'read',
    resource_type: 'lab_result',
    resource_id: patientId,
    session_id: user.sessionId,
    ip_address: request.ip,
    result: 'blocked',
  });

  throw new Error('Record not found');
}

const result = await loadNeededLabFields(patientId);

await audit({
  user_id: user.id,
  patient_id: patientId,
  action: 'read',
  resource_type: 'lab_result',
  resource_id: result.id,
  session_id: user.sessionId,
  ip_address: request.ip,
  result: 'allowed',
});

return result;
```

Test all of these cases:

- A worker at the right site can read the result.
- A worker at another site gets no data.
- A user with no login gets no data.
- A removed worker gets no data.
- A changed URL does not reveal another patient.
- The response has only needed fields.
- Errors and logs contain no PHI.
- Both allowed and blocked tries create audit records.

## Safe Error and Log Examples

```typescript
// Unsafe
throw new Error(`Patient ${patient.name} was not found`);
console.log('Patient:', patient);

// Safe
logger.error('Patient lookup failed', {
  recordId: patient.id,
  facilityId,
});

throw new Error('Record not found');
```

The IDs must be random internal IDs. Do not use a name, national ID, or medical record number.

## Release Check

Before release, confirm:

- Private fields are marked.
- Only needed data is used.
- Login is required on all private routes.
- Server checks and database rules agree.
- Row-level security covers each private table and action.
- Site and tenant borders were tested.
- Removed and expired users were tested.
- Audit logs cover reads, changes, prints, and exports.
- Audit logs do not hold PHI.
- Errors, URLs, logs, alerts, and file names do not leak PHI.
- Browser storage and shared caches do not hold PHI.
- Service keys are not in client code.
- Sessions expire and can be ended.
- Data is encrypted in storage and in transit.
- Backups, exports, test data, and delete rules were checked.
- The team checked the laws and contracts that apply.