DEV Community

Bala Paranj
Bala Paranj

Posted on

Your SaaS vendor said 'use S3 for HIPAA recordings'. Did you secure the bucket?

The handoff problem

SaaS vendors that handle PHI such as telehealth platforms, EHR integrations, diagnostic tools tell customers: "Store your data in your own S3 bucket for HIPAA compliance."

This is architecturally sound. The vendor avoids storing PHI. The customer controls their own data. But it creates a responsibility handoff: the vendor provides instructions, the customer must execute them correctly.

Whereby, a video conferencing platform used in telehealth, tells HIPAA customers to store cloud recordings in their own S3 bucket with this policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowListAndPutRecordings",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<aws_account_id>:user/<aws_user>"
      },
      "Action": ["s3:ListBucket", "s3:PutObject"],
      "Resource": [
        "arn:aws:s3:::<recording_bucket>",
        "arn:aws:s3:::<recording_bucket>/*"
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Good template. Scoped principal, minimal actions, specific resources. But the template only covers the bucket policy. It says nothing about encryption, logging, versioning, Public Access Block, Object Lock, or transport encryption.

The customer is responsible for all of it. They can get it wrong.

What goes wrong

1. No encryption at rest

The customer creates the bucket, pastes the policy, starts recording. Nobody enables server-side encryption. The recordings which contain patient video, audio, and potentially screen-shared medical records sit unencrypted in S3.

Stave catches it:

[FAIL] CONTROLS.001 — high
Compliance: §164.312(a)(2)(iv) — Encryption and Decryption
Finding: Bucket telehealth-recordings: encryption at rest not enabled
Enter fullscreen mode Exit fullscreen mode

2. AWS-managed key instead of CMK

The customer enables SSE-S3 (AES256) which is the default. But AES256 uses an AWS-managed key that the customer cannot revoke. During a breach, the customer needs to disable the key immediately to render recordings unreadable. With the AWS-managed key, they can't.

Stave catches it:

[FAIL] CONTROLS.001.STRICT — critical
Compliance: §164.312(a)(2)(iv) — CMK required for key revocation
Finding: SSE uses AES256 (AWS-managed). CMK required for breach response.
Enter fullscreen mode Exit fullscreen mode

Whereby even notes that "key rotation will provide additional security." But they stop short of requiring CMK. Stave's HIPAA profile makes the distinction explicit.

3. No access logging

The bucket stores PHI recordings but has no server access logging enabled. If an unauthorized party downloads recordings, there's no forensic evidence.

Stave catches it:

[FAIL] AUDIT.001 — critical
Compliance: §164.312(b) — Audit Controls
Finding: Bucket telehealth-recordings: logging not enabled.
Logs cannot be obtained retroactively from AWS.
Enter fullscreen mode Exit fullscreen mode

This is the retroactivity problem. You can't turn on logging after a breach and recover what happened before. The evidence window is zero.

4. Public Access Block not enabled

The customer pastes the bucket policy but doesn't enable Public Access Block. A future policy change such as a Terraform apply with a stale config, a console click could make the bucket public. PAB is the safety net.

Stave catches it:

[FAIL] CTL.S3.CONTROLS.001 — high
Finding: Public Access Block not fully enabled. No safety net against
accidental public exposure from policy or ACL changes.
Enter fullscreen mode Exit fullscreen mode

5. Wildcard actions in the policy

Instead of the vendor's scoped template, the customer pastes a broader policy they found on Stack Overflow:

{
  "Effect": "Allow",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": "arn:aws:s3:::telehealth-recordings/*"
}
Enter fullscreen mode Exit fullscreen mode

Now anyone can read, write, delete, or modify ACLs on the recordings bucket.

Stave catches it:

[FAIL] ACCESS.002 — high
Compliance: §164.312(a)(2)(i) — Least privilege
Finding: Policy grants Allow with wildcard action s3:*
Enter fullscreen mode Exit fullscreen mode

And the compound detector fires:

[critical] COMPOUND.001
Public access with overly broad IAM permissions — the S3 + IAM lateral
movement pattern present in the majority of documented AWS breaches.
Enter fullscreen mode Exit fullscreen mode

6. No Object Lock for retention

HIPAA requires 6 years of PHI retention. Without Object Lock in Compliance mode, recordings can be deleted accidentally, maliciously, or by a lifecycle rule that expires objects too early.

Stave catches it:

[FAIL] RETENTION.002 — critical
Compliance: §164.316(b)(2) — 6-year PHI retention
Finding: Object Lock not enabled — recordings can be deleted.
Enter fullscreen mode Exit fullscreen mode

7. No transport encryption enforcement

The bucket policy doesn't deny non-TLS requests. Someone accessing the bucket via HTTP (not HTTPS) transmits recordings in plaintext.

Stave catches it:

[FAIL] CONTROLS.004 — high
Compliance: §164.312(e)(2)(ii) — Encryption in transit
Finding: No Deny statement for aws:SecureTransport=false
Enter fullscreen mode Exit fullscreen mode

The compound picture

Run all 14 HIPAA controls against a typical "I followed the vendor's template" bucket:

stave evaluate --snapshot recordings-bucket.json --profile hipaa
Enter fullscreen mode Exit fullscreen mode
═══ HIPAA Security Rule ═══
Bucket:    telehealth-recordings
Result:    FAIL

  critical: 0/4 passed
  high:     1/7 passed  (only the vendor's bucket policy is correct)
  medium:   0/2 passed

Overall: FAIL
Enter fullscreen mode Exit fullscreen mode

The vendor's bucket policy template is the only thing that passes. Everything else such as encryption, logging, retention, transport security, PAB is the customer's responsibility. And the customer didn't do it.

The duration dimension

The worst version of this: the bucket has been in this state since the customer set it up 18 months ago. Every recording since then is stored unencrypted, unlogged, without retention protection.

Duration: 13,000+ hours (threshold: 168 hours)
Enter fullscreen mode Exit fullscreen mode

No other tool tracks this. AWS Config says "non-compliant now." Stave says "non-compliant for 13,000 hours." That temporal evidence changes the regulatory conversation.

The lesson for SaaS vendors and customers

For vendors: providing a bucket policy template is necessary but insufficient. Document the full bucket security posture: encryption (CMK, not AWS-managed), logging, PAB, versioning, Object Lock, transport encryption. Or better: provide a Stave observation contract so customers can evaluate their bucket against your HIPAA requirements.

For customers: the vendor's HIPAA compliance ends at their boundary. Your S3 bucket is your responsibility. Run Stave's HIPAA profile against every bucket that stores vendor-delegated PHI:

stave evaluate --snapshot my-recordings-bucket.json --profile hipaa
Enter fullscreen mode Exit fullscreen mode

If it doesn't say PASS across all 14 controls, the bucket isn't HIPAA compliant regardless of what the vendor's documentation says.

Top comments (0)