I checked the conditions under which a policy ARN is displayed in S3 403 errors

I checked the conditions under which a policy ARN is displayed in S3 403 errors

Amazon S3 403 Access Denied errors now display the ARN of the policy that explicitly denied access. In this article, we verified the conditions under which ARNs are displayed, covering SCPs, RCPs, identity-based policies, session policies, and permission boundaries. We also examined the differences from inline policies and bucket policies, as well as whether ARNs are exposed in unauthenticated requests.
2026.08.14

This page has been translated by machine translation. View original

Introduction

On August 13, 2026, Amazon S3 was updated so that 403 Access Denied error messages now include the ARN of the policy that denied access.

https://aws.amazon.com/about-aws/whats-new/2026/08/s3-additional-policy-details-access-denied-error-messages/

Iwasa's article introduces the behavior during explicit Deny via identity-based policies, permissions boundaries, SCPs (service control policies), and RCPs (resource control policies).

In this article, in addition to those, we verified the conditions under which policy ARNs are displayed, also covering session policies, inline policies, and bucket policies. We also tested whether ARNs are exposed to unauthenticated requests from outside the organization.

What We Tested

We first list which policy types display ARNs and which cases do not, then examine each condition individually.

What Is Returned Per Policy Type

We denied s3:GetObject for each policy type capable of explicit Deny and compared the returned error messages.

Policy Type Specification Method Policy ARN Notes
SCP Applied from management account via Organizations -
RCP Applied from management account via Organizations -
Identity-based policy Customer managed policy attached to role -
Session policy Managed policy specified with --policy-arns -
Permissions boundary Customer managed policy set as boundary -
Session policy (inline) Inline JSON specified with --policy × Type only
Identity-based policy (inline) Inline configuration via put-role-policy × Type only
Bucket policy Set directly on the bucket × Type only
Implicit Deny (reference) State where no Allow exists × Denial reason only
Unauthenticated request from outside organization (reference) Unsigned HTTP request × Access Denied only

The "Policy ARN" column indicates whether the error message includes the ARN of the policy that denied access.

Testing was performed on s3:GetObject in a single account within the same organization. The S3 User Guide examples show the format , with policy ARN: <ARN>, but all messages we actually observed used the format : <ARN>.

We used separate buckets for each case to ensure there was only one denial factor. The same command was used to retrieve the object in all cases, with only the bucket name changed. Account IDs, role names, bucket names, and policy IDs have been replaced with example values, so even though the same ExampleRole name appears, it refers to a different role in each case.

aws s3api get-object \
  --bucket <bucket-name> \
  --key test.txt \
  /tmp/out.txt \
  --region ap-northeast-1

SCP

We created the following SCP in the management account and attached it directly to the member account.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyS3GetObject",
      "Effect": "Deny",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket-scp/*"
    }
  ]
}
aws organizations attach-policy \
  --policy-id p-examplescpid \
  --target-id 123456789012
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-scp/test.txt" with an explicit deny in a service control policy: arn:aws:organizations::111122223333:policy/o-exampleorgid/service_control_policy/p-examplescpid

RCP

Unlike SCPs, RCPs require a Principal specification. We created it in the management account and attached it directly to the member account.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyS3GetObject",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket-rcp/*"
    }
  ]
}
aws organizations attach-policy \
  --policy-id p-examplercpid \
  --target-id 123456789012
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-rcp/test.txt" with an explicit deny in a resource control policy: arn:aws:organizations::111122223333:policy/o-exampleorgid/resource_control_policy/p-examplercpid

Identity-Based Policy

We created a customer managed policy named DenyS3GetObjectIdentity and attached it to the role used for execution.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyS3GetObject",
      "Effect": "Deny",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket-identity/*"
    }
  ]
}
aws iam attach-role-policy \
  --role-name ExampleRole \
  --policy-arn arn:aws:iam::123456789012:policy/DenyS3GetObjectIdentity
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-identity/test.txt" with an explicit deny in an identity-based policy: arn:aws:iam::123456789012:policy/DenyS3GetObjectIdentity

Session Policy

We created DenyS3GetObjectSession as a customer managed policy and specified it with --policy-arns in assume-role. Since the effective permissions of a session policy are the intersection with the role's policies, we included an Allow to prevent implicit denial of buckets other than the target.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAll",
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    },
    {
      "Sid": "DenyS3GetObject",
      "Effect": "Deny",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket-session/*"
    }
  ]
}
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/ExampleRole \
  --role-session-name ExampleSession \
  --policy-arns '[{"arn":"arn:aws:iam::123456789012:policy/DenyS3GetObjectSession"}]'
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-session/test.txt" with an explicit deny in a session policy: arn:aws:iam::123456789012:policy/DenyS3GetObjectSession

Session policies are a type listed in the S3 User Guide as subject to ARN display, and the behavior matched the documentation.

Permissions Boundary

We created DenyS3GetObjectBoundary as a customer managed policy and set it as the permissions boundary when creating the role. We granted S3 read permissions on the role side so that the boundary would be the only denial factor.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAll",
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    },
    {
      "Sid": "DenyS3GetObject",
      "Effect": "Deny",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket-boundary/*"
    }
  ]
}
aws iam create-role \
  --role-name ExampleRole \
  --permissions-boundary arn:aws:iam::123456789012:policy/DenyS3GetObjectBoundary \
  --assume-role-policy-document file://trust-policy.json
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-boundary/test.txt" with an explicit deny in a permissions boundary: arn:aws:iam::123456789012:policy/DenyS3GetObjectBoundary

In all cases, only the policy ARN was returned. Which statement matched, or what the policy document contains, was not included.

Cases where ARNs are not returned fall into two categories: cases where the policy type itself does not have an ARN (such as bucket policies), and cases where a policy type that has an ARN is specified inline. Both are covered in the next section.

The Condition for ARN Display Is Whether the Policy Has an ARN

Even for the same policy type, the display differed depending on whether it was specified as a managed policy with an ARN or written inline.

First, session policies. We passed the same policy content as the previous section as inline JSON to --policy instead of --policy-arns.

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/ExampleRole \
  --role-session-name ExampleSession \
  --policy '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"},{"Effect":"Deny","Action":"s3:GetObject","Resource":"arn:aws:s3:::amzn-s3-demo-bucket-session/*"}]}'
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-session/test.txt" with an explicit deny in a session policy

The message ended with session policy and no ARN was appended.

The same was true for identity-based policies. We set it as an inline policy on the role using put-role-policy.

aws iam put-role-policy \
  --role-name ExampleRole \
  --policy-name DenyS3GetObjectInline \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Sid":"DenyS3GetObject","Effect":"Deny","Action":"s3:GetObject","Resource":"arn:aws:s3:::amzn-s3-demo-bucket-inline/*"}]}'
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-inline/test.txt" with an explicit deny in an identity-based policy

Bucket policies also do not have ARNs, so only the type is returned. We set the following policy on the bucket used in the session policy test and ran it with a regular role without a session policy applied. The only denial factor was the bucket policy.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyGetObject",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket-session/*"
    }
  ]
}
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-session/test.txt" with an explicit deny in a resource-based policy

The fact that the display differs between managed and inline policies was not documented on the S3 User Guide or IAM User Guide pages we checked at the time of writing, and was behavior discovered through our actual testing.

The inline specification we tested covered two cases—session policies and identity-based policies—but in both cases no ARN was displayed, and identifying the denial cause remains a manual process as before. Since it still requires listing the inline policies set on the role and visually searching for the matching statement, if investigation ease is a priority, it is advantageous to manage Deny rules as customer managed policies.

Distinguishing Explicit Deny, Implicit Deny, and Pre-Authentication

Just because a 403 is returned does not necessarily mean the cause is an explicit Deny.

We accessed a bucket owned by a different account within the same organization via AWS CLI. This was an implicit Deny where no Allow existed. The HTTP status was 403, and while the denial reason was shown, no policy ARN was appended.

An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-other-account/example-object.yaml" because no resource-based policy allows the s3:GetObject action

Next, we sent an HTTP request without credentials to an existing private bucket. The target was a bucket we had confirmed returns HTTP 200 with authenticated head-bucket. The HTTP status was 403, and the body contained only a generic Access Denied.

curl -s https://amzn-s3-demo-bucket-private.s3.ap-northeast-1.amazonaws.com/
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>...</RequestId><HostId>...</HostId></Error>

Accessing a nonexistent bucket name without authentication returned HTTP 404 with NoSuchBucket.

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist</Message><BucketName>amzn-s3-demo-bucket-no-such</BucketName>...</Error>

For one of the bucket names we assumed to be nonexistent, we received HTTP 403 with AllAccessDisabled. That bucket actually exists and is considered to be in a state where access has been disabled.

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AllAccessDisabled</Code><Message>All access to this object has been disabled</Message>...</Error>

The criterion is whether a principal name is present. Messages beginning with User: arn:aws:sts:: have reached the authorization evaluation stage and are worth investigating policies for. Among the four examples above, only the implicit Deny qualified; the rest were generic Access Denied, NoSuchBucket, and AllAccessDisabled. However, a generic Access Denied cannot be distinguished by message alone as to whether the cause is missing credentials or a policy denial.

In the next section, we confirm that unauthenticated requests denied by RCP also result in the same generic message.

ARNs Are Not Exposed to Unauthenticated Requests Even for Public Buckets

Among the five policy types that display ARNs, RCP is the one expected to apply to principals outside the organization. RCPs control access to resources within the organization, including principals outside the organization. We therefore applied an RCP to a public bucket and verified whether ARNs are exposed to unauthenticated requests.

We applied an RCP to a publicly accessible bucket that Denies only s3:GetObject for denied.txt. Before applying, both denied.txt and allowed.txt were readable unauthenticated with HTTP 200.

After applying, unauthenticated requests to denied.txt returned 403.

HTTP/1.1 403 Forbidden
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>...</RequestId><HostId>...</HostId></Error>

When the same object was accessed from an authenticated principal within the same organization, the RCP ARN was returned.

An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket-public/denied.txt" with an explicit deny in a resource control policy: arn:aws:organizations::111122223333:policy/o-exampleorgid/resource_control_policy/p-examplercpid2

As a control, allowed.txt in the same bucket remained readable unauthenticated with HTTP 200.

HTTP/1.1 200 OK
this object is publicly readable

The response body for unauthenticated requests did not contain a policy ARN. We only tested unauthenticated requests; access from authenticated principals outside the organization was not tested. The S3 User Guide states that cross-account requests from outside the same organization return a generic Access Denied.

Difference Between CloudTrail and Server Access Logs

When investigating after receiving a report, the full error message may not be available. We checked whether the policy ARN can be traced retroactively from CloudTrail or S3 server access logs.

CloudTrail S3 data events record the full error message in errorMessage. The policy ARN is also preserved there.

{
  "eventVersion": "1.11",
  "eventName": "GetObject",
  "errorCode": "AccessDenied",
  "errorMessage": "User: arn:aws:sts::123456789012:assumed-role/ExampleRole/ExampleSession is not authorized to perform: s3:GetObject on resource: \"arn:aws:s3:::amzn-s3-demo-bucket-scp/test.txt\" with an explicit deny in a service control policy: arn:aws:organizations::111122223333:policy/o-exampleorgid/service_control_policy/p-examplescpid",
  "additionalEventData": {
    "httpStatusCode": 403
  }
}

However, object operations are not recorded in management events. To use this for post-incident investigation, data events must be enabled in advance. Please note that recording data events incurs charges.

For S3 server access logs, we created a delivery configuration to CloudWatch Logs and checked the recordFields included in the create-delivery response.

schema_version_id, bucket_arn, bucket_name, request_time, bucket_owner_id, remote_ip,
requester, request_id, operation, key_name, request_uri, http_status, error_code,
bytes_sent_size, object_size, total_duration, turn_around_duration, referer, user_agent,
version_id, host_id, signature_version, cipher_suite, authentication_type, host_header,
tls_version, access_point_arn, acl_required, source_region

While http_status and error_code are present, there are no fields corresponding to the denial reason or policy ARN. Therefore, S3 server access logs alone cannot identify the cause of a Deny. Tracing back to the policy after the fact requires CloudTrail data events.

Summary

S3 403 Access Denied errors now include the ARN of the denying policy, making it easier to investigate the cause of explicit Denies. If Deny rules are managed using policies that have ARNs, you can navigate directly from the error message to the relevant policy.

On the other hand, for policies without ARNs—such as inline policies and bucket policies—only the type is shown, as before. If ease of investigation is a priority, managing Deny rules as customer managed policies is an effective approach.

Also, when an RCP denies an unauthenticated request to a public bucket, the response is still a generic Access Denied. There is no risk of policy ARNs being exposed to unauthenticated clients outside the organization.

This S3 update is part of a broader effort to improve error messages for explicit Denies caused by policies such as SCPs and RCPs. For more details, see the AWS Security Blog post.

References


そのマルチアカウント運用、気合いで支えていませんか

Organizations や Control Tower で土台は作れても、アカウントもポリシーも増えるほど、運用は「詳しい一人」に寄りかかっていく。属人化が限界を迎える前に、組織として回す仕組み=CCoEへ。5,600社の支援から得た立ち上げの型を、無料資料にまとめました。

CCoE総合支援

組織で回す仕組みの資料をもらう

Share this article

AWSのお困り事はクラスメソッドへ