The AWS Console likes to reassure you with green badges claiming “Bucket and objects not public,” but that status indicator is dangerously simplistic. It primarily tracks S3 Block Public Access settings and legacy Access Control Lists (ACLs). In reality, buckets leak through complex resource policies, misconfigured S3 Access Points, and overly broad cross-account IAM trusts that bypass those basic checks entirely.
If your S3 security audit stops at checking bucket ACLs, you’re missing the exact attack surface that modern cloud exposure incidents exploit. Here is how to perform a deep-dive audit of your S3 exposure beyond the surface layer using the AWS CLI and IAM Access Analyzer.
Hunting Down Complex Bucket Policy Leaks
Disabling public ACLs and enabling S3 Block Public Access at the bucket level is good baseline hygiene, but bucket policies can still introduce unexpected access paths if Block Public Access was ever partially turned off or configured with exemptions.
A common anti-pattern is using "Principal": "*" alongside a Condition block that the author assumed secured the bucket, but actually fails to do so. For instance, relying on easily forged request headers like aws:UserAgent or aws:Referer leaves the bucket publicly readable to anyone who crafts a custom HTTP request.
To audit a bucket policy directly, fetch the policy JSON and inspect its statements:
aws s3api get-bucket-policy \
--bucket target-data-bucket \
--query Policy \
--output text | jq .
When evaluating the output, look specifically for these constructs:
- Wildcard Principals with Weak Conditions: Look for
"Principal": "*"or"Principal": {"AWS": "*"}paired with conditions that don’t enforce strong identity constraints (likeaws:PrincipalArnoraws:PrincipalOrgID). - Insecure Use of
NotPrincipal: Combining"Effect": "Allow"with"NotPrincipal"grants access to everyone except the listed principal. If an unauthenticated attacker requests an object, they are not the excluded principal, so the policy allows the request. - Missing IP or VPC Enforcements: If a bucket policy is meant to restrict access to internal infrastructure, verify that it enforces
aws:SourceVpcoraws:SourceIpusing aDenystatement with aStringNotEqualscondition rather than a permissiveAllow.
Auditing S3 Access Points and Their Independent Policies
S3 Access Points are dedicated network endpoints attached to buckets, each with its own distinct resource policy. A bucket policy might look completely locked down, while an Access Point attached to that same bucket exposes objects to external networks or unauthenticated entities.
Crucially, S3 Access Points maintain their own Block Public Access configurations separate from the underlying bucket.
First, enumerate all Access Points in the account for your target region:
aws s3control list-access-points \
--account-id 123456789012 \
--query "AccessPointList[*].[Name, NetworkOrigin, VpcConfiguration.VpcId, Bucket]" \
--output table
Next, evaluate the Block Public Access settings for each Access Point to ensure public access isn’t explicitly permitted at the endpoint layer:
aws s3control get-public-access-block \
--account-id 123456789012 \
--name app-data-access-point
Finally, inspect the Access Point policy itself. Access Point policies behave similarly to bucket policies and can grant read/write access independently:
aws s3control get-access-point-policy \
--account-id 123456789012 \
--name app-data-access-point \
--query Policy \
--output text | jq .
If an Access Point’s network origin is set to Internet and its policy contains a wildcard principal without aws:PrincipalOrgID or explicit IAM user restrictions, your underlying bucket data is publicly accessible through the Access Point alias ARN.
Uncovering Cross-Account Trust Chain Vulnerabilities
Public exposure isn’t always direct unauthenticated access. A common vector involves granting read access on an S3 bucket to a trusted third-party AWS account (e.g., an analytics vendor or partner account), which then misconfigures its own IAM roles.
If Bucket A grants read permissions to arn:aws:iam::999999999999:root, any IAM entity in account 999999999999 with sufficient permissions can read your objects. If that third-party account has a role with an overly permissive trust policy—allowing "Principal": "*" to assume it—an attacker can assume the vendor’s role and pivot directly into your S3 bucket.
To check cross-account grants in your bucket policy using the AWS CLI:
aws s3api get-bucket-policy \
--bucket target-data-bucket \
--output json | jq '.Policy | fromjson | .Statement[] | select(.Principal.AWS != null)'
When cross-account principals are identified:
- Ensure grants target specific role ARNs (
arn:aws:iam::999999999999:role/VendorSpecificRole) rather than account roots (arn:aws:iam::999999999999:root). - If external roles assume a role inside your account to read S3 data, verify that the assuming role’s trust policy requires a unique
sts:ExternalId.
Automating Surface Detection with IAM Access Analyzer
Manual policy analysis breaks down when managing dozens of AWS accounts and hundreds of buckets. AWS IAM Access Analyzer uses mathematical logic (formal reasoning) to analyze resource policies and detect external exposure across buckets, Access Points, and KMS keys.
To set up an account-level analyzer for S3 using the AWS CLI:
aws accessanalyzer create-analyzer \
--analyzer-name s3-exposure-check \
--type ACCOUNT
Once the initial scan completes, list all active findings targeting S3 resources:
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/s3-exposure-check \
--filter '{"status": {"eq": ["ACTIVE"]}, "resourceType": {"eq": ["AWS::S3::Bucket"]}}' \
--query "findings[*].[id, resource, action, isPublic]" \
--output table
Access Analyzer flags findings where isPublic is true (accessible to unauthenticated users or any AWS identity) as well as access granted to external AWS principals outside your Zone of Trust (your AWS Organization).
Integrating list-findings queries into your continuous integration pipelines or security automation scripts gives you a reliable signal of unintended S3 exposure before an external scanner indexes your data.
Related content
The IAM Blind Spots That Keep Showing Up in Cloud Assessments
ResearchStop Guessing IAM: Building AWS Least-Privilege Policies from CloudTrail History
ResearchThe Seam of Failure: Why Identity Handoffs Break Cloud Security
ResearchHardening Kubernetes RBAC: Auditing and Downsizing ClusterRoles Safely
Want a second set of eyes on your security posture?
Let's talk about where your real exposure is.
Book an advisory call