DEV Community

Bala Paranj
Bala Paranj

Posted on

Are presigned URLs a HIPAA risk? The presigned URL gap in HIPAA S3 compliance

✓ Human-authored analysis; AI used for formatting and proofreading.

We use presigned URLs to share reports with providers. Are we HIPAA compliant? The discussion is split between "they're temporary, it's fine" and "they're a ticking time bomb." The nuance is in what your bucket policy enforces.

The Problem

A presigned URL embeds IAM credentials into the URL itself. Anyone with the URL can download the object without AWS credentials or MFA check. Default expiration is up to 7 days for IAM user credentials. If a presigned URL to a PHI object leaks via email, Slack, or a log file, you have an uncontrolled access window to protected health information.

The bucket policy doesn't restrict presigned URLs by default. Presigned URLs bypass IAM policies, VPC endpoint conditions, and IP restrictions. Unless the bucket policy explicitly includes conditions for s3:signatureAge or s3:authType.

The HIPAA Citation

HIPAA section 164.312(a)(1) requires access controls that restrict access to electronic PHI to authorized persons. A presigned URL that can be forwarded to any person on the internet is not an access control. It's an access delegation with no audit trail of who used it.

How Stave Detects It

Stave control CTL.S3.PRESIGNED.001 checks whether the bucket policy includes presigned URL restrictions:

id: CTL.S3.PRESIGNED.001
name: Presigned URL Access Must Be Restricted
severity: medium
compliance:
  hipaa: "164.312(a)(1)"

unsafe_predicate:
  all:
    - field: properties.storage.kind
      op: eq
      value: bucket
    - field: properties.storage.access.presigned_url_restricted
      op: eq
      value: false
Enter fullscreen mode Exit fullscreen mode

The control checks for two specific guardrails in the bucket policy:

  1. s3:signatureAge Limits how long a presigned URL remains valid. A Deny statement with NumericGreaterThan on s3:signatureAge of 600000 (10 minutes) forces short-lived URLs.
  2. s3:authType A Deny statement with StringNotEquals on s3:authType set to REST-HEADER blocks presigned URL access, requiring direct IAM authentication.
stave evaluate \
  --controls controls/s3/access/ \
  --observations observations/ \
  --eval-time 2026-04-08T00:00:00Z
Enter fullscreen mode Exit fullscreen mode
Security State: NON_COMPLIANT

Findings:
  phi-reports-bucket:
    CTL.S3.PRESIGNED.001  UNSAFE  Presigned URL access unrestricted
      Bucket policy does not restrict presigned URL access. Presigned
      URLs can provide long-lived unauthenticated access to objects.

      Remediation: Add a Deny statement with Condition NumericGreaterThan
      s3:signatureAge (e.g., 600000 for 10 minutes) or StringNotEquals
      s3:authType REST-HEADER to block presigned URL access.
Enter fullscreen mode Exit fullscreen mode

Presigned URLs are inherently uncontrolled. The security comes from the bucket policy conditions that constrain them, not from the URLs themselves. If your PHI bucket policy doesn't include s3:signatureAge or s3:authType conditions, every presigned URL is a potential breach vector with no expiration guardrail beyond the IAM credential lifetime. Stave flags this gap before an auditor or an attacker finds it.

Top comments (0)