Contact Jeff

Drop me a note. I usually respond within one business day.

← All posts

Building a HIPAA Pipeline on AWS That Survives an Audit

The Change Healthcare numbers keep getting revised upward, and the figure going around this month is about 100 million people. The part I keep coming back to is how it started. Attackers used stolen credentials on a remote access portal that didn’t have multi-factor authentication turned on, and that was enough.

Nobody had to defeat an architecture. That has been true of most of the compliance problems I’ve been brought into, where the real failure turns out to be a setting nobody enabled or a policy that got loosened for a migration in 2022 and never tightened back up. Making a PHI pipeline defensible is mostly unglamorous work, and what follows is a pass through the parts of it that decide whether the thing holds up.

Clinical documents arrive in an S3 landing bucket encrypted with a customer managed key, inside a PHI zone that is in scope of the AWS business associate agreement. Processing detects and removes identifiers, then writes the identified copy to a PHI store that stays inside the zone. The only thing that crosses into the de-identified zone, which has broad access for analytics, is the de-identified output. CloudTrail data events record every read and write in the PHI zone and go to a separate log account with object lock enabled.

The whole design question is what is allowed to cross that boundary, and who can prove what crossed it.

Start with the BAA, and read the list properly

You need a business associate agreement with AWS before PHI goes anywhere near it, and that part is easy now. You accept it yourself through AWS Artifact in the console, it takes a few minutes, and it stopped being a procurement conversation years ago.

What the BAA covers is the more interesting question. AWS maintains a HIPAA eligible services list , well over a hundred services long at this point, and their framing is that customers “may use any AWS service in an account designated as a HIPAA account, but they should only process, store, and transmit protected health information (PHI) in the HIPAA eligible services.” Your account can run anything. Your PHI has a much shorter list.

Read that list properly rather than searching it for a service name, because eligibility is often scoped to part of a service rather than the whole thing. RDS is eligible for particular engines only. SageMaker is eligible with Studio Lab and the public and vendor workforce options carved out, Bedrock is eligible with specific models excluded, and Augmented AI carries a similar exclusion around workforces. I have watched a team confirm that SageMaker appeared on the list, tick the box, and then build on a part of it that was explicitly outside the carve-out.

There’s a second layer that’s easier to miss. Eligibility means AWS is willing to sign up to handling PHI in that service. It says nothing about whether the way you configured it is defensible, and under the shared responsibility model that half is entirely yours.

Addressable does not mean optional

The Security Rule tags every implementation specification as either required or addressable, and I have sat in meetings where somebody read addressable as optional and the room nodded along. It’s a costly misreading.

164.306(d) spells out the actual procedure. You assess whether the specification is a reasonable and appropriate safeguard in your environment. If it is, you implement it. If it isn’t, you document why not and you implement an equivalent alternative measure where that’s reasonable and appropriate. Nowhere does it let you skip the control and write nothing down.

Encryption of ePHI at rest under 164.312(a)(2)(iv) is addressable rather than required, which surprises people, and the same goes for encryption in transit under 164.312(e)(2)(ii). I wouldn’t spend any time at all on that particular piece of flexibility. Turning on SSE-KMS everywhere is an afternoon of work, and the alternative is writing and then maintaining a risk analysis that explains why you decided not to encrypt protected health information, which is harder to produce and much harder to defend when somebody asks about it three years later.

Audit controls under 164.312(b) sit at a different level. That one is a standard rather than an addressable specification underneath one, and it requires mechanisms that record and examine activity in information systems containing ePHI. There is no flexibility clause to reach for.

KMS keys, and the per-tenant question

Use customer managed keys rather than the AWS managed ones. The reason is authorization rather than cryptography, because a customer managed key carries its own key policy, and that policy is a boundary IAM cannot route around on its own. You can end up in a state where an administrator with broad permissions across the account still cannot decrypt the PHI bucket, purely because the key policy doesn’t say they can. That’s difficult to build any other way and auditors respond well to it.

Then, usually around month two, somebody proposes a separate KMS key per tenant. It isn’t a bad idea, which is why it’s worth being precise about what it actually buys. You get a real cryptographic boundary between customers, and you get crypto-shredding, where destroying one key renders exactly one tenant’s data unrecoverable and touches nothing else. If a contract commits you to either of those, the discussion is already over.

What pushes back isn’t quotas. AWS gives you 100,000 customer managed keys per region and 50,000 grants per key, so you’d need to be at real scale before service limits are the thing stopping you. It’s the ongoing weight instead. Each key is a dollar a month, which is $12,000 a year at a thousand tenants. Key policies drift apart from one another once there are hundreds of them. Rotation and scheduled deletion become fleet management. Any job that spans tenants starts making one KMS call per tenant instead of one call total.

My default is one key per data classification per environment, so production PHI gets a key that nothing else shares. Per-tenant keys are worth it when a contract or a regulator asks for them by name, and they’re less painful to introduce later than the people arguing for them up front tend to assume.

Turn on CloudTrail data events

This is the one that quietly ruins projects. CloudTrail data events are off by default. AWS states it plainly, that trails and event data stores do not log data events unless you configure them to, and the consequence is that a new account faithfully records who changed a bucket policy while recording nothing whatsoever about who read the objects inside it.

For most workloads that default is defensible, since data events are high volume and plenty of people genuinely don’t need them. For a HIPAA workload it’s backwards. A management event tells you a configuration changed. A data event tells you that a named principal called GetObject against a named PHI object at a specific time, and the second one is what 164.312(b) is asking you to be able to produce.

Turn them on for the buckets holding PHI and scope the event selectors to exactly those buckets. Data events bill per event, and an unscoped selector across a busy account will produce a memorable bill without producing better evidence than a targeted one would.

Where the logs land matters about as much as whether they exist. Put them in a separate log archive account with S3 Object Lock in compliance mode, so the engineer who read the PHI has no path to editing the record of having read it. That separation is most of the reason anyone believes an audit trail in the first place. Set retention to at least six years, since 164.316(b)(2)(i) requires documentation be retained “for 6 years from the date of its creation or the date when it last was in effect, whichever is later.” I have seen a lifecycle rule quietly expire an audit trail at 90 days because that was the number already in the console, and that is not something you want to discover on the day somebody asks.

Safe Harbor, Expert Determination, and a confidence score

There are two routes to de-identified data, and which one you’re on decides how much of this you can automate. Teams often end up on one by accident and find out about the constraints partway through building.

Safe Harbor is the mechanical route. You remove the 18 enumerated identifiers, covering names, geography below state level, every date element more specific than a year, phone numbers, record numbers, biometrics, full face photos and the rest of the list, and you also need no actual knowledge that what remains could identify somebody. Being a checklist is exactly what lets it become a pipeline stage.

Expert Determination is the other route, where a person with the right statistical background documents that the risk of re-identification is very small. That’s someone writing a report, and no amount of engineering turns it into anything else. It does tend to let you keep data that Safe Harbor would force you to destroy, which is why research teams keep asking about it.

On the automated side, Amazon Comprehend Medical has a DetectPHI operation that pulls names, ages, dates, addresses, IDs and professions out of clinical text. It works well. AWS is also unusually candid in the documentation about where it stops, noting that the detected entities “don’t map 1:1 to the list specified by the Safe Harbor method” and recommending additional human review for compliance use cases specifically.

Their own example shows why. Run DetectPHI over “Patient is John Smith, a 48-year-old teacher and resident of Seattle, Washington” and “John Smith” comes back at 0.997 while “Washington” comes back at 0.38.

{
  "Text": "Washington",
  "Category": "PROTECTED_HEALTH_INFORMATION",
  "Type": "ADDRESS",
  "Score": 0.38217034935951233
}

Both of those are Safe Harbor identifiers. The model is confident about one of them. That score is the whole problem in miniature, because a model produces a probability and the standard demands a guarantee, so something in the pipeline has to bridge the gap. In practice that means running the threshold lower than feels comfortable so that you over-redact, sending everything in the uncertain band to human review rather than dropping it, and recording each redaction decision so you can show afterward what came out and why. Erring toward over-redaction costs you some analytic value. Erring the other way costs you a breach notification.

I should say plainly that I have a stake in this, since it’s what I’ve spent most of this year on. Philter is what I’ve been building for it, sitting on top of the Phileas redaction engine, and the reason both of them run inside your own network rather than as a hosted API is that a lot of the healthcare teams I talk to cannot send clinical text to a third party under any reading of their own policies. There’s more about the tooling over at Philterd than belongs in a post about pipelines.

The surrounding design barely changes regardless of what does the detecting. Detection hands you probabilities, the standard wants certainty, and the review queue is where those two meet.

What you can add later, and what you can’t

A lot of this is retrofittable. Bucket policies, IAM, encryption on new objects and network controls can all be tightened later, with effort but without much drama.

Logging isn’t. You cannot go back and log. If somebody asks in 2026 what was read out of a PHI bucket during 2024 and data events were switched off, there is no answer available at any price. Account separation is nearly as bad, being trivial on day one and a migration project by day four hundred. The same goes for the classification tags that decide which zone a document lands in, which cost nothing to apply at write time and are miserable to infer later across a few hundred million objects.

If you only get one thing right, make it the boundary in the diagram. Decide exactly what is allowed to cross from the PHI zone into the de-identified one, make that the only route across, and log the crossing. Most of the other controls in this post exist either to enforce that boundary or to prove afterward that it held.

If you’re building something like this, or you have one already and want an honest read on whether it would hold up, please reach out.