Back to Checks
S3 bucket policy does not allow cross-account access
s3_bucket_cross_account_access
Service: s3Severity: high
OfficialBuilt-inVerified
Categories:identity-accesstrust-boundaries
Resource Type:AwsS3Bucket
Amazon S3 bucket policies are analyzed for statements that grant cross-account access.
Any policy that names principals outside the owning account (other account IDs or Principal: "*") is treated as cross-account; absence of a policy implies no cross-account grants.
Risk
Cross-account grants can let external principals read, write, or administer the bucket, impacting:
- Confidentiality: unauthorized object access/exfiltration
- Integrity: object tampering, policy or encryption changes
- Availability: deletions, versioning changes, or lockouts causing data loss and downtime
Run this check with Prowler
Run in Prowler CloudRun
prowler aws --checks s3_bucket_cross_account_accessRecommendation
Enforce least privilege: limit bucket policy Principal to your account or approved org IDs with fixed values; avoid wildcards.
Use role-based cross-account access with scoped permissions when needed. Add defense-in-depth conditions (private networks, TLS), and periodically review for unintended external access.
Remediation
CLI
aws s3api delete-bucket-policy --bucket <example_resource_name>Terraform
# Terraform: restrict bucket policy to this AWS account only
resource "aws_s3_bucket_policy" "<example_resource_name>" {
bucket = "<example_resource_name>"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { AWS = "arn:aws:iam::<example_resource_id>:root" } # Critical: limits access to the bucket owner's account, preventing cross-account access
Action = "s3:*"
Resource = [
"arn:aws:s3:::<example_resource_name>",
"arn:aws:s3:::<example_resource_name>/*"
]
}]
})
}NativeIaC
# CloudFormation: restrict bucket policy to this AWS account only
Resources:
<example_resource_name>BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: <example_resource_name>
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:root # Critical: limits access to the bucket owner's account, preventing cross-account access
Action: s3:*
Resource:
- !Sub arn:aws:s3:::<example_resource_name>
- !Sub arn:aws:s3:::<example_resource_name>/*Other
1In the AWS console, go to S3 > Buckets > select the bucket
2Open the Permissions tab > Bucket policy
3Click Delete bucket policy (or remove all statements) and Save
4If a policy is required, ensure all statements only use your account as Principal (arn:aws:iam::<YOUR_ACCOUNT_ID>:root); remove any other accounts or "*"
Check Code
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.iam.lib.policy import is_policy_public
from prowler.providers.aws.services.s3.s3_client import s3_client
class s3_bucket_cross_account_access(Check):
def execute(self):
findings = []
trusted_account_ids = s3_client.audit_config.get("trusted_account_ids", [])
for bucket in s3_client.buckets.values():
if bucket.policy is None:
continue
report = Check_Report_AWS(metadata=self.metadata(), resource=bucket)
report.status = "PASS"
report.status_extended = f"S3 Bucket {bucket.name} has a bucket policy but it does not allow cross account access."
if not bucket.policy:
report.status = "PASS"
report.status_extended = (
f"S3 Bucket {bucket.name} does not have a bucket policy."
)
elif is_policy_public(
bucket.policy,
s3_client.audited_account,
is_cross_account_allowed=False,
trusted_account_ids=trusted_account_ids,
):
report.status = "FAIL"
report.status_extended = f"S3 Bucket {bucket.name} has a bucket policy allowing cross account access."
findings.append(report)
return findings