Originally published on AWS Builder Center.
A hands-on look at finding sensitive data in S3 before it becomes a breach. Then I built a five-layer safety net around it and tried to break it. 100% fake data.
Why I tried this
2026 has been another heavy year for US health data. Two of the biggest incidents:
- DentaQuest (dental and vision benefits) — about 15 million people notified after a data-theft attack in May 2026. No malware, no encrypted systems: the attackers simply took data — names, Social Security numbers, Medicaid and Medicare numbers, and treatment details.
- Aesto Health (a vendor that migrates and archives health records for providers) — about 9.5 million people, after attackers accessed part of its AWS environment in December 2025. It took around five months to confirm that patient data was involved. How the attackers got in hasn't been made public — and to be clear, this was the customer's environment, not a breach of AWS itself.
By the end of July, more than 460 large breaches had been reported to HHS for 2026, affecting more than 58 million people. And the year wasn't over.
Every one of those incidents starts with the same question: where exactly was the patient data, and who could reach it? In a cloud account the honest answer is often "we're not sure" — a spreadsheet exported "just for a quick analysis", a backup copied to a new bucket, a log file nobody looked at. HIPAA calls that data PHI (protected health information). You can't protect what you don't know you have.
Amazon Macie's job is to tell you where it is. Many engineers here in Kathmandu build software for US companies, including healthcare. If we touch their data, we share the job of protecting it.
What Amazon Macie is, in one picture
Macie is a sniffer dog for S3. It looks inside your files for sensitive data and checks whether your buckets are public, encrypted or shared. Whatever it finds becomes a finding that you can send to a dashboard or an alert.
The test: four files, one bucket
I created a private bucket (Block Public Access on, default SSE-S3 encryption) and uploaded four files. Everything is synthetic — generated with a small Python script, no real people, no real cards, no real keys.
| Object | What's inside | Expected result |
|---|---|---|
exports/patients_export.csv | 20 fake patients: name, date of birth, SSN-format number, Medicare-format ID, address, phone, diagnosis | Flag it |
finance/billing_notes.txt | Public test card numbers (4111 1111 1111 1111) and an SSN-format number | Flag it |
app/deploy_config.env | An AWS-style access key and secret that never existed | Flag it |
public-site/cafe_menu.txt | A café menu (momo, masala chai) | Ignore it |
The generator, if you want to repeat this (simplified — my lab version also added fake addresses and 555 phone numbers):
import random, string random.seed(1112) first = ["Aarav","Maya","Liam","Sofia","Noah","Emma","Ethan","Olivia","Lucas","Ava", "Mason","Isla","Logan","Mia","Elijah","Zara","James","Leah","Henry","Nora"] last = ["Carter","Nguyen","Patel","Brooks","Rivera","Kim","Foster","Hayes","Morgan","Price", "Reed","Ward","Cole","Gray","Bell","Hart","Lane","West","Ross","Shaw"] dx = ["Type 2 diabetes (E11.9)","Hypertension (I10)","Asthma (J45.909)"] mbi_alpha = [c for c in string.ascii_uppercase if c not in "SLOIBZ"] def fake_mbi(): # Medicare Beneficiary Identifier *format*, random values n = lambda lo=0: str(random.randint(lo, 9)) a = lambda: random.choice(mbi_alpha) an = lambda: random.choice(mbi_alpha + list("0123456789")) return n(1)+a()+an()+n()+a()+an()+n()+a()+a()+n()+n() rows = ["patient_name,date_of_birth,ssn,medicare_beneficiary_id,diagnosis"] for i in range(20): ssn = f"{random.randint(100,599)}-{random.randint(10,99)}-{random.randint(1000,9999)}" rows.append(f"{first[i]} {last[i]},19{random.randint(40,99)}-01-15,{ssn},{fake_mbi()},{random.choice(dx)}") open("patients_export.csv", "w").write("\n".join(rows) + "\n") Running it
- Turn on Macie (Macie console → Get started → Enable Macie). Macie creates its own role so it can read your buckets. New accounts get a 30-day free trial.
- Create a job: Jobs → Create job → pick the bucket → One-time job → managed data identifiers: all → give it a name → Submit.
- Wait. My job took about 10 minutes for four tiny files. The first findings showed up after about 4 minutes. Most of that time is start-up, not scanning.
What Macie found
Three findings, all High severity — and nothing for the café menu:
| Object | Finding type | What Macie counted |
|---|---|---|
exports/patients_export.csv | SensitiveData:S3Object/Personal | 20 SSNs · 20 dates of birth · 20 addresses · 20 phone numbers · 16 names |
finance/billing_notes.txt | SensitiveData:S3Object/Personal | 1 SSN · 3 names |
app/deploy_config.env | SensitiveData:S3Object/Credentials | 1 AWS credential pair |
public-site/cafe_menu.txt | — | nothing (correct) |
So: 3 of 3 risky files caught, 0 false alarms. But look at what's missing:
- No Medicare IDs. Twenty Medicare-format identifiers sat in a column called
medicare_beneficiary_id, and none were reported. Macie does have health-related identifiers, but many of them need specific keywords near the value and validate the format; my randomly generated values or my column name evidently didn't qualify. Later, Comprehend Medical also missed a Medicare ID on a scanned form, even with the label "Medicare ID (MBI)" right next to it (see Test 1 below). If your data has Medicare IDs, add a custom data identifier for them. - No diagnoses. "Type 2 diabetes (E11.9)" next to a name is textbook PHI, but diagnosis text and ICD-10 codes weren't flagged — the finding says Personal, not health data.
- No card numbers. The two payment-card numbers (well-known public test numbers) weren't reported; the billing file was caught only because of the SSN. My guess is that Macie skips well-known test numbers on purpose, but I didn't test that.
- 4 of 20 names missed — name detection is probabilistic.
The lesson for anyone handling PHI: Macie's built-in identifiers are a strong starting point, not a complete PHI detector. Add custom data identifiers for the formats your organisation actually uses (medical record numbers, member IDs, ICD-10 codes) and test them with fake data — exactly like this.
What this looks like at a big company
My test had four files. Real health companies have millions of files and hundreds of AWS accounts. So here is a made-up example. The company is not real, but the mistakes are common ones.
AnyCompany Health is a US health insurer. It has 2 million members and 150 AWS accounts.
Without Macie:
- Day 1. An analyst copies 2 million claims records into a new bucket. The file has names, SSNs and diagnoses. She plans to delete it next week. She forgets.
- Day 40. An engineer turns off Block Public Access to share one file with a vendor.
- Day 41. A stranger finds the open bucket and downloads everything.
- Month 6. The data shows up for sale. Only now does the company find out.
With Macie:
- Day 1 or 2. Macie checks new buckets on its own. It finds SSNs and names in the new bucket. The bucket owner gets a ticket.
- Day 40. Macie sees that public access was turned off. EventBridge starts a Lambda function. The function turns Block Public Access back on, and the security team gets an alert.
- Day 41. The bucket is closed. There is nothing to download.
One security account runs Macie for all 150 accounts. The team sees every finding in one place.
Why this matters for HIPAA
HIPAA is the US law that protects health data. Here is how Macie helps:
| HIPAA asks you to… | How Macie helps |
|---|---|
| Know your risks. The Security Rule says you must do a risk analysis. | You can't check data you don't know about. Macie lists the buckets that hold PHI. |
| Protect the data. Limit who can see it. | Macie warns you when a bucket becomes public, unencrypted or shared with another account. |
| Prove it. Auditors want evidence. | Findings and job history show that you look for PHI on a schedule. |
And if a breach does happen, the clock starts. You must tell each person within 60 days of finding the breach. If 500 or more people are affected, you must also tell HHS. Your company's name then goes on HHS's public breach list.
A clear warning: Macie would not stop the download by itself. It finds problems fast, so they don't sit unnoticed for months. The real lock is to turn on Block Public Access for the whole account. Then add a rule (an SCP) so no one can turn it off. Macie is the safety net under that lock.
What Macie does not do
- It only looks at S3. For patient data in logs, use CloudWatch Logs data protection to mask it. Databases need their own controls.
- It can't read images. Scans, photos and X-rays are skipped. More on that below.
- It doesn't make you HIPAA compliant. You also need the AWS BAA (signed in AWS Artifact), and you must keep PHI in HIPAA-eligible services only. Add encryption, tight access and audit logs. Then come the people and paperwork: risk checks, policies and training. Macie is one tool in that picture.
- Findings don't fix anything. Connect them to an alert or an automatic fix.
Then I built the whole safety net and tried to break it
Macie alone leaves gaps. So I built all five layers in my own AWS account, again with fake data only. It took about two hours.
What I turned on
| Layer | What I built |
|---|---|
| ① Prevent | Block Public Access for the whole account. A KMS key for the bucket. A bucket policy that refuses unencrypted connections. |
| ② Discover | Macie, plus a small Lambda function. It sends images to Textract and all text to Comprehend Medical. |
| ③ Detect | CloudTrail (including who opened which file), GuardDuty, and AWS Config with AWS's HIPAA rule pack (130 rules). |
| ④ Respond | Security Hub collects every finding. EventBridge copies High findings to an alert log. A second Lambda switches Block Public Access back on if anyone turns it off. |
| ⑤ Prove | CloudTrail logs go to a bucket with Object Lock, so nobody can edit them. The HIPAA rule pack gives a score. |
This time I added two harder files: a doctor's note in plain text, and a fake scanned referral form saved as an image.
Test 1: Can it find PHI in every format?
| File | Macie | Textract + Comprehend Medical |
|---|---|---|
patients_export.csv | ✅ High: 20 SSNs, 20 dates of birth, 20 phones, 19 addresses, 15 names | not needed |
clinic-note.txt | ⚠️ Medium: 2 names, 1 date of birth. Missed the diagnosis and the medicine. | ✅ Names, date, record number, the diagnosis and the medicine |
referral-scan.png | ❌ Skipped. Macie can't read images. | ✅ Name, date of birth, SSN, address, phone and doctor. It coded the diagnosis as E11.9 and found metformin. |
cafe_menu.txt | ✅ Nothing, which is correct | not scanned |
Two things stood out. Comprehend Medical understood that "No chest pain" means the patient does not have chest pain. But neither tool caught the Medicare ID on the scan, and Comprehend Medical called the clinic's name an address. So test with your own real formats, and add custom patterns where the tools fall short.
Test 2: Can someone leak the bucket?
I played the careless engineer.
| What I tried | What happened |
|---|---|
| Add a bucket policy that makes every file public | Refused. Account-wide Block Public Access stopped it. |
| Turn off Block Public Access on the bucket | Switched back on after about 9 seconds by the auto-fix Lambda |
| Turn off Block Public Access for the whole account | The Lambda switched it back on after about 8 seconds. AWS then took about 2 minutes to apply it everywhere. |
GuardDuty and Macie also noticed the change on their own. Everything landed in one list:
Test 3: The HIPAA rule pack score
AWS Config checked the account against 130 HIPAA-related rules:
- 17 passed
- 18 failed
- 95 had nothing to check (I had no databases or servers)
The failures were the most useful part. Some were lab shortcuts, like logs encrypted with S3 keys instead of KMS. Others were real gaps in my own account: no MFA on my IAM user, old access keys, and no password policy. The same day, I set a password policy and removed my unused access keys. MFA for my admin user is next. That is what this layer is for. It finds the boring gaps that people forget.
The one rule I couldn't test
The strongest lock is a service control policy (SCP). It is a rule set in AWS Organizations that no one in an account can switch off, not even an admin. This one stops anyone from turning off Block Public Access or the logs:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "NobodyTurnsOffBlockPublicAccess", "Effect": "Deny", "Action": ["s3:PutAccountPublicAccessBlock", "s3:PutBucketPublicAccessBlock", "s3:DeleteBucketPublicAccessBlock"], "Resource": "*", "Condition": { "ArnNotLike": { "aws:PrincipalArn": "arn:aws:iam::*:role/SecurityBreakGlass" } } }, { "Sid": "NobodyStopsTheAuditTrail", "Effect": "Deny", "Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail", "guardduty:DeleteDetector", "macie2:DisableMacie", "config:StopConfigurationRecorder", "securityhub:DisableSecurityHub"], "Resource": "*" } ] } I did not test it live. SCPs don't apply to an organization's main (management) account, and that is the only account I have. With an SCP in place, my "careless engineer" test would have failed at step one.
What this test run does not prove
- It is not HIPAA compliance. HIPAA also needs a signed BAA, a risk analysis, written policies, staff training and more. These tools cover the technical part.
- I didn't sign the BAA in AWS Artifact, because there was no real patient data.
- One account is not 150. In a real company, one security account runs these tools for every account.
Still to try: a custom data identifier for Medicare IDs, and Macie's automated discovery.
Cost and clean-up
The first Macie test cost $0.00: one-time jobs include 1 GB of scanned data free each month, and these files were a few kilobytes.
The full five-layer test run cost about USD 0.19, by AWS's estimate the next day. GuardDuty and Security Hub were on their 30-day free trials. AWS Config was the biggest cost, because it charges for every rule check.
To clean up, I deleted the buckets, Lambda functions, rules and trail, and turned off Macie, GuardDuty, Security Hub and Config. I left account-wide Block Public Access on. It costs nothing and should stay on.
नेपालीमा छोटकरीमा
मैले S3 मा नक्कली बिरामीको विवरण, नक्कली कार्ड नम्बर र नक्कली AWS key राखेर Amazon Macie ले के भेट्टाउँछ भनेर परीक्षण गरेँ। Macie ले जोखिमपूर्ण तीनवटै फाइल भेट्टायो र क्याफेको मेनुलाई छोड्यो — तर Medicare ID र रोगको विवरण (diagnosis) भने छुटायो। त्यसैले आफ्नो संस्थाको ढाँचा अनुसार custom identifier थप्नुपर्छ। स्वास्थ्य वा व्यक्तिगत डेटा कहाँ छ भन्ने थाहा नभए सुरक्षित गर्न सकिँदैन — Macie ले त्यो पत्ता लगाउन मद्दत गर्छ। खर्च करिब शून्य।
त्यसपछि मैले आफ्नै AWS खातामा पाँच तहको सुरक्षा प्रणाली बनाएर परीक्षण गरेँ। स्क्यान गरिएको फारम (तस्बिर) Macie ले पढ्न सकेन, तर Textract र Comprehend Medical ले बिरामीको नाम, जन्म मिति र रोग पत्ता लगाए। बकेट सार्वजनिक बनाउन खोज्दा AWS ले रोक्यो, र सुरक्षा सेटिङ बन्द गर्दा Lambda ले करिब ९ सेकेन्डमै फेरि अन गर्यो। तर याद गर्नुहोस्: यी उपकरणले मात्र HIPAA पालना हुँदैन।
Sources
- DentaQuest: BankInfoSecurity, HealthExec
- Aesto Health: SecurityWeek, BleepingComputer
- 2026 totals: HIPAA Journal's counts of the HHS OCR breach portal — May 2026 report, June 2026 report, July 2026 report
- Macie: Amazon Macie User Guide, managed data identifiers
- HIPAA on AWS: AWS HIPAA compliance, HIPAA Eligible Services Reference
- What Macie can and can't read: Supported storage classes and formats
- Amazon Textract, Amazon Comprehend Medical
- AWS Config: Operational Best Practices for HIPAA Security, Service control policies
Not legal or compliance advice — check HIPAA obligations with your compliance team. I write about securing AI agents and sensitive data on AWS from Kathmandu.






