HIPAA & PHI - Covered Entities, Business Associates, BAAs, Security Rule Safeguards, Breach Notification & De-identification
HIPAA for builders: PHI vs health data that is not PHI, covered entities vs business associates, BAAs (including cloud and no-view providers), the Privacy Rule's minimum necessary standard, Security Rule safeguards and the status of the 2025 proposal, and breach notification (60 days, the 500 thresholds, the encryption safe harbor). Also covers Safe Harbor vs Expert Determination de-identification, tracking-technology guidance and enforcement cases, with a runnable de-identifier and PHI-safe logger.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
Is a patient-facing fitness app that no provider is involved with covered by HIPAA?
Answer
Usually not. It may fall under the FTC Health Breach Notification Rule and state health-privacy laws instead.
L2
Is a cloud provider storing only encrypted ePHI a business associate?
Answer
Yes. HHS says storing ePHI makes it a BA even with no key; the conduit exception covers only transient transmission.
L3
What does addressable mean?
Answer
Assess the spec; implement it if reasonable and appropriate, otherwise document why and implement an equivalent alternative.
L4
What is minimum necessary?
Answer
Use, disclose or request only the PHI a purpose needs. It does not apply to treatment disclosures between providers.
L5
When is a lost laptop not a reportable breach?
Answer
When its PHI was encrypted consistently with HHS guidance and the key was not compromised, so the PHI counts as secured.
L6
Why does an unkeyed hash of an MRN fail Safe Harbor?
Answer
Anyone with a list of MRNs can recompute it, so HHS treats it as an identifying code.
L7
What does OCR cite most after a breach?
Answer
A missing accurate and thorough enterprise-wide risk analysis, as in Anthem, Premera and the four 2026 ransomware settlements.
Failure modes
PHI copied into vendors without a BAA
Request bodies in debug logs, analytics tags on authenticated pages or chart text pasted into a chatbot send ePHI to a vendor with no BAA, an impermissible disclosure.
ePHI in a non-eligible cloud service
A cloud BAA covers only listed HIPAA-eligible services, so storing ePHI anywhere else is a violation even with no breach.
Encryption skipped as addressable
A lost device becomes a reportable breach of unsecured PHI, and the missing written justification becomes its own finding.
Misconceptions
Our vendor is HIPAA compliant, so we are.
Compliance is shared. The vendor's BAA covers its services; your configuration, access control and logging are yours.
Hashing the MRN de-identifies the data.
Unkeyed hashes fail Safe Harbor. Use a random code with a protected lookup table, or a keyed hash under Expert Determination.
The 2025 Security Rule update is in force.
It is still a proposal as of 2026-10-08, with final action projected for July 2027. The current rule applies, though building to the proposal is sensible.
Interviewer traps
Saying a BA has 60 days after the covered entity learns of a breach.
The BA reports to the covered entity without unreasonable delay and within 60 days of its own discovery; BAAs usually set a much shorter window.
Sending PHI to an LLM API because the vendor is reputable.
A prompt is a disclosure. It needs a BAA covering that service and its required configuration, or de-identification first.
Design scenario
Same prompt for every reader.
Requirements
Clinicians see what treatment needs; support and analytics must not see clinical notes; every PHI access is attributable.
Failure assumptions
- A clinician account is phished.
- The log vendor has no BAA.
- A support engineer exports charts without a stated purpose.
Constraints
- The cloud BAA covers a listed set of eligible services.
- The LLM vendor offers a BAA only with zero data retention.
Prompt
Design the PHI path for a telehealth SaaS where clinicians open patient charts, the support team handles tickets, and product wants analytics and an LLM summary feature.
API
How do authentication, BAA scope, role and purpose checks and minimum-necessary field filtering sit in front of the chart API?
Data
Where do ePHI, de-identified analytics data and the audit trail live, and how is each encrypted and retained?
Architecture
How do you keep PHI out of logs, analytics and prompts, and detect snooping fast enough to meet the 60-day clock?
Overview
Engineering guidance, not legal advice. Whether a law applies to your company, and what a contract obliges you to do, is a question for counsel and your auditors. Facts were checked against official sources on 2026-10-08.
HIPAA is the US law that governs protected health information (PHI) held by covered entities (health plans, health care clearinghouses and most health care providers) and the business associates that handle PHI for them. For engineers it comes down to five ideas: know what counts as PHI, know whether you are a covered entity or a business associate and sign a BAA for every PHI flow, apply the Security Rule safeguards to ePHI (risk analysis first), keep PHI out of places that were never meant to hold it (logs, analytics, LLM prompts), and be ready to run a breach notification on a 60-day clock. This page compares Safe Harbor with Expert Determination de-identification, required with addressable safeguards, and the BAA patterns for cloud and AI vendors, and it ends with public enforcement cases.
Not re-taught here (see Elsewhere in the library): envelope encryption and KMS, ABAC policy design, secrets leakage and audit trails, observability pipelines, backup and DR strategy.
What PHI and ePHI are
PHI is individually identifiable health information held or transmitted by a covered entity or business associate, in any form: electronic, paper or oral. It is health information (past, present or future physical or mental condition, care provided, or payment for care) linked to something that identifies the person. ePHI is PHI in electronic form, and it is what the Security Rule protects.
The link matters. A list of names and phone numbers is not PHI on its own. The same list labelled "patients of the Lakeside oncology clinic" is. An app's appointment table, a lab result webhook, a claims file and a support ticket that mentions a diagnosis are all PHI when they sit with a covered entity or its business associate.
Not PHI: de-identified data, employment records a covered entity holds as an employer, education records covered by FERPA, and health data a consumer app collects directly from users with no covered entity involved. That last case is a trap: the app is outside HIPAA but may fall under the FTC Health Breach Notification Rule and state health-privacy laws.
The 18 Safe Harbor identifiers
Safe Harbor de-identification (45 CFR 164.514(b)(2)) requires removing these identifiers of the individual and of relatives, employers and household members:
| # | Identifier | # | Identifier |
|---|---|---|---|
| 1 | Names | 10 | Account numbers |
| 2 | Geographic subdivisions smaller than a state (ZIP3 allowed if its area has more than 20,000 people) | 11 | Certificate/license numbers |
| 3 | All date elements except year directly related to the person; ages over 89 aggregated to 90+ | 12 | Vehicle identifiers, including license plates |
| 4 | Telephone numbers | 13 | Device identifiers and serial numbers |
| 5 | Fax numbers | 14 | Web URLs |
| 6 | Email addresses | 15 | IP addresses |
| 7 | Social Security numbers | 16 | Biometric identifiers (finger and voice prints) |
| 8 | Medical record numbers | 17 | Full-face photos and comparable images |
| 9 | Health plan beneficiary numbers | 18 | Any other unique identifying number, characteristic or code |
Plus a second condition: the covered entity must have no actual knowledge that what remains could identify someone.
Keep PHI out of logs by design, or log everything and scrub later?
Prefer
Allowlisted logs plus a separate audit trail
Structured logs keep only approved keys, and PHI access goes to its own append-only audit store.
- The logger dropped name, dob, diagnosis and body keys and kept counts only.
- The audit trail recorded who viewed which patient and why, and flagged an export with no purpose.
- Patient references replace names, so log lines stay useful for debugging.
Alternative
Log everything, scrub afterwards
Full request bodies go to the log pipeline and regexes try to remove identifiers.
- The log vendor now holds ePHI and needs a BAA.
- Regex scrubbers catch an SSN in a URL but miss a name typed into a note.
- There is no clean record of who accessed which patient.
A PHI chart request, condensed
Diagram 1 condensed.
- 1
Authenticate and check BAA scope
Tie the request to one person and allow PHI only to services and vendors covered by a signed BAA. - 2
Check role, purpose and minimum necessary
Allow access only for a permitted purpose and return only the fields that purpose needs. - 3
Decrypt with KMS and write the access audit
Managed-key encryption keeps PHI secured; the audit record says who viewed which record. - 4
Return a redacted view and log without PHI
Keep PHI out of screens, caches, logs and traces that were never built to hold it. - 5
Anomaly detected?
Snooping or compromise starts a four-factor risk assessment on the 60-day clock.
Covered entities, business associates and BAAs
| Role | Who | Obligations |
|---|---|---|
| Covered entity | Health plans, clearinghouses, providers that conduct standard electronic transactions (claims, eligibility) | All three rules: Privacy, Security, Breach Notification |
| Business associate (BA) | Anyone that creates, receives, maintains or transmits PHI on a covered entity's behalf: EHR and telehealth SaaS, billing, cloud hosting, IT support, analytics | Security Rule directly (since HITECH), Privacy Rule uses only as the BAA allows, breach notice to the covered entity |
| Subcontractor BA | A BA's vendors that touch PHI: your cloud provider, log platform, email service | Same as a BA; you need a BAA with them |
| Conduit (narrow exception) | Pure transmission with only transient storage: postal service, ISPs | Not a BA; does not apply to anything that stores data |
A business associate agreement is the contract HIPAA requires before PHI flows to a BA. It limits permitted uses and disclosures, requires Security Rule safeguards, requires breach and incident reporting, flows the same terms down to subcontractors, and requires return or destruction of PHI at termination.
Cloud BAAs. HHS guidance is explicit: a cloud provider that stores ePHI is a business associate even if the data is encrypted and the provider holds no key ("no-view" services). Major clouds sign a BAA that covers only a listed set of HIPAA-eligible services, so the engineering control is: PHI may only land in services on that list, configured as the BAA requires. Putting ePHI in a non-eligible service is a violation even when the vendor's security is excellent.
AI and LLM vendors. Sending PHI in a prompt is a disclosure to that vendor. It is allowed only if the vendor signs a BAA covering that service, under the configuration it requires (often zero data retention and no training on your data). Otherwise de-identify first, or keep the model inside your own BAA-covered environment.
The Privacy Rule: minimum necessary
The Privacy Rule defines permitted uses and disclosures (treatment, payment, health care operations, and others) and patient rights (access, amendment, accounting of disclosures). Its most engineering-relevant standard is minimum necessary: use, disclose and request only the PHI needed for the purpose. It does not apply to disclosures to a provider for treatment, to the patient, under the patient's authorization, to HHS, or where required by law.
In systems this becomes field-level and purpose-aware access: a billing service sees codes and amounts but not clinical notes; a scheduling UI shows names and times but not diagnoses; analytics jobs read de-identified or limited data sets. Role-based access alone tends to over-grant; attribute-based policies that include purpose and relationship to the patient fit better (see abac-attributes-policies-pdp-pep).
The Security Rule: safeguards, required vs addressable
The Security Rule protects the confidentiality, integrity and availability of ePHI with three safeguard families. Each standard has implementation specifications marked Required (R) or Addressable (A).
| Family (45 CFR) | Examples (R = required, A = addressable) |
|---|---|
| Administrative (164.308) | Risk analysis (R), risk management (R), sanction policy (R), information system activity review (R), workforce security, security awareness training, security incident procedures, contingency plan: data backup (R), disaster recovery (R), emergency mode (R), testing (A); business associate contracts |
| Physical (164.310) | Facility access controls, workstation use and security, device and media controls: disposal (R), media re-use (R), accountability (A), data backup before moves (A) |
| Technical (164.312) | Access control: unique user ID (R), emergency access (R), automatic logoff (A), encryption/decryption (A); audit controls (standard, required); integrity: authenticate ePHI (A); person or entity authentication (required); transmission security: integrity controls (A), encryption (A) |
Addressable does not mean optional. For each addressable spec you assess whether it is reasonable and appropriate; if yes you implement it; if not you document why and implement an equivalent alternative. For a cloud system there is no credible write-up that declines encryption.
Status of the 2025 proposal. HHS published a Security Rule NPRM on 2025-01-06 that would remove the addressable category, require encryption at rest and in transit, MFA, asset inventories and network maps, and add testing and restore-time requirements. As of 2026-10-08 it is still a proposal: no final rule has been published, and the 2026 regulatory agenda moved final action to a projected July 2027. The current rule remains in force, but building to the proposal (encrypt everything, MFA everywhere, inventory) is the sensible engineering target.
The control OCR cites most often is the first one: an accurate and thorough, enterprise-wide risk analysis. All four ransomware settlements OCR announced on 2026-04-23 cited a failure to conduct one.
The Breach Notification Rule
A breach is an impermissible acquisition, access, use or disclosure of unsecured PHI. It is presumed to be a breach unless a documented four-factor risk assessment shows a low probability that the PHI was compromised (nature and extent of the PHI, who received it, whether it was actually acquired or viewed, how far the risk was mitigated).
| Who is told | When |
|---|---|
| Affected individuals | Without unreasonable delay, no later than 60 calendar days after discovery |
| Prominent media in a state or jurisdiction | If more than 500 residents of that state or jurisdiction are affected, same deadline |
| HHS | 500 or more individuals: contemporaneously with individual notice (within 60 days); fewer than 500: log them and report within 60 days after the end of the calendar year |
| The covered entity (by a BA) | Without unreasonable delay, no later than 60 days after discovery; BAAs usually set a much shorter window |
Unsecured is the engineering lever: PHI encrypted consistently with HHS guidance (NIST-aligned encryption, keys not compromised) or properly destroyed is "secured", and losing it is not a reportable breach. A stolen laptop with full-disk encryption and no exposed key is an incident; the same laptop unencrypted is a breach notice to every patient on it.
De-identification, limited data sets and pseudonyms
| Method | How | Pros | Cons |
|---|---|---|---|
| Safe Harbor | Remove the 18 identifiers, no actual knowledge of re-identification | Mechanical, cheap, no expert needed | Destroys dates and fine geography; free text is hard; ZIP3 table needs current Census data |
| Expert Determination | A qualified statistician documents that re-identification risk is very small for the anticipated recipient | Keeps more utility (generalized dates, ZIP, keyed-hash IDs) | Costs money, recipient-specific, often time-limited |
| Limited data set | Remove 16 direct identifiers but keep dates, city, state, 5-digit ZIP, ages | Useful for research, public health, operations | Still PHI; requires a data use agreement |
Two details catch engineers. First, a re-identification code is allowed under Safe Harbor only if it is not derived from patient information; HHS guidance says even a hash without a secret key is an identifying code. A keyed hash can pass only under Expert Determination with the key withheld. Second, identifiers in free text count exactly like structured fields.
How a PHI request flows through a compliant system
Diagram 1: a clinician's request for a patient chart in a business associate's SaaS, with the failure path.
Decisions
- 1
Step 1: user authenticates
- nextStep 2: check BAA scope
- 2
Step 2: check BAA scope
- nextStep 3: check role and purpose
- 3
Step 3: check role and purpose
- nextStep 4: apply minimum necessary
- 4
Step 4: apply minimum necessary
- nextStep 5: decrypt with KMS
- 5
Step 5: decrypt with KMS
- nextStep 6: write access audit
- 6
Step 6: write access audit
- nextStep 7: return redacted view
- 7
Step 7: return redacted view
- nextStep 8: app logs without PHI
- 8
Step 8: app logs without PHI
- nextStep 9: anomaly detected?
- ?
Step 9: anomaly detected?
- nextStep 10: periodic access review
- nextFailure: suspected breach
- 10
Step 10: periodic access review
- 11
Failure: suspected breach
- nextFour-factor risk assessment
- 12
Four-factor risk assessment
- nextNotify within 60 days
- 13
Notify within 60 days
Lesson map
HIPAA & PHI - Covered Entities, Business Associates, BAAs, Security Rule Safeguards, Breach Notification & De-identification
HIPAA for builders: PHI vs health data that is not PHI, covered entities vs business associates, BAAs (including cloud and no-view providers), the Privacy Rule's minimum necessary standard, Security Rule safeguards and the status of the 2025 proposal, and breach notification (60 days, the 500 thresholds, the encryption safe harbor). Also covers Safe Harbor vs Expert Determination de-identification, tracking-technology guidance and enforcement cases, with a runnable de-identifier and PHI-safe logger.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB s1["Step 1: user authenticates"] s2["Step 2: check BAA scope"] s3["Step 3: check role and purpose"] s4["Step 4: apply minimum necessary"] s5["Step 5: decrypt with KMS"] s6["Step 6: write access audit"] s7["Step 7: return redacted view"] s8["Step 8: app logs without PHI"] s9["Step 9: anomaly detected?"] s10["Step 10: periodic access review"] f1["Failure: suspected breach"] f2["Four-factor risk assessment"] f3["Notify within 60 days"] s1 -->|continues| s2 s2 -->|continues| s3 s3 -->|continues| s4 s4 -->|continues| s5 s5 -->|continues| s6 s6 -->|continues| s7 s7 -->|continues| s8 s8 -->|continues| s9 s9 -->|continues| s10 s9 -->|continues| f1 f1 -->|continues| f2 f2 -->|continues| f3
Runnable: Safe Harbor de-identifier
The concept above in code: structured identifiers dropped, dates reduced to year, ages over 89 collapsed, restricted ZIP3s zeroed, free text scrubbed, and a random (not derived) study code kept by the covered entity.
"""HIPAA Safe Harbor de-identifier (45 CFR 164.514(b)(2)) - teaching version.
Concept first:
* Safe Harbor = remove all 18 identifier types of the patient AND of relatives,
employers and household members, and have no actual knowledge that what is
left could identify someone.
* Dates -> year only. Ages over 89 -> "90+" (and birth years that imply 90+).
* ZIP -> first 3 digits, unless that ZIP3 area has <= 20,000 people -> "000".
* Free text counts too: an SSN inside a note is still an identifier.
* A re-identification code is allowed ONLY if it is not derived from patient
data (164.514(c)). An unkeyed or keyed hash of the MRN is derived, so it is
not Safe Harbor; use a random code held in a protected lookup table.
Real systems add NLP-based PHI detection for notes; regexes alone miss names.
"""
import re, secrets
from datetime import date
# HHS list of restricted 3-digit ZIPs (2000 Census). Refresh from current Census data.
RESTRICTED_ZIP3 = {"036", "059", "063", "102", "203", "556", "692", "790", "821",
"823", "830", "831", "878", "879", "884", "890", "893"}
DROP_FIELDS = {"name", "street", "city", "phone", "fax", "email", "ssn", "mrn",
"health_plan_id", "account_no", "license_no", "vehicle_plate",
"device_serial", "url", "ip", "photo", "biometric", "employer"}
FREE_TEXT_PATTERNS = [
(re.compile(r"\b\d{3}-\d{2}-\d{4}\b"), "[SSN]"),
(re.compile(r"\b[\w.+-]+@[\w-]+\.[\w.]+\b"), "[EMAIL]"),
(re.compile(r"\(?\b\d{3}\)?[-. ]\d{3}[-. ]\d{4}\b"), "[PHONE]"),
(re.compile(r"\bMRN[:# ]*\d+\b", re.I), "[MRN]"),
(re.compile(r"\b\d{1,2}/\d{1,2}/(\d{4})\b"), r"\1"), # 03/14/2026 -> 2026
(re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"), "[IP]"),
]
reid_table: dict[str, str] = {} # random code -> MRN; stays with the covered entity
def zip3(z: str) -> str:
return "000" if z[:3] in RESTRICTED_ZIP3 else z[:3]
def age_bucket(birth: date, as_of: date) -> tuple[str, str]:
age = as_of.year - birth.year - ((as_of.month, as_of.day) < (birth.month, birth.day))
if age > 89:
return "90+", "90+" # year of birth would reveal the age, so collapse it too
return str(age), str(birth.year)
def scrub_text(t: str) -> str:
for rx, rep in FREE_TEXT_PATTERNS:
t = rx.sub(rep, t)
return t
def deidentify(rec: dict, as_of: date) -> dict:
out = {k: v for k, v in rec.items() if k not in DROP_FIELDS}
out["zip3"] = zip3(out.pop("zip"))
out["age"], out["birth_year"] = age_bucket(out.pop("dob"), as_of)
out["admit_year"] = str(out.pop("admit_date").year)
out["note"] = scrub_text(out["note"])
code = secrets.token_hex(4) # random, NOT derived from the MRN
reid_table[code] = rec["mrn"]
out["study_code"] = code
return out
def residual_identifiers(rec: dict) -> list[str]:
# a cheap safety net: scan output for anything that still looks like an identifier
blob = " ".join(map(str, rec.values()))
return [rep for rx, rep in FREE_TEXT_PATTERNS[:4] if rx.search(blob)]
if __name__ == "__main__":
patients = [
{"name": "Ana Ruiz", "mrn": "448812", "ssn": "123-45-6789", "zip": "60614",
"dob": date(1958, 3, 2), "admit_date": date(2026, 9, 30), "dx": "E11.9",
"employer": "Acme Corp",
"note": "Pt called from 312-555-0199 re MRN 448812, follow-up 10/14/2026."},
{"name": "Joe Blake", "mrn": "990011", "ssn": "987-65-4321", "zip": "82301",
"dob": date(1931, 7, 9), "admit_date": date(2026, 8, 1), "dx": "I10",
"employer": "-", "note": "Daughter email joe.b@example.com; seen 08/01/2026."},
]
print("(study_code is random per run, so it is not printed)")
for p in patients:
d = deidentify(p, as_of=date(2026, 10, 8))
shown = {k: v for k, v in d.items() if k != "study_code"}
print(shown)
print(" residual identifiers:", residual_identifiers(d) or "none")
# what NOT to do: hashing the MRN is still an identifying code under Safe Harbor
import hashlib
print("unsalted sha256(MRN) prefix:", hashlib.sha256(b"448812").hexdigest()[:12],
"-> anyone can rebuild it from a list of MRNs, so it fails (R)")
print("re-id table size kept by covered entity:", len(reid_table))Output:
(study_code is random per run, so it is not printed)
{'dx': 'E11.9', 'note': 'Pt called from [PHONE] re [MRN], follow-up 2026.', 'zip3': '606', 'age': '68', 'birth_year': '1958', 'admit_year': '2026'}
residual identifiers: none
{'dx': 'I10', 'note': 'Daughter email [EMAIL]; seen 2026.', 'zip3': '000', 'age': '90+', 'birth_year': '90+', 'admit_year': '2026'}
residual identifiers: none
unsalted sha256(MRN) prefix: 5dd30bb35eb1 -> anyone can rebuild it from a list of MRNs, so it fails (R)
re-id table size kept by covered entity: 2Notice the second patient: born in 1931, so both age and birth year collapse to "90+", and ZIP 82301 starts with 823, one of the 17 restricted three-digit areas, so it becomes "000". The first patient keeps "606" because that area has far more than 20,000 people. The regexes catch the phone, MRN, email and dates, but they would miss a name typed into a note. Real pipelines add a clinical NLP de-identifier and human review for free text.
PHI in logs, analytics and LLM prompts
Most PHI leaks in modern stacks are not hacks; they are copies. A request body in a debug log ships to a log SaaS with no BAA. A product analytics SDK on an authenticated patient portal sends page URLs that include a condition. A support engineer pastes a chart into a chatbot. Each copy creates a new system holding ePHI that needs a BAA, access control, audit and retention.
The online-tracking problem is real: OCR's guidance on tracking technologies (updated March 2024) says tracking code on authenticated patient pages generally has access to PHI and needs a BAA or must not be used. In June 2024 a federal court (AHA v. Becerra) vacated the part of that guidance covering certain unauthenticated public pages, but the authenticated-page analysis was not changed. Engineers should treat analytics on logged-in health experiences as a PHI disclosure.
The controls are design decisions: deny-by-default structured logging, PHI never in URLs or query strings, internal patient references instead of names, a separate audit trail for PHI access, and an allowlist of BAA-covered destinations enforced in egress and in code review.
Runnable: PHI-safe logging with a separate access audit
// PHI-safe logging: allowlist fields, redact free text, and write PHI ACCESS
// to a separate audit trail.
// Concept: application logs flow to many places (log SaaS, on-call laptops,
// support tickets, LLM debugging tools). If PHI lands there, every one of those
// becomes a system holding ePHI, and every vendor needs a BAA. So:
// 1. app logs are deny-by-default: only allowlisted keys pass through;
// 2. strings that do pass are scrubbed for obvious identifiers;
// 3. "who looked at which patient record and why" goes to a dedicated,
// access-controlled, append-only audit log (164.312(b) audit controls),
// keyed by an internal id, never by name.
type Fields = Record<string, unknown>;
const ALLOW = new Set(["event", "route", "status", "latency_ms", "request_id", "user_id", "patient_ref", "error_code"]);
const REDACTORS: [RegExp, string][] = [
[/\b\d{3}-\d{2}-\d{4}\b/g, "[SSN]"],
[/\b[\w.+-]+@[\w-]+\.[\w.]+\b/g, "[EMAIL]"],
[/\(?\b\d{3}\)?[-. ]\d{3}[-. ]\d{4}\b/g, "[PHONE]"],
[/\b(19|20)\d{2}-\d{2}-\d{2}\b/g, "[DATE]"],
[/\bMRN[:# ]*\d+\b/gi, "[MRN]"],
];
function scrub(v: unknown): unknown {
if (typeof v !== "string") return v;
return REDACTORS.reduce((s, [rx, rep]) => s.replace(rx, rep), v);
}
const dropped: Record<string, number> = {};
function appLog(fields: Fields): string {
const safe: Fields = {};
for (const [k, v] of Object.entries(fields)) {
if (ALLOW.has(k)) safe[k] = scrub(v);
else dropped[k] = (dropped[k] ?? 0) + 1; // count, never print, the dropped value
}
return JSON.stringify(safe);
}
interface AuditEvent { ts: string; actor: string; action: "view" | "export" | "update"; patient_ref: string; purpose: string }
const audit: AuditEvent[] = [];
function auditPhiAccess(e: AuditEvent): void {
audit.push({ ...e }); // in production: append-only store (WORM bucket / ledger table), separate IAM
}
// --- demo -----------------------------------------------------------------
const lines = [
appLog({ event: "chart_view", route: "/patients/:id", status: 200, latency_ms: 41, request_id: "r-1",
user_id: "u-77", patient_ref: "pt_9f2c", name: "Ana Ruiz", dob: "1958-03-02", diagnosis: "E11.9" }),
appLog({ event: "msg_send_failed", status: 502, request_id: "r-2", user_id: "u-77", error_code: "SMTP_TIMEOUT",
route: "/messages", body: "Hi Ana, your A1C is 9.1" }),
appLog({ event: "search", status: 200, request_id: "r-3", user_id: "u-12",
route: "/search?q=123-45-6789 ana@example.com", latency_ms: 88 }),
];
lines.forEach((l) => console.log("app:", l));
console.log("dropped keys (counts only):", JSON.stringify(dropped));
auditPhiAccess({ ts: "2026-10-08T14:02:11Z", actor: "u-77", action: "view", patient_ref: "pt_9f2c", purpose: "treatment" });
auditPhiAccess({ ts: "2026-10-08T14:05:40Z", actor: "u-12", action: "export", patient_ref: "pt_9f2c", purpose: "none-given" });
const suspicious = audit.filter((e) => e.action === "export" && e.purpose === "none-given");
console.log("audit events:", audit.length, "| flagged for review:", suspicious.map((e) => `${e.actor}:${e.action}:${e.patient_ref}`).join(","));Output:
app: {"event":"chart_view","route":"/patients/:id","status":200,"latency_ms":41,"request_id":"r-1","user_id":"u-77","patient_ref":"pt_9f2c"}
app: {"event":"msg_send_failed","status":502,"request_id":"r-2","user_id":"u-77","error_code":"SMTP_TIMEOUT","route":"/messages"}
app: {"event":"search","status":200,"request_id":"r-3","user_id":"u-12","route":"/search?q=[SSN] [EMAIL]","latency_ms":88}
dropped keys (counts only): {"name":1,"dob":1,"diagnosis":1,"body":1}
audit events: 2 | flagged for review: u-12:export:pt_9f2cExpectedapp: {"event":"chart_view","route":"/patients/:id","status":200,"latency_ms":41,"request_id":"r-1","user_id":"u-77","patient_ref":"pt_9f2c"} app: {"event":"msg_send_failed","status":502,"request_id":"r-2","user_id":"u-77","error_code":"SMTP_TIMEOUT","route":"/messages"} app: {"event":"search","status":200,"request_id":"r-3","user_id":"u-12","route":"/search?q=[SSN] [EMAIL]","latency_ms":88} dropped keys (counts only): {"name":1,"dob":1,"diagnosis":1,"body":1} audit events: 2 | flagged for review: u-12:export:pt_9f2c
Press Run. Snippets must be self-contained — no network, files, or native modules.
The third line shows why the allowlist is not enough on its own: an SSN arrived inside a search URL. The scrubber caught it, but the real fix is to never put identifiers in URLs. Note that the audit trail records who accessed which patient and why, which is exactly what an investigator asks for after a snooping incident.
Decision chart: can this data flow to this system?
Decisions
- 1
Q1
- nextNormal data controls
- nextQ2: can it be de-identified first?
- 2
Normal data controls
- ?
Q2: can it be de-identified first?
- nextSafe Harbor or Expert Determination
- nextQ3: destination under a BAA?
- 4
Safe Harbor or Expert Determination
- ?
Q3: destination under a BAA?
- nextBlock the flow or sign a BAA
- nextQ4: HIPAA-eligible service and config?
- 6
Block the flow or sign a BAA
- ?
Q4: HIPAA-eligible service and config?
- nextMove to an eligible service
- nextQ5: minimum necessary fields only?
- 8
Move to an eligible service
- ?
Q5: minimum necessary fields only?
- nextTrim fields or use a limited data set
- nextAllow, encrypt, audit access
- 10
Trim fields or use a limited data set
- 11
Allow, encrypt, audit access
Enforcement examples (public HHS OCR cases)
| Case | Year | Amount | What OCR found |
|---|---|---|---|
| Anthem | 2018 | $16 million (largest HIPAA settlement to date) | Cyberattack exposed ePHI of almost 79 million people; failures included enterprise-wide risk analysis, information system activity review and access controls |
| Premera Blue Cross | 2020 | $6.85 million | Phishing-installed malware went undetected for about nine months; over 10.4 million people affected; failures in risk analysis, risk management and audit controls |
| Four ransomware settlements (Axia Women's Health, Assured Imaging, Consociate Health, Star Group health plan) | 2026-04-23 | $1,165,000 total, plus two-year corrective action plans | Every case cited a missing accurate and thorough risk analysis; some also impermissible disclosure and late breach notice |
The lesson for engineers is consistent: the expensive cases are about basic controls not operating (risk analysis, log review, access control), not about exotic attacks.
What happens if you choose otherwise
- Store ePHI in a cloud service outside the BAA's eligible list: a Privacy and Security Rule violation even with no breach.
- Hash MRNs and call the data de-identified: unkeyed hashes fail Safe Harbor; recipients can rebuild them from MRN lists.
- Skip encryption because it is addressable: a lost device becomes a reportable breach of unsecured PHI, and the missing write-up becomes a finding.
- Log request bodies in a health app: your log vendor now holds ePHI; without a BAA that is an impermissible disclosure.
Pitfalls
- Treating "we are HIPAA compliant" as a property of a vendor. Compliance is shared: the vendor's BAA covers its services, and your configuration and access control are yours.
- Forgetting that BAs report breaches to the covered entity, which then runs the 60-day clock; slow BA reporting eats the covered entity's time.
- Running one-time risk analyses. OCR expects them kept current as systems change.
- Using production PHI in staging, demos or test fixtures.
How the code was checked
- The Safe Harbor de-identifier ran under Python 3.13 and the PHI-safe logger under
tsc --strictand Node 20. Their output blocks are the real captured output. - The study code is random per run, so the de-identifier does not print it. Patients, MRNs and messages are made-up example data.
Interview Q&A
What makes health data PHI?
Answer
It is individually identifiable health or payment information held or transmitted by a covered entity or business associate. The identifier has to be linked to health information; a contact list alone is not PHI, the same list as a clinic's patient list is.
Covered entity vs business associate?
Answer
Covered entities are health plans, clearinghouses and providers that conduct standard electronic transactions. A business associate creates, receives, maintains or transmits PHI on their behalf, such as a telehealth SaaS or a billing vendor. Since HITECH, BAs are directly liable under the Security Rule.
Is a cloud provider a business associate if it only stores encrypted ePHI and never has the key?
Answer
Yes. HHS guidance says storing ePHI makes it a BA even in a no-view model. The conduit exception covers only transmission with transient storage.
Explain required vs addressable implementation specifications.
Answer
Required specs must be implemented. Addressable specs must be assessed: implement if reasonable and appropriate, otherwise document why and implement an equivalent alternative. Addressable is not optional, and the 2025 proposal would make nearly all of them required.
What is minimum necessary and how do you implement it?
Answer
Use, disclose or request only the PHI needed for the purpose. In code: field-level, purpose-aware authorization, separate views per role, and de-identified or limited data sets for analytics. It does not apply to treatment disclosures between providers.
Walk through HIPAA breach notification timing.
Answer
Individuals within 60 calendar days of discovery, without unreasonable delay. Media too if more than 500 residents of one state or jurisdiction are affected. HHS at the same time if 500 or more people are affected, otherwise in an annual log within 60 days after year end. Encrypted PHI with an uncompromised key is secured and not reportable.
Safe Harbor vs Expert Determination?
Answer
Safe Harbor removes the 18 identifiers and requires no actual knowledge of re-identification; it is cheap but loses dates and geography. Expert Determination has a statistician document very small re-identification risk for a specific recipient; it keeps more utility but costs more and is context-specific.
Is a SHA-256 of the MRN a de-identified ID?
Answer
Not under Safe Harbor: HHS treats an unkeyed hash as an identifying code because anyone with MRNs can recompute it. Use a random code with a protected lookup table, or a keyed hash under Expert Determination with the key withheld.
How do you keep PHI out of logs?
Answer
Deny-by-default structured logging with allowlisted fields, no PHI in URLs, internal patient references, scrubbing as a safety net, and a separate, access-controlled audit log for PHI access. Every log destination needs a BAA if PHI can reach it.
Can you send PHI to an LLM API?
Answer
Only if the vendor signs a BAA covering that service and you use the required configuration, typically zero retention and no training. Otherwise de-identify first or run the model in your own BAA-covered environment. The prompt is a disclosure like any other.
What does OCR most often cite after a breach?
Answer
The lack of an accurate, thorough, enterprise-wide risk analysis, followed by missing risk management, audit controls and activity review. Anthem, Premera and the 2026 ransomware settlements all cite these.
Check yourself
Trace one PHI field (for example a diagnosis code) through a system you know: every service, log, cache, analytics tag and vendor it reaches. Mark each hop with whether a BAA covers it and whether it needs the field at all.
Elsewhere in the library
These pages stay as they are. This lesson only points at them: Envelope Encryption — DEK, KEK & CMK Hierarchy, ABAC — Attributes, Policies, PDP & PEP, Secrets Threat Model — Leakage Paths, Side Channels & Audit Trails, Observability Triad — Metrics, Logs & Distributed Tracing, Backups That Actually Restore - Snapshots, PITR, Immutable Copies & Restore Drills, RTO, RPO & DR Strategies - Backup/Restore vs Pilot Light vs Warm Standby vs Active-Active, Service-to-Service Auth — mTLS, Client Credentials & Workload Identity, TLS — Handshake, Certs, mTLS & Termination, Agent Reliability — Evals, Guardrails, Tracing & Failure Modes.
Go Deeper
- HHS: Summary of the HIPAA Privacy Rule
- HHS: Summary of the HIPAA Security Rule
- HHS: Breach Notification Rule
- HHS: De-identification guidance (Safe Harbor and Expert Determination)
- HHS: Guidance on HIPAA and Cloud Computing
- HHS: Business Associate Contracts (sample provisions)
- HHS: Minimum Necessary Requirement
- HHS: Online tracking technologies guidance
- HHS: Security Rule NPRM fact sheet (proposed, not final)
- HHS: Anthem $16 million settlement
- HHS: Premera $6.85 million settlement
- HHS: Four ransomware settlements (2026-04-23)
- NIST SP 800-66 Rev. 2: Implementing the HIPAA Security Rule