S3 Bucket Policies: The Missing SourceAccount Check
A bucket policy grants a wildcard AWS service Principal like sns.amazonaws.com or cloudtrail.amazonaws.com write access without pinning aws:SourceAccount, so any AWS account can configure that same service to target the bucket. IAM boundaries never trigger, because the request arrives as a trusted first-party service call, not a cross-account API invocation.
Operational summary
At a glance
- Symptom
- A different AWS account configures the same trusted service to target the exposed bucket ARN, and the service delivers on their behalf without any…
- Likely cause
- An S3 bucket policy grants an AWS service Principal (e.g.
- Impact
- Unauthenticated writes accumulate as legitimate service traffic, evading GuardDuty and IAM Access Analyzer while inflating storage costs and poisoning downstream log analysis.
- Verification signal
- Re-run the detection test and a controlled negative-path test.
- Safe correction
- Require aws:SourceAccount and aws:SourceArn conditions on every service-principal statement and enforce this with a custom AWS Config rule.
- Rollback or recovery
- Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
The Trap
Service Principal grants in an S3 bucket policy that omit the aws:SourceAccount condition key, trusting an AWS service by name alone rather than scoping which account’s instance of that service may act.
The Default State
Console wizards for S3 event notifications, CloudTrail delivery, ALB access logging, AWS Config recorder delivery, and SES receipt rule storage generate a Principal block such as {“Service”: “cloudtrail.amazonaws.com”} and often include aws:SourceArn, but a large share of hand-written Terraform modules and older CloudFormation samples predating the 2021 confused-deputy advisory skip aws:SourceAccount entirely. Engineers copying a working policy from one project to another rarely notice the omission, since the bucket still functions correctly for the intended account.
The Blast Radius
The policy trusts the named AWS service, not your account specifically. Any AWS customer can create their own CloudTrail trail, SNS topic, or ALB and configure it to deliver output to your bucket ARN if it becomes known through shared logs, Terraform state leaks, or predictable naming. The service then writes objects on their behalf, and because the calling identity in your bucket’s access logs is the legitimate AWS service endpoint, not an external ARN, GuardDuty and IAM Access Analyzer’s cross-account findings stay silent — Access Analyzer flags foreign account ARNs in Principal blocks, not service principals paired with an unscoped SourceAccount. Downstream SIEM pipelines parsing injected log objects as trusted CloudTrail data compound the damage, and storage costs climb from unauthenticated write volume nobody billed for.
The Lead Mechanic Fix
Pin both aws:SourceAccount and aws:SourceArn on every service-principal statement:n{n “Effect”: “Allow”,n “Principal”: {“Service”: “cloudtrail.amazonaws.com”},n “Action”: “s3:PutObject”,n “Resource”: “arn:aws:s3:::bucket/prefix/*”,n “Condition”: {n “StringEquals”: {“aws:SourceAccount”: “111122223333”},n “ArnLike”: {“aws:SourceArn”: “arn:aws:cloudtrail:eu-west-2:111122223333:trail/trailname”}n }n}nAudit live policies with aws s3api get-bucket-policy –bucket , script a check for any Service principal statement lacking both condition keys, and enforce compliance with a custom AWS Config rule that flags or denies PutBucketPolicy calls where a service Principal omits aws:SourceAccount.
Apply the safer control
Before you change production
Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.
Require aws:SourceAccount and aws:SourceArn conditions on every service-principal statement and enforce this with a custom AWS Config rule.
Validate the vendor-specific syntax in official documentation before applying it.
Verify, roll back or escalate
Verify
Re-run the detection test and a controlled negative-path test. Confirm the unsafe behaviour is blocked while approved traffic still succeeds.
Rollback
Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.