HIPAA Compliance Checklist for Healthcare SaaS on AWS and GCP (2026)
The 7 cloud misconfigurations that fail OCR HIPAA audits — with detection commands for AWS and GCP and a complete technical safeguards checklist for 2026.
- What HIPAA Actually Requires From Cloud Infrastructure
- #1 Unencrypted ePHI Storage — The Most Common OCR Finding
- #2 Missing or Misconfigured Audit Logging — Your Access Control Gap
- #3 IAM Overpermission — Minimum Necessary Access Failures
- #4 Unprotected Data in Transit — TLS and API Security Gaps
- #5 No Automatic Logoff and Session Controls
- #6 Encryption Key Management Failures — KMS / Cloud KMS
- #7 Missing Breach Detection and Incident Response Controls
- Complete HIPAA Technical Safeguards → AWS/GCP Control Mapping
- Your 30-Day HIPAA Remediation Timeline
HIPAA enforcement is accelerating. OCR collected over $140M in settlements and civil penalties from 2022–2025. The largest fines aren't hitting hospitals — they're hitting the healthcare SaaS companies that store, process, or transmit ePHI on their behalf. If you've signed a Business Associate Agreement, you own the same HIPAA liability as your covered entity customers. Here's what your AWS and GCP infrastructure needs to stay compliant.
What HIPAA Actually Requires From Cloud Infrastructure
HIPAA's Security Rule divides requirements into three categories: Administrative Safeguards (§164.308), Physical Safeguards (§164.310), and Technical Safeguards (§164.312). For cloud-hosted healthcare SaaS, the Technical Safeguards are where OCR finds violations — and where AWS and GCP misconfigurations directly create liability.
The four Technical Safeguard standards that map most directly to cloud infrastructure:
Access Controls (§164.312(a)(1))
Implement technical policies and procedures to allow only authorized users to access ePHI. On AWS and GCP, this means IAM policies, resource-level permissions, and identity-aware proxy configurations. "Authorized users only" is not achieved by security group rules alone.
Audit Controls (§164.312(b))
Implement hardware, software, and procedural mechanisms to record and examine activity in systems that contain ePHI. CloudTrail (AWS) and Cloud Audit Logs (GCP) are required — but they must be configured correctly. Disabled, misconfigured, or tampered logs are OCR findings.
Integrity (§164.312(c)(1))
Protect ePHI from improper alteration or destruction. This includes encryption at rest, object versioning on storage that holds ePHI, and tamper-resistant backup procedures.
Transmission Security (§164.312(e)(1))
Guard against unauthorized access to ePHI transmitted over a network. TLS 1.2 minimum everywhere. Unencrypted API calls to ePHI-bearing endpoints are per-incident HIPAA violations.
One important distinction: HIPAA uses "required" and "addressable" specifications. "Addressable" doesn't mean optional — it means you must implement the safeguard or document a specific risk-based rationale for an equivalent alternative. OCR has made clear in enforcement actions that treating addressable as optional is itself a violation.
#1 Unencrypted ePHI Storage — The Most Common OCR Finding
OCR's Wall of Shame lists dozens of settlements where covered entities and business associates stored ePHI in unencrypted databases or cloud storage. The HIPAA Security Rule §164.312(a)(2)(iv) (addressable) and §164.312(e)(2)(ii) (addressable) both address encryption — and in enforcement, OCR has consistently found that not implementing encryption without documented alternatives constitutes willful neglect.
For healthcare SaaS on AWS: S3 buckets, RDS instances, DynamoDB tables, EBS volumes, and EFS file systems holding ePHI must be encrypted at rest with customer-managed keys. For GCP: Cloud Storage buckets, Cloud SQL instances, BigQuery datasets, and Persistent Disks must use CMEK (Customer-Managed Encryption Keys) — the default Google-managed encryption is insufficient for documented HIPAA key ownership requirements.
Detection — AWS CLI
Detection — AWS CLI
# Check S3 buckets for SSE configuration
aws s3api list-buckets --query 'Buckets[].Name' --output text | while read bucket; do
enc=$(aws s3api get-bucket-encryption --bucket "$bucket" 2>/dev/null | jq -r '.ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.SSEAlgorithm' 2>/dev/null || echo "NONE")
kms=$(aws s3api get-bucket-encryption --bucket "$bucket" 2>/dev/null | jq -r '.ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.KMSMasterKeyID' 2>/dev/null || echo "NO-CMK")
echo "$bucket: SSE=$enc | KMS=$kms"
done
# Find unencrypted RDS instances (ePHI databases)
aws rds describe-db-instances --query 'DBInstances[*].[DBInstanceIdentifier,StorageEncrypted,KmsKeyId,Engine]' --output table | grep -v 'True'
# Find EBS volumes without encryption
aws ec2 describe-volumes --query 'Volumes[?Encrypted==`false`].[VolumeId,State,Size]' --output table
Detection — GCP CLI (gcloud)
Detection — GCP CLI (gcloud)
# Check Cloud Storage buckets for CMEK
gcloud storage buckets list --format="json" | jq -r '.[] | select(.encryption.defaultKmsKeyName == null) | .name' | xargs -I{} echo "NO CMEK: {}"
# Check Cloud SQL instances for disk encryption type
gcloud sql instances list --format="json" | jq -r '.[] | [.name, .diskEncryptionConfiguration.kmsKeyName // "GOOGLE-MANAGED"] | @tsv'
# Check BigQuery datasets for CMEK
bq ls --format=json | jq -r '.[] | .id' | while read dataset; do
enc=$(bq show --format=json "$dataset" 2>/dev/null | jq -r '.defaultEncryptionConfiguration.kmsKeyName // "GOOGLE-MANAGED"')
echo "$dataset: $enc"
done
How to fix it for HIPAA compliance
- For S3: Enable default SSE-KMS with a customer-managed KMS key. Never use SSE-S3 (AWS-managed) for ePHI — you need to prove key ownership to your BAA auditor:
aws s3api put-bucket-encryption --bucket <bucket> --server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"aws:kms","KMSMasterKeyID":"<cmk-arn>"},"BucketKeyEnabled":true}]}' - For unencrypted RDS: Create a new encrypted instance using a CMK and migrate using AWS DMS. You cannot enable encryption on an existing unencrypted RDS instance in place.
- Tag all ePHI data stores with
DataClassification=ePHI— this creates the documented inventory of ePHI locations required under HIPAA's risk analysis requirement (§164.308(a)(1)) - For GCP: Assign CMEK to Cloud Storage buckets at creation or via
gcloud storage buckets update --default-encryption-key=<key-name> gs://<bucket> - Enable S3 Object Lock (Compliance mode) or GCS Object Holds on ePHI backup buckets — immutable storage satisfies HIPAA's integrity requirements under §164.312(c)(2)
#2 Missing or Misconfigured Audit Logging — Your Access Control Gap
HIPAA §164.312(b) requires "hardware, software, and procedural mechanisms that record and examine activity in information systems that contain or use ePHI." OCR interprets this broadly: every read, write, and delete on ePHI must be auditable to a specific user. In cloud infrastructure, this means CloudTrail (AWS) and Cloud Audit Logs (GCP) must capture data-plane events — not just management-plane events — for all ePHI-touching services.
The audit logging gaps we see most often in healthcare SaaS: CloudTrail enabled only for management events (misses S3 GetObject/PutObject on ePHI), S3 server access logging disabled, Cloud Audit Logs DATA_READ events not enabled on Cloud Storage, and audit log retention below 6 years (HIPAA requires 6-year retention of security-related documentation per §164.316(b)(2)(i)).
Detection — AWS CLI
Detection — AWS CLI
# Check CloudTrail trails for data events configuration
aws cloudtrail get-event-selectors --trail-name <trail-arn> --query 'EventSelectors[*].[IncludeManagementEvents,DataResources]' --output json
# Verify S3 server access logging is enabled on ePHI buckets
aws s3api list-buckets --query 'Buckets[].Name' --output text | while read bucket; do
logging=$(aws s3api get-bucket-logging --bucket "$bucket" 2>/dev/null | jq -r '.LoggingEnabled.TargetBucket // "DISABLED"')
echo "$bucket: ACCESS_LOGGING=$logging"
done
# Check CloudTrail log file validation (tamper detection)
aws cloudtrail describe-trails --query 'Trails[*].[Name,LogFileValidationEnabled,IsMultiRegionTrail]' --output table
# Check CloudWatch log group retention for audit logs
aws logs describe-log-groups --query 'logGroups[*].[logGroupName,retentionInDays]' --output table | awk '$2 != "" && $2 < 2191' # 2191 days = 6 years
Detection — GCP CLI (gcloud)
Detection — GCP CLI (gcloud)
# Check which audit log types are enabled per service
gcloud projects get-iam-policy <project-id> --format=json | jq '.auditConfigs[] | {service: .service, logTypes: [.auditLogConfigs[].logType]}'
# Check for DATA_READ events on Cloud Storage (critical for ePHI access audit)
gcloud projects get-iam-policy <project-id> --format=json | jq '.auditConfigs[] | select(.service=="storage.googleapis.com")'
# Check Cloud Logging export sinks (is audit data being retained?)
gcloud logging sinks list --format=json | jq '.[] | {name: .name, destination: .destination, filter: .filter}'
How to fix it for HIPAA compliance
- Enable CloudTrail data events for S3 on all ePHI-tagged buckets — specifically
GetObject,PutObject, andDeleteObject. Without these, you have no per-user access audit for patient data. - Enable S3 server access logging separately — CloudTrail and S3 access logs are complementary, not duplicative. OCR examiners expect both.
- For GCP: Enable
DATA_READandDATA_WRITEaudit log types forstorage.googleapis.com,cloudsql.googleapis.com, and any other ePHI-touching service in your project IAM policy. - Set a 2,192-day (6-year) retention policy on all HIPAA audit log destinations — S3 lifecycle policies, CloudWatch log group retention, or GCS bucket lifecycle rules.
- Write audit logs to a separate security account (AWS) or separate project (GCP) that ePHI application workloads cannot write to — this prevents the risk of an application-layer breach also destroying its own audit trail.
- Enable CloudTrail Insights and set up anomaly alerting — HIPAA requires not just logging but the ability to detect unusual access patterns (§164.312(b) "examine activity").
#3 IAM Overpermission — Minimum Necessary Access Failures
HIPAA's minimum necessary standard (§164.502(b), implemented technically through §164.312(a)(1)) requires that users and systems only access the ePHI required to perform their specific function. On AWS and GCP, this translates directly to IAM policies: wildcard action grants, overpermissioned service accounts, and cross-service role assumptions that touch ePHI without business justification are HIPAA compliance failures.
What we find most often in healthcare SaaS: Lambda functions with broad S3 read permissions that include ePHI buckets outside their functional scope, GCP service accounts with project-level Editor roles that have implicit read access to all Cloud Storage, and IAM users with console access to ePHI databases for convenience debugging.
Detection — AWS CLI
Detection — AWS CLI
# Find IAM policies with wildcard access that could reach ePHI
aws iam list-policies --scope Local --query 'Policies[*].[PolicyName,Arn]' --output text | while read name arn; do
version=$(aws iam get-policy --policy-arn "$arn" --query 'Policy.DefaultVersionId' --output text)
doc=$(aws iam get-policy-version --policy-arn "$arn" --version-id "$version" --query 'PolicyVersion.Document' --output json)
if echo "$doc" | grep -qE '"Action".*"*"'; then
echo "WILDCARD ACTION: $name ($arn)"
fi
done
# Find IAM users with direct AWS console access (should be role-based for ePHI systems)
aws iam list-users --query 'Users[*].[UserName,CreateDate]' --output text | while read user date; do
keys=$(aws iam list-access-keys --user-name "$user" --query 'AccessKeyMetadata[*].[AccessKeyId,Status]' --output text)
if echo "$keys" | grep -q "Active"; then
echo "USER WITH ACTIVE KEY: $user (created $date)"
fi
done
# Find Lambda functions with S3 access across all buckets
aws lambda list-functions --query 'Functions[*].[FunctionName,Role]' --output text | while read fn role; do
rolename=$(echo $role | awk -F'/' '{print $NF}')
policy=$(aws iam list-role-policies --role-name "$rolename" --output text 2>/dev/null)
echo "Lambda: $fn | Role: $rolename | Inline policies: $policy"
done
Detection — GCP CLI (gcloud)
Detection — GCP CLI (gcloud)
# Find service accounts with Editor or Owner role (massive overpermission for ePHI)
gcloud projects get-iam-policy <project-id> --format=json | jq -r '.bindings[] | select(.role == "roles/editor" or .role == "roles/owner") | {role: .role, members: .members}'
# Find service accounts with Storage Object Admin (should be scoped to specific buckets)
gcloud projects get-iam-policy <project-id> --format=json | jq -r '.bindings[] | select(.role | startswith("roles/storage")) | {role: .role, members: .members}'
# List all service accounts and their key ages
gcloud iam service-accounts list --format=json | jq -r '.[].email' | while read sa; do
keys=$(gcloud iam service-accounts keys list --iam-account="$sa" --format=json 2>/dev/null)
echo "$sa: $(echo $keys | jq 'length') keys"
done
How to fix it for HIPAA compliance
- Scope all IAM policies to specific resource ARNs for ePHI data stores — never
Resource: "*"for any role that could touch patient data. Usearn:aws:s3:::phi-bucket-*or the specific GCS bucket name. - Remove direct IAM user access to ePHI systems — replace with role-based access through AWS SSO/IAM Identity Center or GCP Workload Identity. Human access to ePHI should be auditable to a specific person, with a specific session time.
- Enable AWS IAM Access Analyzer and GCP IAM Recommender for continuous minimum-necessary drift detection — these flag when permissions are granted but not used.
- Create a documented access control matrix: which IAM role/service account has access to which ePHI resource, who owns that role, and what business function justifies the access. This is the evidence your BAA auditor will request under §164.308(a)(4).
- Implement attribute-based access control (ABAC) using resource tags — roles that have
ConditionStringEquals: {"s3:ExistingObjectTag/DataClassification": "ePHI"}can only access tagged ePHI objects, not all S3 data. - Audit and remove all user access keys older than 90 days — HIPAA §164.312(a)(2)(iii) requires automatic logoff/termination of access when it's no longer needed.
#4 Unprotected Data in Transit — TLS and API Security Gaps
HIPAA §164.312(e)(1) requires that covered entities and BAs "implement technical security measures to guard against unauthorized access to ePHI that is being transmitted over an electronic communications network." In practice: every API call, database connection, and inter-service communication that carries ePHI must use TLS 1.2 minimum. Unencrypted HTTP endpoints, weak cipher configurations, and missing TLS enforcement on load balancers are direct HIPAA violations.
The most common gaps in healthcare SaaS APIs: application load balancers with TLS 1.0/1.1 listener policies still enabled (legacy defaults), RDS instances with SSL/TLS not enforced at the parameter group level, and GCP Cloud SQL instances with require_ssl set to false.
Detection — AWS CLI
Detection — AWS CLI
# Check ALB listener policies for deprecated TLS versions
aws elbv2 describe-listeners --query 'Listeners[*].[LoadBalancerArn,SslPolicy,Protocol,Port]' --output table
# Check RDS parameter groups for SSL enforcement
aws rds describe-db-instances --query 'DBInstances[*].[DBInstanceIdentifier,DBParameterGroups[0].DBParameterGroupName]' --output text | while read instance pg; do
ssl=$(aws rds describe-db-parameters --db-parameter-group-name "$pg" --query "Parameters[?ParameterName=='require_secure_transport'].ParameterValue" --output text 2>/dev/null || echo "N/A")
echo "$instance: require_secure_transport=$ssl"
done
# Check Security Groups for any unencrypted DB port exposure
aws ec2 describe-security-groups --query 'SecurityGroups[*].[GroupId,GroupName,IpPermissions[*].[FromPort,ToPort,IpRanges[*].CidrIp]]' --output json | jq '.[] | select(.[2][][0] == 5432 or .[2][][0] == 3306) | select(.[2][][2][] | contains("0.0.0.0/0") or contains("::/0"))'
Detection — GCP CLI (gcloud)
Detection — GCP CLI (gcloud)
# Check Cloud SQL instances for SSL enforcement
gcloud sql instances list --format=json | jq -r '.[] | {name: .name, requireSsl: .settings.ipConfiguration.requireSsl}'
# Check load balancer SSL policies
gcloud compute ssl-policies list --format=json | jq -r '.[] | {name: .name, profile: .profile, minTlsVersion: .minTlsVersion, enabledFeatures: .enabledFeatures}'
# Find any Cloud Run services with unauthenticated invocation (potential ePHI exposure)
gcloud run services list --format=json | jq -r '.[] | {name: .metadata.name, allowUnauthenticated: (.metadata.annotations["run.googleapis.com/allow-unauthenticated"] // "false")}'
How to fix it for HIPAA compliance
- Update all ALB listeners to use the
ELBSecurityPolicy-TLS13-1-2-2021-06policy — this enforces TLS 1.2/1.3 and removes all deprecated cipher suites. Never use policies with "2016" in the name for ePHI workloads. - For RDS MySQL/MariaDB: Set
require_secure_transport=ONin the parameter group. For PostgreSQL: Setrds.force_ssl=1. This forces all database connections to use TLS — plaintext connections are rejected at the server level. - For GCP Cloud SQL: Set
--require-sslflag on the instance. Revoke thepublic-ip-unrestrictedauthorized network if it exists. - Enable AWS Shield Standard (included free) and configure WAF on all ALBs in front of ePHI APIs — not a TLS control, but HIPAA §164.312(c)(1) integrity requires protecting against unauthorized modification in transit too.
- Use AWS Certificate Manager (ACM) or GCP Certificate Manager for all TLS certificates — never manually managed certificates that can expire. An expired certificate on an ePHI API is an addressable safeguard failure with every request it serves.
- Document your transit encryption approach in your HIPAA risk analysis — specify which services handle ePHI in transit and confirm each uses TLS 1.2+.
#5 No Automatic Logoff and Session Controls
HIPAA §164.312(a)(2)(iii) requires "automatic logoff" as an addressable implementation specification — terminate electronic sessions after a predetermined time of inactivity. For healthcare SaaS, this applies to both your application (end-user sessions accessing ePHI) and your admin access (AWS Console, GCP Console, database tools). Long-lived sessions that persist indefinitely on systems with ePHI access are HIPAA findings.
We see this in AWS environments as: IAM roles with no session duration configured (defaulting to 12 hours), AWS SSO sessions without idle timeout policies, and GCP service account keys that never expire. The subtler issue: Lambda functions with environment variables containing database credentials (effectively permanent access with no session boundary at all).
Detection — AWS CLI
Detection — AWS CLI
# Check IAM role session duration limits (default is 3600s = 1 hour, but can be 12 hours)
aws iam list-roles --query 'Roles[*].[RoleName,MaxSessionDuration]' --output table | awk '$2 > 28800' # flag roles > 8 hours
# Find old access keys (stale, long-lived credentials = no session boundary)
aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 --decode | awk -F, 'NR>1 {print $1,$9,$10}' | while read user key1 key2; do
echo "User: $user | Key1 last used: $key1 | Key2 last used: $key2"
done
# Find Lambda functions with hardcoded env vars (no rotation boundary)
aws lambda list-functions --query 'Functions[*].[FunctionName,Environment.Variables]' --output json | jq -r '.[] | select(.[1] != null) | select(.[1] | to_entries[] | .key | test("DB_PASS|DATABASE_URL|SECRET|CREDENTIAL|PASSWORD"; "i")) | .[0]'
Detection — GCP CLI (gcloud)
Detection — GCP CLI (gcloud)
# Find service account keys with no expiration (permanent access)
gcloud iam service-accounts list --format=json | jq -r '.[].email' | while read sa; do
gcloud iam service-accounts keys list --iam-account="$sa" --format=json | jq -r '.[] | select(.keyType == "USER_MANAGED") | "SA: $sa | key: " + (.name | split("/")[-1]) + " | valid until: " + .validAfterTime'
done
# Check organization policies for session length controls
gcloud resource-manager org-policies list --organization=<org-id> --format=json 2>/dev/null | jq -r '.[] | select(.constraint | contains("iam.allowedPolicyMemberDomains")) | .constraint'
How to fix it for HIPAA compliance
- Set
--max-session-durationon all IAM roles used to access ePHI systems to 3600 seconds (1 hour) or less. Apply this especially to roles assumed via SSO or human-operated CLI sessions:aws iam update-role --role-name <role> --max-session-duration 3600 - Enforce IAM Identity Center (AWS SSO) session policies with a maximum session duration of 8 hours and require re-authentication for sensitive ePHI access. Set the session inactivity timeout to 15 minutes.
- Replace Lambda environment variables storing database credentials with AWS Secrets Manager references. Secrets Manager provides automatic rotation and per-invocation fetch — no persistent credential in the execution environment.
- For GCP: Replace service account key files with Workload Identity Federation for all compute workloads. Key files are credentials with no session boundary — they're valid until manually revoked.
- Configure GCP IAM Conditions with
request.timeconstraints for any elevated access roles — time-bound access for ePHI administrative actions. - Document your session timeout policy: what systems have ePHI access, what is the configured timeout, and who is responsible for reviewing session activity. This documentation is required by §164.308(a)(1)(i) (risk analysis) and §164.316(b)(1).
#6 Encryption Key Management Failures — KMS / Cloud KMS
HIPAA §164.312(a)(2)(iv) (addressable) covers encryption and decryption — and the OCR guidance on encryption is unambiguous: if you encrypt ePHI, you must manage the keys. Key management failures (missing rotation, overly broad key policies, loss of key access causing data unavailability) show up in HIPAA audits as §164.312(a)(2)(iv) gaps. They also show up as catastrophic operational incidents when keys are accidentally deleted.
The pattern we see in healthcare SaaS: CMKs protecting ePHI databases with no rotation enabled, key policies that grant kms:Decrypt to arn:aws:iam::ACCOUNT:root (effectively all IAM principals), and GCP Cloud KMS keys with no documented key rings or key owners. These fail both HIPAA and any reasonable internal security review.
Detection — AWS CLI
Detection — AWS CLI
# List all CMKs and check rotation status
aws kms list-keys --query 'Keys[*].KeyId' --output text | while read kid; do
rotation=$(aws kms get-key-rotation-status --key-id "$kid" --query 'KeyRotationEnabled' --output text 2>/dev/null || echo "ERROR")
alias=$(aws kms list-aliases --key-id "$kid" --query 'Aliases[0].AliasName' --output text 2>/dev/null || echo "no-alias")
state=$(aws kms describe-key --key-id "$kid" --query 'KeyMetadata.KeyState' --output text 2>/dev/null)
echo "$kid | alias: $alias | rotation: $rotation | state: $state"
done
# Check key policies for overly permissive access
aws kms list-keys --query 'Keys[*].KeyId' --output text | while read kid; do
policy=$(aws kms get-key-policy --key-id "$kid" --name default --query 'Policy' --output text 2>/dev/null)
if echo "$policy" | grep -q '"kms:\u002a"'; then
echo "WILDCARD KMS ACTION: $kid"
fi
done
Detection — GCP CLI (gcloud)
Detection — GCP CLI (gcloud)
# List all Cloud KMS key rings and keys
gcloud kms keyrings list --location=global --format=json | jq -r '.[].name' | while read ring; do
gcloud kms keys list --keyring="$ring" --location=global --format=json | jq -r '.[] | {name: .name, rotationPeriod: .rotationPeriod, state: .primary.state}'
done
# Check for keys with no rotation schedule (null rotationPeriod = no auto-rotation)
gcloud kms keyrings list --location=global --format=json | jq -r '.[].name' | while read ring; do
gcloud kms keys list --keyring="$ring" --location=global --format=json | jq -r '.[] | select(.rotationPeriod == null) | "NO ROTATION: " + .name'
done
How to fix it for HIPAA compliance
- Enable automatic annual rotation on all CMKs protecting ePHI:
aws kms enable-key-rotation --key-id <key-id>. For GCP:gcloud kms keys update <key> --rotation-period=365d --next-rotation-time=<date> - Scope KMS key policies to specific IAM roles — never
Principal: {"AWS": "arn:aws:iam::ACCOUNT:root"}for ePHI keys. The root principal grants access to all IAM principals by default. - Create a documented key inventory: key ID/name, alias, what ePHI resource it protects, who the key owner is (named person + role), rotation date, and deletion protection status.
- Enable KMS key deletion protection and require a 30-day deletion window for all ePHI keys — unintentional deletion is an ePHI availability incident. Under HIPAA §164.312(a)(2)(ii), loss of access to ePHI is treated similarly to unauthorized disclosure.
- For GCP: Organize keys in named key rings by sensitivity (
phi-production,phi-backup) and use IAM conditions on Cloud KMS roles to restrict which service accounts can decrypt each key ring. - Set up AWS Config rule
cmk-backing-key-rotation-enabledand GCP Security Command Center to alert when CMK rotation is disabled — catch configuration drift before your next HIPAA audit.
#7 Missing Breach Detection and Incident Response Controls
HIPAA's Breach Notification Rule (§164.400–§164.414) requires that Business Associates notify covered entities within 60 days of discovering a breach of unsecured ePHI. The word "discovering" is key — OCR has found that organizations that lacked detection controls (and therefore didn't know about a breach) were still liable from the date the breach occurred, not the date they found out.
For cloud-hosted healthcare SaaS, "breach detection" means: monitoring for unauthorized ePHI access, automated alerting on anomalous patterns (bulk downloads, access from new IPs, API calls outside normal business hours), and having a documented incident response plan that's been tested. AWS GuardDuty and GCP Security Command Center both provide the detection infrastructure — but they need to be enabled and connected to alerting.
Detection — AWS CLI
Detection — AWS CLI
# Check if GuardDuty is enabled in each region
for region in $(aws ec2 describe-regions --query 'Regions[*].RegionName' --output text); do
status=$(aws guardduty list-detectors --region "$region" --query 'DetectorIds' --output text 2>/dev/null)
if [ -z "$status" ]; then
echo "GUARDDUTY DISABLED: $region"
else
echo "GuardDuty enabled: $region (detector: $status)"
fi
done
# Check Security Hub activation and standards enrollment
aws securityhub get-hub --query 'HubArn' --output text 2>/dev/null || echo "SECURITY HUB DISABLED"
aws securityhub describe-standards --query 'Standards[*].[Name,StandardsStatus]' --output table
# Check for unresolved HIGH/CRITICAL GuardDuty findings
aws guardduty list-findings --detector-id <detector-id> --finding-criteria '{"Criterion":{"severity":{"Gte":7},"service.archived":{"Eq":["false"]}}}' --query 'FindingIds' --output text | wc -w | xargs echo "Open HIGH/CRITICAL findings:"
Detection — GCP CLI (gcloud)
Detection — GCP CLI (gcloud)
# Check Security Command Center tier (Standard vs. Premium)
gcloud scc settings describe --organization=<org-id> --format=json 2>/dev/null | jq -r '.enablementState // "NOT ENABLED"'
# List open HIGH severity SCC findings
gcloud scc findings list <org-id> --filter='severity="HIGH" OR severity="CRITICAL"' --filter='state="ACTIVE"' --format='json' 2>/dev/null | jq 'length | "Open HIGH/CRITICAL SCC findings: " + (.|tostring)'
# Check if Cloud Armor is attached to ePHI-serving load balancers
gcloud compute security-policies list --format=json | jq -r '.[] | {name: .name, rules: (.rules | length)}'
How to fix it for HIPAA compliance
- Enable GuardDuty in every region where you have ePHI workloads — specifically enable S3 Protection, RDS Protection, and EKS Runtime Monitoring if applicable. GuardDuty is the primary ePHI access anomaly detector on AWS.
- Enable Security Hub and enroll the AWS Foundational Security Best Practices standard — this gives you a continuous compliance score mapped to security controls that align with HIPAA Technical Safeguards.
- Create a CloudWatch Alarm or SNS notification for any GuardDuty HIGH or CRITICAL severity finding — your incident response SLA starts at finding discovery, and undiscovered findings don't stop the HIPAA 60-day breach notification clock.
- For GCP: Enable Security Command Center at Premium tier for the Event Threat Detection and Container Threat Detection modules — Standard tier doesn't provide the runtime threat detection needed for ePHI monitoring.
- Create and test an incident response runbook: who is notified when a GuardDuty HIGH finding fires, what is the triage process, what constitutes ePHI exposure, how is the covered entity notified, who drafts the HHS notification. HIPAA §164.308(a)(6) requires a documented and tested response procedure.
- Enable VPC Flow Logs on all VPCs containing ePHI workloads and ship them to a SIEM or at minimum CloudWatch Logs Insights — bulk data exfiltration is detectable in flow logs before it's visible in application logs.
Complete HIPAA Technical Safeguards → AWS/GCP Control Mapping
| HIPAA Technical Safeguard | CFR Reference | AWS Control | GCP Control | Guardrail Rule | Severity |
|---|---|---|---|---|---|
| Access Controls — Unique user ID | §164.312(a)(2)(i) | IAM Users/Roles, SSO | Cloud Identity, Workload Identity | IAM user with shared/generic credentials | Critical |
| Access Controls — Emergency access procedure | §164.312(a)(2)(ii) | Break-glass IAM role with MFA | Emergency IAM binding with audit | No break-glass role documented | Medium |
| Access Controls — Automatic logoff | §164.312(a)(2)(iii) | IAM role max session, SSO idle timeout | Service account key expiry | IAM role session > 8h, no SSO timeout | Medium |
| Access Controls — Encryption/decryption | §164.312(a)(2)(iv) | KMS CMK on S3/RDS/EBS | Cloud KMS CMEK on GCS/SQL | S3/RDS unencrypted or AWS-managed key | Critical |
| Audit Controls — Activity logging | §164.312(b) | CloudTrail (mgmt + data events), S3 access logs | Cloud Audit Logs (DATA_READ/WRITE) | CloudTrail data events disabled | High |
| Integrity — PHI alteration/destruction protection | §164.312(c)(1) | S3 Object Lock, RDS backup retention | GCS Object Hold, Cloud SQL backups | S3 versioning/lock disabled on ePHI bucket | High |
| Integrity — Electronic mechanism | §164.312(c)(2) | S3 Object Integrity checksum, CloudTrail log validation | GCS Object CRC32c, Audit log CMEK | CloudTrail log validation disabled | High |
| Person/Entity Authentication | §164.312(d) | IAM MFA enforcement, SSO MFA policy | 2-step verification enforcement | IAM user without MFA enabled | Critical |
| Transmission Security — Guard against unauthorized access | §164.312(e)(1) | ALB TLS policy (1.2/1.3), RDS SSL enforcement | Cloud Load Balancer SSL policy, Cloud SQL require_ssl | ALB with TLS 1.0/1.1 policy | High |
| Transmission Security — Encryption in transit | §164.312(e)(2)(ii) | ACM certificates, PrivateLink for internal services | Google-managed TLS, VPC Service Controls | HTTP endpoint serving ePHI | Critical |
Your 30-Day HIPAA Remediation Timeline
Most healthcare SaaS companies can close their critical and high findings in 4 weeks. Here's the fastest path from current state to audit-ready:
Week 1 — Inventory and Critical Fixes
- Run Guardrail's full scan to identify every unencrypted ePHI data store — these are your Critical findings and the first thing an OCR investigator asks for.
- Tag every resource that stores or processes ePHI with
DataClassification=ePHIin AWS orphi=truelabel in GCP — this is your documented ePHI inventory, required by HIPAA risk analysis §164.308(a)(1). - Enable S3 Block Public Access at the account level and verify no ePHI buckets have public access enabled.
- Fix all unencrypted RDS/Cloud SQL instances and S3/GCS buckets — migrate unencrypted instances to encrypted, create CMKs, document key ownership.
Week 2 — Logging and Access Controls
- Enable CloudTrail data events for all S3 ePHI buckets (GetObject, PutObject, DeleteObject) and S3 server access logging.
- For GCP: Enable DATA_READ and DATA_WRITE audit log types on all ePHI-touching services in your project IAM policy.
- Audit all IAM roles and GCP service accounts with ePHI access — remove wildcard actions, scope permissions to specific ePHI-tagged resources.
- Enable GuardDuty in all regions (AWS) or SCC Premium (GCP) and connect findings to SNS alerting.
Week 3 — Encryption Key Management and Transmission Security
- Enable KMS CMK rotation for all keys protecting ePHI, create a written key inventory with owners and rotation schedule.
- Update all ALB listener policies to TLS 1.2/1.3 only — test each endpoint with
openssl s_client -connect host:443to confirm no TLS 1.0/1.1 fallback. - Enforce SSL on all RDS instances (parameter group change) and Cloud SQL instances (
--require-ssl). - Replace any Lambda environment variable credentials with Secrets Manager references.
Week 4 — Documentation, Session Controls, and Validation
- Run Guardrail's scan again to validate all findings are closed — export the report as evidence.
- Set IAM role max session durations and SSO idle timeouts — document the policy.
- Draft or update your HIPAA Risk Analysis document: list all ePHI systems, threat/vulnerability pairs, likelihood/impact ratings, and implemented safeguards.
- Confirm 6-year log retention is configured on all audit log destinations and conduct a tabletop test of your incident response runbook.
Get Your HIPAA Cloud Security Readiness Score
Guardrail scans your AWS environment against the misconfigurations in this checklist — unencrypted ePHI stores, misconfigured audit logging, IAM overpermission, TLS gaps, and KMS key failures. Runs in under 5 minutes. No credentials stored.
Run Free HIPAA AWS Scan →