Back to Blog
June 18, 2026

Securing S3 Bucket Policies: Public Access, Conditions, and Common Mistakes

S3 bucket policies are written once and forgotten. They survive team changes, architecture pivots, and migration projects — and they accumulate permissions that nobody intended to leave open. When those permissions combine with a missing condition or a stale public-access setting, the result is an exposed bucket. S3 misconfigurations have been behind some of the largest cloud data breaches on record, and most of them share the same handful of root causes.

Why S3 Bucket Policies Matter

Bucket policies are resource-based policies: they are attached to the bucket itself and evaluated alongside IAM identity policies. This means a bucket policy can grant access to principals that have no IAM permissions at all — including anonymous callers — if the policy allows it. Unlike IAM role policies, which travel with an identity, bucket policies are always in scope for anyone who reaches the bucket endpoint. A single misconfigured statement can override months of careful IAM work.

Bucket policies also interact with the S3 Block Public Access controls at both the account and bucket level. Understanding that interaction is essential before writing any policy that uses Principal: "*".

The Three Most Dangerous Patterns

Most S3 data exposures trace back to one of three patterns. Recognizing them on sight is the fastest way to audit a policy.

  • Principal: "*" with no condition. An unconditioned wildcard principal means any AWS account, any IAM entity, and any unauthenticated caller can invoke the allowed actions. On a public S3 endpoint this is effectively the open internet. The statement below grants read access to every object in the bucket with no restriction whatsoever.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DangerousPublicRead",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-company-data/*"
    }
  ]
}

If BlockPublicPolicy and BlockPublicAcls are disabled at the account or bucket level, this policy makes every object in the bucket publicly readable with no authentication required.

  • Missing condition on cross-account grants. Allowing another AWS account to access a bucket without a condition means any principal in that account is eligible — not just the specific role or service you intended. If the external account is ever compromised, your data is too. Always pair cross-account grants with aws:PrincipalArn or aws:PrincipalOrgID conditions.
  • s3:GetObject on Resource: "*". Even without a wildcard principal, scoping s3:GetObject to * in the resource field grants read access to every object in every bucket the policy applies to. Always scope the resource to the specific bucket prefix the grantee actually needs.

How Public Access Controls Interact with Bucket Policies

S3 Block Public Access adds a second layer of evaluation on top of bucket policies. There are four separate settings, and their interaction with policies is not always intuitive:

  • BlockPublicAcls — ignores and blocks new ACLs that would make the bucket or objects public. Does not affect bucket policies.
  • IgnorePublicAcls — existing public ACLs are ignored at access time. Does not affect bucket policies.
  • BlockPublicPolicy — rejects any PutBucketPolicy call that would make the bucket publicly accessible. This is the control that actually blocks a dangerous bucket policy from being saved.
  • RestrictPublicBuckets — if the bucket policy already grants public access, this setting restricts all anonymous and cross-account access even if the policy says Allow. This is a strong safety net for buckets that already have a bad policy.

The common mistake is assuming that Block Public Access is an all-or-nothing toggle. It is not. BlockPublicAcls being enabled does nothing to a bucket policy. Only BlockPublicPolicy and RestrictPublicBuckets interact with bucket policy evaluation. Enable all four settings at the account level and override only for buckets that genuinely need public access — then audit those buckets more carefully.

Writing a Safe Cross-Account S3 Policy

Cross-account bucket access is a legitimate use case — sharing data with a partner account, a logging pipeline in a central security account, or a CI/CD system in a separate tooling account. The key is constraining who in the external account can assume the grant. The safest options, in order of preference:

  • aws:PrincipalOrgID — restricts access to principals from accounts within your AWS Organization. Nothing outside the organization can satisfy this condition, even with the correct account ID.
  • Explicit account or role ARN via aws:PrincipalArn — names the exact IAM role or user. More specific than an organization-wide condition, but requires updating when the role name or account changes.
  • aws:SourceArn / aws:SourceAccount — used when a service (Lambda, CloudTrail, Config) writes to the bucket on behalf of an account. Prevents the confused deputy problem where a service is tricked into acting for a different account.

Here is a safe cross-account policy that restricts access to all principals inside your AWS Organization and scopes the resource to a specific prefix:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CrossAccountOrgRead",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-company-data",
        "arn:aws:s3:::my-company-data/shared/*"
      ],
      "Condition": {
        "StringEquals": {
          "aws:PrincipalOrgID": "o-exampleorgid11"
        }
      }
    }
  ]
}

This policy uses Principal: "*" safely because the aws:PrincipalOrgID condition filters out everyone outside the organization before the effect is applied. Anonymous callers from the internet do not belong to any AWS Organization and will never satisfy the condition. The resource field is also scoped to a specific prefix rather than the entire bucket.

Auditing Your Bucket Policies

A bucket policy audit should answer three questions for every statement: who can invoke this, on what resources, and under what conditions. Working through that checklist manually is error-prone, especially across an account with dozens of buckets. A few approaches that help:

  • IAM Access Analyzer. Enables automated detection of resource policies that grant access outside your account or organization. It generates a finding for every bucket (and other resource type) that is accessible to an external principal. Enable it at the organization level so it covers all member accounts.
  • AWS Config rules. s3-bucket-public-read-prohibited and s3-bucket-public-write-prohibited flag buckets that allow public access and report them to a central aggregator. Add these to your conformance pack.
  • CloudFormation and CDK review. If your buckets are defined as infrastructure as code, the policy is reviewable before it deploys. Catching a wildcard principal in a code review is far cheaper than finding it in an Access Analyzer finding after the fact. Tools that analyze CloudFormation templates can surface these patterns before they reach a real account.
  • Periodic policy export and diff. Export all bucket policies with the AWS CLI or S3 console and diff them against the last known-good snapshot. Policy drift — permissions that appeared between audits — is a common signal of unauthorized or accidental changes.

Bucket policies are not a one-time configuration. They change as teams add new integrations, as buckets get repurposed, and as old access grants are never cleaned up. Treating policy review as a recurring practice rather than a one-off task is the most reliable way to keep S3 data exposure in check.

Find S3 policy misconfigurations before data leaks

Upload a CloudFormation template or paste a resource policy for AI-Powered analysis — free, no credit card.

Amazon Web Services (AWS) is a trademark of Amazon.com, Inc. Shieldly is not affiliated with, endorsed by, or sponsored by Amazon Web Services.