Skip to content

SC-034: Cloud Storage Misconfiguration — Operation Open Vault

Scenario Header

Type: Cloud / Healthcare  |  Difficulty: ★★★☆☆  |  Duration: 3–4 hours  |  Participants: 4–8

Threat Actor: DATA HARVEST — cybercrime group specializing in healthcare data theft and extortion

Primary ATT&CK Techniques: T1530 · T1580 · T1526 · T1567.002 · T1486 · T1078.004

Facilitator Note

This scenario simulates a cloud storage misconfiguration leading to PHI exfiltration and extortion. Participants should include cloud security engineers, SOC analysts, HIPAA privacy officers, and incident responders. The scenario demonstrates how a single S3 bucket policy misconfiguration creates a direct path from public internet to protected health information — no authentication bypass, no exploit, no vulnerability — just a misconfiguration. All data is synthetic. All organizations, IPs, and indicators are fictional.


Threat Actor Profile

DATA HARVEST is a financially motivated cybercrime group active since 2024, specializing in discovering and exploiting misconfigured cloud storage across healthcare, insurance, and financial services sectors. Unlike sophisticated APT groups, DATA HARVEST requires minimal technical skill — their tradecraft relies on automated scanning for publicly accessible cloud storage buckets, blob containers, and GCS buckets. Once they identify exposed sensitive data, they download it, verify its value, and issue extortion demands threatening public disclosure.

The group operates a semi-automated pipeline: custom scripts enumerate S3 buckets using common naming patterns (company name + keywords like backup, data, phi, patient, records), test for public read access via unauthenticated ListBucket and GetObject calls, and flag buckets containing file types associated with medical records (.hl7, .dcm, .pdf, .csv, .xlsx). Average time from discovery to extortion demand: 72 hours. Average ransom demand: $250K–$2M (scaled to estimated record count and organization revenue).

Motivation: Financial — extortion based on threat of public disclosure of PHI. Secondary revenue: sale of PHI records on dark web markets ($50–$250 per record). DATA HARVEST has compromised 14 healthcare organizations in the past 18 months, with a 60% payment rate on extortion demands.


Scenario Narrative

Scenario Context

HealthBridge Medical Systems is a regional health information exchange (HIE) serving 42 hospitals, 380 clinics, and 1,200 independent practices across the southeastern United States. HealthBridge aggregates, normalizes, and distributes patient health records between participating healthcare organizations via HL7 FHIR APIs and batch file transfers. Their AWS infrastructure includes 47 S3 buckets supporting data ingestion, processing, analytics, and archival. The environment processes approximately 2.1 million patient records representing 8.4 million residents. A cloud migration project 6 months ago moved batch processing data from on-premises SFTP to S3 — during the migration, a developer created a bucket (healthbridge-data-exchange-prod) with a public access policy to troubleshoot a partner connectivity issue. The "temporary" public access was never reverted. HealthBridge's cloud security team (2 FTEs) relies on quarterly AWS Security Hub reviews — they do not have real-time bucket policy monitoring or automated guardrails (SCPs) preventing public bucket creation.


Phase 1 — Reconnaissance and Bucket Discovery (~30 min)

DATA HARVEST begins with passive reconnaissance against HealthBridge Medical Systems. The attacker identifies HealthBridge's AWS presence through DNS records (CNAME entries pointing to s3.amazonaws.com), SSL certificate transparency logs (certificates issued to *.healthbridge-med.example.com), and public job postings mentioning AWS, S3, and healthcare data engineering roles.

Using this intelligence, the attacker generates a list of candidate bucket names following common patterns: healthbridge-, hb-, healthbridge-med-, combined with keywords (data, exchange, backup, archive, patient, phi, prod, staging, dev). The attacker's automated tool tests each candidate for public access by issuing unauthenticated ListBucket (GET) requests.

Evidence Artifacts:

Artifact Detail
DNS Recon (attacker) data.healthbridge-med.example.com — CNAME → healthbridge-data-exchange-prod.s3.us-east-1.amazonaws.com — Public DNS resolution confirms bucket exists
S3 Server Access Log Bucket: healthbridge-data-exchange-prod — Operation: REST.GET.BUCKET (ListBucket) — Requester: - (unauthenticated) — Source IP: 203.0.113.88 — HTTP 200 OK — 2026-03-05T04:12:33Z
S3 Server Access Log Bucket: healthbridge-data-backup — Operation: REST.GET.BUCKET — Requester: - — Source IP: 203.0.113.88 — HTTP 403 AccessDenied — 2026-03-05T04:12:34Z
S3 Server Access Log Bucket: healthbridge-patient-records — Operation: REST.GET.BUCKET — Requester: - — Source IP: 203.0.113.88 — HTTP 404 NoSuchBucket — 2026-03-05T04:12:35Z
S3 Bucket Policy Bucket: healthbridge-data-exchange-prod — Policy: {"Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject", "s3:ListBucket"], "Resource": ["arn:aws:s3:::healthbridge-data-exchange-prod", "arn:aws:s3:::healthbridge-data-exchange-prod/*"]} — Block Public Access: Disabled
CloudTrail No CloudTrail entry — unauthenticated S3 requests are not logged in CloudTrail (only in S3 Server Access Logs)
Phase 1 — Discussion Inject

Technical: Unauthenticated S3 requests do not appear in CloudTrail — only in S3 Server Access Logs. Many organizations enable CloudTrail but not S3 Server Access Logging. How would you ensure visibility into unauthenticated access attempts? Consider: S3 Server Access Logs vs CloudTrail Data Events, cost implications of logging at scale (2.1M records), and automated analysis of access patterns.

Decision: You discover that S3 Server Access Logging is only enabled on 12 of 47 buckets. Enabling it on all buckets will generate approximately 50 GB/day of logs at a storage cost of ~$1,150/month. The CISO asks whether this cost is justified. How do you make the business case for comprehensive S3 access logging in a healthcare environment subject to HIPAA?

Expected Analyst Actions: - [ ] Audit all S3 buckets for public access policies using aws s3api get-bucket-policy-status - [ ] Check S3 Block Public Access settings at account and bucket level - [ ] Review S3 Server Access Logs for unauthenticated (-) requests - [ ] Identify the bucket policy change that enabled public access — trace to developer/change ticket - [ ] Verify S3 Server Access Logging is enabled on all buckets containing PHI


Phase 2 — Data Enumeration and Download (~40 min)

Having confirmed public read access to healthbridge-data-exchange-prod, DATA HARVEST begins systematic enumeration and download of bucket contents. The bucket contains 847,000 objects totaling 1.2 TB, organized in a date-partitioned prefix structure:

healthbridge-data-exchange-prod/
├── inbound/
│   ├── 2026-03-04/
│   │   ├── mercy-general/    (HL7 FHIR bundles from Mercy General Hospital)
│   │   ├── st-luke-medical/  (patient records from St. Luke Medical Center)
│   │   └── coastal-health/   (lab results from Coastal Health Network)
│   └── 2026-03-03/
├── processed/
│   ├── normalized/           (standardized patient demographics)
│   └── enriched/             (records with insurance + billing data)
├── analytics/
│   ├── population-health/    (aggregated health metrics — de-identified)
│   └── quality-measures/     (HEDIS quality reporting data)
└── archive/
    └── 2025-Q4/              (quarterly archive — 340,000 records)

The attacker targets the inbound/ and processed/ prefixes, which contain identifiable PHI including patient names, dates of birth, Social Security numbers, diagnoses (ICD-10 codes), medications, lab results, and insurance information. The download is conducted using aws s3 sync with the --no-sign-request flag over a 6-hour period from a VPS at 203.0.113.88.

Evidence Artifacts:

Artifact Detail
S3 Server Access Log Operation: REST.GET.OBJECT — Requester: - — Source IP: 203.0.113.88 — Key: inbound/2026-03-04/mercy-general/patient_bundle_20260304_001.json — HTTP 200 — Bytes Sent: 2,847,392 — 2026-03-05T04:18:22Z
S3 Server Access Log Operation: REST.GET.OBJECT — Source IP: 203.0.113.88 — 142,000 GetObject requests over 6 hours — Total bytes: 187 GB — 2026-03-05T04:18:22Z–10:32:41Z
S3 Server Access Log Operation: REST.GET.BUCKET — Source IP: 203.0.113.88prefix=processed/normalized/ — 3,400 ListBucket requests — 2026-03-05T05:02:00Z
Sample PHI Record (Synthetic) {"patient_id": "HB-2026-847291", "name": "Jane Doe", "dob": "1985-07-14", "ssn": "XXX-XX-4872", "diagnosis": ["E11.9 - Type 2 DM", "I10 - HTN"], "medications": ["metformin 1000mg", "lisinopril 10mg"], "insurance": "BlueCross PPO #BC847291", "provider": "Mercy General Hospital"}
S3 Metrics (CloudWatch) Bucket: healthbridge-data-exchange-prodGetRequests: 142,000 (baseline: 2,400/day) — BytesDownloaded: 187 GB (baseline: 12 GB/day) — Spike: 59x normal request volume — No alarm configured
User Agent String S3 Server Access Log — User-Agent: aws-cli/2.15.0 Python/3.11.6 Linux/5.15.0-91-generic — Consistent across all 142,000 requests — Indicates automated scripted download
KQL — S3 Anonymous Access Volume Anomaly
let baseline_window = 14d;
let threshold_multiplier = 5;
AWSS3AccessLogs
| where Requester == "-"  // Anonymous
| where HTTPStatus == 200
| summarize RequestCount = count(), BytesTotal = sum(BytesSent)
    by bin(TimeGenerated, 1h), BucketName, RemoteIP
| join kind=inner (
    AWSS3AccessLogs
    | where TimeGenerated between (ago(baseline_window) .. ago(1d))
    | where Requester == "-"
    | summarize BaselineHourlyAvg = count() / (toint(baseline_window / 1h))
        by BucketName
) on BucketName
| where RequestCount > BaselineHourlyAvg * threshold_multiplier
| project TimeGenerated, BucketName, RemoteIP, RequestCount, BytesTotal, BaselineHourlyAvg
| sort by RequestCount desc
KQL — S3 Bulk Download Detection (PHI Buckets)
AWSS3AccessLogs
| where Operation == "REST.GET.OBJECT"
| where BucketName has_any ("phi", "patient", "health", "medical", "data-exchange")
| summarize
    ObjectCount = dcount(Key),
    TotalBytes = sum(BytesSent),
    FirstRequest = min(TimeGenerated),
    LastRequest = max(TimeGenerated),
    DurationMinutes = datetime_diff('minute', max(TimeGenerated), min(TimeGenerated))
    by RemoteIP, BucketName
| where ObjectCount > 1000 or TotalBytes > 1073741824  // >1000 objects or >1GB
| extend ThreatIndicator = case(
    ObjectCount > 10000, "Critical - Mass Exfiltration",
    ObjectCount > 1000, "High - Bulk Download",
    "Medium - Elevated Access"
)
| sort by ObjectCount desc
Phase 2 — Discussion Inject

Technical: CloudWatch metrics show a 59x spike in GetObject requests and 15x spike in bytes downloaded. However, no alarm was configured. What CloudWatch metric alarms and anomaly detection would you implement for S3 buckets containing PHI? Consider: NumberOfObjects, BucketSizeBytes, GetRequests, BytesDownloaded, and the cost of CloudWatch alarms at scale.

Decision: The 187 GB download represents approximately 142,000 patient records from 12 participating hospitals. Under HIPAA Breach Notification (45 CFR 164.408), HealthBridge must notify HHS within 60 days for breaches affecting 500+ individuals, and must notify affected individuals without unreasonable delay. The breach also triggers notification obligations to 12 participating hospital organizations under BAA terms. Legal counsel estimates notification costs at $8–12 per individual ($1.1M–$1.7M). Do you begin notification immediately or wait for forensic scope confirmation?

Expected Analyst Actions: - [ ] Analyze S3 Server Access Logs for all unauthenticated GetObject requests - [ ] Quantify data exposure: number of records, types of PHI, affected organizations - [ ] Immediately enable S3 Block Public Access on the affected bucket - [ ] Preserve S3 Server Access Logs (copy to separate, locked bucket) - [ ] Review CloudWatch metrics for anomalous access patterns on all PHI buckets - [ ] Engage HIPAA privacy officer for breach assessment


Phase 3 — Extortion Demand (~30 min)

Three days after completing the data download, DATA HARVEST sends an extortion email to HealthBridge's publicly listed CEO, CISO, and general counsel email addresses. The email is sent from a ProtonMail address and contains a sample of 50 patient records as proof of data possession.

Evidence Artifacts:

Artifact Detail
Extortion Email From: data.harvest.group@proton.example.com — To: ceo@healthbridge-med.example.com, ciso@healthbridge-med.example.com, legal@healthbridge-med.example.com — Subject: "URGENT: HealthBridge Patient Data Breach — 142,000 Records" — 2026-03-08T09:14:22Z
Email Body (Excerpt) "We have downloaded 142,000 patient records from your S3 bucket healthbridge-data-exchange-prod. Records include SSNs, diagnoses, medications, and insurance data from 12 hospitals. We will publish this data on our leak site in 7 days unless payment of $1.5M in Monero is received. Attached: 50 sample records as proof. Payment address: [REDACTED]. Do not contact law enforcement — we will know and publish immediately."
Attached Sample 50 patient records matching format and content from healthbridge-data-exchange-prod/processed/normalized/ — Records verified as authentic (matching internal database)
Dark Web Leak Site data-harvest[.]onion — "Upcoming Releases" page lists "HealthBridge Medical Systems — 142K patient records — 7 days" — Screenshot captured 2026-03-08T14:00:00Z
Sample Extortion Proof Record (Synthetic)
{
  "record_number": 12,
  "patient_id": "HB-2026-291847",
  "full_name": "Robert J. Thompson",
  "date_of_birth": "1972-03-22",
  "ssn": "XXX-XX-8841",
  "address": "1247 Oak Street, Synthetic City, SC 29401",
  "diagnosis_codes": ["I25.10 - CAD", "E78.5 - Hyperlipidemia", "G47.33 - OSA"],
  "medications": ["atorvastatin 40mg", "aspirin 81mg", "CPAP therapy"],
  "lab_results": {
    "HbA1c": "5.8%",
    "LDL": "142 mg/dL",
    "eGFR": "78 mL/min"
  },
  "insurance": {
    "carrier": "Aetna PPO",
    "member_id": "AET-8841-2026",
    "group": "MERCY-EMP-500"
  },
  "source_facility": "Mercy General Hospital",
  "last_visit": "2026-02-28"
}
Phase 3 — Discussion Inject

Technical: The extortion email includes 50 authentic patient records. How do you verify the scope of the breach without relying on the attacker's claims? Consider: correlating S3 access logs with actual downloaded objects, verifying the sample records against the production database, and determining whether the attacker accessed only the listed prefixes or performed broader enumeration.

Decision: The attacker demands $1.5M in Monero with a 7-day deadline. FBI Cyber Division recommends against payment, noting that DATA HARVEST has published data even after receiving payment in 2 of 14 previous cases (14% double-extortion rate). Insurance covers up to $5M in cyber extortion payments. Do you (A) pay to attempt to prevent public disclosure, (B) refuse to pay and begin breach notification immediately, or (C) engage a negotiator to extend the deadline while you complete forensic analysis and begin notification? What are the ethical, legal, and regulatory considerations?

Expected Analyst Actions: - [ ] Preserve the extortion email and attachments as evidence - [ ] Verify sample records against production database to confirm authenticity - [ ] Engage FBI Cyber Division (IC3 reporting) - [ ] Engage cyber insurance carrier — activate extortion response coverage - [ ] Begin HIPAA breach risk assessment per 45 CFR 164.402 - [ ] Notify participating hospital organizations per BAA breach notification terms


Phase 4 — Forensic Analysis and Remediation (~40 min)

HealthBridge's incident response team, augmented by a forensic firm, conducts a comprehensive investigation to determine the full scope of the breach and implement remediation.

Forensic Findings:

Finding Detail
Root Cause Developer dev-jpark modified bucket policy on 2025-09-12T16:42:00Z to add Principal: * for troubleshooting partner connectivity issue — change ticket CHG-4821 marked "temporary" — never reverted
Exposure Window 175 days (2025-09-12 to 2026-03-05) — bucket publicly readable for nearly 6 months
Access Analysis 203.0.113.88 (DATA HARVEST): 142,000 objects, 187 GB — 203.0.113.201 (unknown): 847 objects, 1.1 GB on 2025-12-14 (possible earlier compromise) — 203.0.113.55 (unknown): 23 ListBucket requests on 2025-11-03 (enumeration only, no downloads)
Data Scope 142,000 unique patient records — PHI fields: name, DOB, SSN, diagnoses, medications, lab results, insurance — 12 participating hospitals affected — Records span 2025-Q3 through 2026-Q1
AWS Config S3 Block Public Access: disabled at account level (no SCP guardrail) — AWS Config rule s3-bucket-public-read-prohibited: not deployed — Security Hub: enabled but findings reviewed quarterly
CloudTrail Gap CloudTrail S3 Data Events not enabled — only Management Events logged — unauthenticated access invisible in CloudTrail
KQL — S3 Unauthenticated Access Detection (via Log Ingestion)
AWSCloudTrail
| where EventName in ("GetObject", "ListBucket", "ListObjects")
| where UserIdentityType == "Anonymous" or isempty(UserIdentityAccountId)
| summarize RequestCount = count(), TotalBytes = sum(BytesSent),
    DistinctObjects = dcount(RequestParameters_key)
    by bin(TimeGenerated, 1h), SourceIpAddress, BucketName
| where RequestCount > 100 or TotalBytes > 1073741824  // >1GB
| sort by RequestCount desc
KQL — S3 Bucket Policy Changes
AWSCloudTrail
| where EventName in ("PutBucketPolicy", "DeleteBucketPublicAccessBlock", "PutBucketAcl")
| extend PolicyDocument = tostring(parse_json(RequestParameters).bucketPolicy)
| where PolicyDocument contains "\"Principal\":\"*\"" or PolicyDocument contains "\"Principal\":{\"AWS\":\"*\"}"
| project TimeGenerated, UserIdentityArn, BucketName, EventName, PolicyDocument, SourceIpAddress
Phase 4 — Discussion Inject

Technical: The forensic analysis reveals two additional unknown IPs accessed the bucket before DATA HARVEST. How would you investigate whether these represent additional threat actors, automated scanners, or legitimate access? Consider: IP reputation, access patterns (ListBucket-only vs GetObject), user agent strings, and geographic origin.

Decision: The root cause is a developer's "temporary" bucket policy change that was never reverted — 175 days of exposure. Your change management process requires approval for production changes but does not have automated enforcement. Do you (A) implement SCPs preventing public S3 bucket creation organization-wide (may break legitimate public-facing buckets for marketing/static websites), (B) deploy AWS Config remediation rules that automatically revert public bucket policies, or (C) implement both with exception management via tagging? Consider the balance between security automation and operational flexibility.

Expected Analyst Actions: - [ ] Reconstruct complete access timeline from S3 Server Access Logs - [ ] Investigate the two additional unknown source IPs for scope expansion - [ ] Trace the root cause bucket policy change to CHG-4821 and developer dev-jpark - [ ] Implement S3 Block Public Access at the AWS Organization level via SCP - [ ] Deploy AWS Config rule s3-bucket-public-read-prohibited with auto-remediation - [ ] Enable CloudTrail S3 Data Events for all PHI-containing buckets - [ ] Enable S3 Server Access Logging on all 47 buckets


Detection & Response

Key Detection Opportunities

Detection Point Event / Data Source Query Logic
Public bucket creation/modification CloudTrail PutBucketPolicy Policy contains Principal: *
Unauthenticated S3 access S3 Server Access Logs Requester = - (anonymous)
Anomalous download volume CloudWatch S3 Metrics GetRequests or BytesDownloaded > 5x baseline
Block Public Access disabled AWS Config s3-account-level-public-access-blocks non-compliant
Bucket policy allows public read AWS Config s3-bucket-public-read-prohibited non-compliant
Data exfiltration volume VPC Flow Logs / S3 Metrics Outbound data transfer > threshold

Immediate Containment (0–4 hours)

  • [ ] Enable S3 Block Public Access on healthbridge-data-exchange-prod immediately
  • [ ] Enable S3 Block Public Access at the AWS account level
  • [ ] Preserve all S3 Server Access Logs (copy to immutable archive bucket with Object Lock)
  • [ ] Rotate all IAM credentials for the affected AWS account
  • [ ] Review and restrict all bucket policies across 47 buckets
  • [ ] Enable CloudTrail S3 Data Events for all PHI buckets

Short-Term Actions (24–72 hours)

  • [ ] Deploy AWS Config rules: s3-bucket-public-read-prohibited, s3-account-level-public-access-blocks
  • [ ] Implement SCP preventing s3:PutBucketPolicy with Principal: * at the Organization level
  • [ ] Enable S3 Server Access Logging on all 47 buckets
  • [ ] Deploy CloudWatch alarms for anomalous S3 access patterns
  • [ ] Begin HIPAA breach notification process (HHS, affected individuals, media if >500)
  • [ ] Notify all 12 participating hospital organizations per BAA terms
  • [ ] Engage FBI IC3 for extortion reporting

Long-Term Actions (1–12 weeks)

  • [ ] Implement AWS Organizations SCP preventing public bucket creation without exception tag
  • [ ] Deploy automated bucket policy scanning and remediation (AWS Config + Lambda)
  • [ ] Implement S3 Object Lock for all PHI data (WORM compliance)
  • [ ] Deploy AWS Macie for automated PHI discovery and classification across all S3 buckets
  • [ ] Implement data-at-rest encryption with customer-managed KMS keys for all PHI buckets
  • [ ] Redesign data exchange architecture: replace public S3 with signed URLs or AWS Transfer Family
  • [ ] Implement change management automation: infrastructure-as-code with policy validation
  • [ ] Conduct HHS OCR breach investigation preparation

Lessons Learned

What Went Wrong

Gap Detail Remediation
"Temporary" public bucket policy never reverted Developer created public access for troubleshooting — 175-day exposure window Implement IaC with policy validation; automated expiration for temporary exceptions
No S3 Block Public Access at account level Account-level guardrail not enabled — any developer could create public buckets Enable S3 BPA at Organization level via SCP
No real-time bucket policy monitoring AWS Config not deployed; Security Hub reviewed quarterly Deploy AWS Config with auto-remediation; real-time alerting on policy changes
CloudTrail S3 Data Events not enabled Unauthenticated access invisible in CloudTrail — only in S3 access logs Enable CloudTrail Data Events for all PHI buckets
S3 Server Access Logging incomplete Only 12 of 47 buckets had access logging — forensic gaps Enable access logging on all buckets; centralize to security account
No CloudWatch alarms for access anomalies 59x spike in requests went undetected Deploy anomaly detection alarms for request count and bytes transferred
No automated PHI discovery PHI stored in S3 without classification or DLP scanning Deploy AWS Macie for continuous PHI discovery and classification
Change management gap Ticket CHG-4821 marked "temporary" with no revert deadline or automated enforcement Implement automated change expiration; require revert tickets for temporary changes

What Went Right

Control Impact
S3 Server Access Logging (partial) Enabled on affected bucket — provided complete forensic record of attacker access
CloudWatch S3 Metrics (passive) Metrics were collected (even without alarms) — enabled retrospective anomaly analysis
HIPAA BAA framework Clear notification obligations accelerated partner communication
Cyber insurance coverage Extortion response and breach notification costs covered under policy

ATT&CK Navigator Mapping

Technique ID Technique Name Phase
T1595.003 Active Scanning: Wordlist Scanning Reconnaissance
T1530 Data from Cloud Storage Collection
T1580 Cloud Infrastructure Discovery Discovery
T1526 Cloud Service Discovery Discovery
T1567.002 Exfiltration Over Web Service: Exfiltration to Cloud Storage Exfiltration
T1078.004 Valid Accounts: Cloud Accounts Initial Access
T1537 Transfer Data to Cloud Account Exfiltration


Scenario Debrief

Operation Open Vault demonstrates the devastating simplicity of cloud storage misconfiguration — no exploit, no zero-day, no authentication bypass. A single bucket policy allowing Principal: * read access, created as a "temporary" troubleshooting measure and never reverted, exposed 142,000 patient records containing SSNs, diagnoses, medications, and insurance data for 175 days. The attack required no technical sophistication — only automated bucket name guessing and unauthenticated HTTP GET requests. The most critical lesson: unauthenticated S3 access is invisible in CloudTrail — organizations that rely solely on CloudTrail for cloud monitoring have a fundamental visibility gap. Defense requires defense-in-depth: S3 Block Public Access at the Organization level (SCP), AWS Config rules with auto-remediation, S3 Server Access Logging on all buckets, CloudWatch anomaly detection, and continuous PHI discovery with AWS Macie. The $1.5M extortion demand pales in comparison to the estimated $12M+ in total breach costs (notification, regulatory fines, litigation, and reputational damage).