Join our Newsletter — 33% off our NHI Course
Home› Guides› AWS AccessDenied Troubleshooting Guide: Reading IAM Denials and…
Guide Cloud Security

AWS AccessDenied Troubleshooting Guide: Reading IAM Denials and Fixing the Right Policy

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 11 November 2026 16 min read
On this page

An AWS AccessDenied error tells you that a request failed authorisation, but the fix depends on which of up to eight policy types said no, and guessing usually ends with an over-broad Allow. This guide shows how to read the denial message, which policy type to inspect for each message form, how to decode encoded authorisation messages, and how to confirm the fix in CloudTrail and the IAM policy simulator. It covers cross-account requests, assumed-role sessions, S3 and KMS. It is written for cloud engineers, platform teams and identity practitioners who own IAM for humans and for machine identities such as roles, pipelines and workloads.

Key takeaways

  • Read the tail of the message first. "with an explicit deny in a ... policy" and "because no ... policy allows" name the policy type, and for explicit denies often the policy ARN, which tells you exactly where to look.
  • Explicit and implicit denies have opposite fixes. An implicit deny needs a missing Allow added in the named policy type; an explicit deny needs a Deny statement found and narrowed, never an extra Allow, because an Allow cannot override a Deny.
  • Identify the real caller before reading any policy. Most "the policy is correct but it still fails" cases are the wrong principal: a different role session, a stale credential in the chain, or a pipeline running as a role nobody expected.
  • Cross-account and KMS have extra gates. Cross-account requests need an Allow in both accounts, and KMS needs the key policy to allow the caller or to delegate to IAM before any IAM policy has effect.
  • Fix narrowly and prove it. Reproduce with the simulator or a test call, change one statement, confirm the CloudTrail errorCode disappears, and never close a ticket with "Action": "*".

How AWS reaches a deny

The default is deny. A request succeeds only when some policy allows it, nothing explicitly denies it, and every filtering policy also permits it. The relationships that matter for troubleshooting:

  • Explicit deny wins everywhere: a Deny in any applicable policy overrides any Allow, including Deny statements in SCPs, RCPs, permissions boundaries, identity-based and resource-based policies.
  • Identity and resource policies are a union in the same account: within one account, an Allow in either the identity-based policy or the resource-based policy is enough.
  • Boundaries, session policies, SCPs and RCPs are intersections: they grant nothing. They only limit what identity-based policies can grant, so the action must be allowed by the identity policy and by each filter.
  • Cross-account is two evaluations: the trusted account (where the principal lives) and the trusting account (where the resource lives) are evaluated separately, and the request is allowed only if both return Allow. A Deny in either account denies the request.
  • Temporary credentials cannot exceed their source: an assumed-role session has the intersection of the role's identity-based policies and any session policies passed at AssumeRole time (inline Policy or up to 10 PolicyArns).

Reading the error message

Most AWS services return a message in this shape: User: <principal ARN> is not authorized to perform: <action> on resource: <resource ARN> because <context>. The "on resource" part is not always present, and AWS documents that some services do not use this format at all. Where the context is present, it takes one of two forms, quoted here from the IAM troubleshooting page:

  • Explicit deny: "with an explicit deny in a service control policy", optionally followed by the policy ARN. Inline policies have no ARN, so the type is named without one.
  • Implicit deny: "because no identity-based policy allows the codecommit:ListRepositories action", with the policy type substituted.

Two limits matter. If several policy types deny the request, only one type appears in the message. If several policies of the same type deny it, the message does not say how many. Fixing the named policy can therefore reveal a second denial from another type, which is normal and not a sign the first fix failed.

Message to policy type reference

Phrase in the messagePolicy typeWhere to lookTypical fix
no identity-based policy allowsIdentity-based (user, group or role)Policies attached to the principal; for a session, the role's policiesAdd a scoped Allow for the action and resource ARN
explicit deny in an identity-based policyIdentity-basedDeny statements, often with conditions such as aws:SourceIp or aws:RequestedRegionCorrect the condition or exempt the specific case
no resource-based policy allowsResource-based (bucket, queue, topic, secret, key)The resource's policy in the resource accountName the principal and action; required for cross-account
no permissions boundary allowsPermissions boundaryThe managed policy set as the principal's boundaryExtend the boundary, or confirm the action is meant to be out of bounds
no session policy allowsSession policyThe Policy or PolicyArns passed by the caller or brokerChange what the broker passes at assume time
service control policySCP (AWS Organizations)SCPs on the account, its OUs and the rootRequest a scoped exception from the organisation owners
resource control policyRCP (AWS Organizations)RCPs on the resource's account pathAs above; RCPs act on the resource side
VPC endpoint policyVPC endpoint policyThe endpoint the request travelled throughAdd the action or resource to the endpoint policy
no role trust policy allows sts:AssumeRoleRole trust policyThe target role's trust relationshipName the caller as principal; check conditions such as sts:ExternalId

When the message tells you nothing

  • Encoded authorisation message: some operations, notably EC2 calls failing with Client.UnauthorizedOperation (HTTP 403), return an encoded message instead. Decode it with aws sts decode-authorization-message --encoded-message <value>. The output shows whether the deny was explicit, the matched statements, the principal, action, resource and the condition key values in the request context. The caller needs sts:DecodeAuthorizationMessage; AWS encodes the message deliberately because it can reveal privileged details, so grant that permission to operators, not to every workload. The decode fails with InvalidAuthorizationMessage if the value has expired or contains line breaks.
  • Bare "Access Denied": Amazon S3 returns only a generic message for cross-account requests from outside your organisation, for directory buckets, and for some VPC endpoint cases. Work from CloudTrail and the policy checklist below.

Step-by-step diagnosis

  1. Capture the exact request. Record the full error text, the API action (for example s3:GetObject, not "S3 read"), the resource ARN, the region and the request ID (x-amz-request-id for S3). The action name in the error is the IAM action to grant, which is not always the API call name the SDK shows.
  2. Confirm the caller. Run aws sts get-caller-identity from the same environment, process and profile. Compare the returned ARN with the principal in the error. An assumed-role/<role>/<session> ARN means the role's policies apply; a user ARN where you expected a role means the credential chain found static keys first (environment variables or a credentials file). See the AWS Access Keys Security Guide.
  3. Classify the denial. Use the message to policy type table. If the message names a type and an ARN, open that policy and search for Deny statements that match the action. If it names a type with no ARN, search every policy of that type (all SCPs in the account path, all inline policies on the role).
  4. Check conditions, not just actions. Many denials come from condition blocks: aws:SourceIp fails when the call comes through a VPC endpoint or an AWS service, aws:RequestedRegion blocks a region, aws:PrincipalOrgID excludes a partner account, and tag conditions (aws:ResourceTag/, aws:RequestTag/) fail when a tag is missing. AWS notes that condition key names are case-insensitive, so two keys differing only by case can collide.
  5. Check the resource side. For services with resource-based policies (S3, SQS, SNS, Secrets Manager, KMS, Lambda), read the resource policy. Same account: either side may grant. Cross-account: both must grant, and the resource policy must name the principal or its account.
  6. Check the guardrails. Permissions boundary on the principal, session policies passed by the broker, SCPs and RCPs on the account path, and the VPC endpoint policy if the request used an endpoint. AWS notes that when a VPC endpoint policy denial is not logged in CloudTrail, the endpoint owner may be a different account from the caller or the target.
  7. Reproduce before changing anything. Use the IAM policy simulator (aws iam simulate-principal-policy) with the same action, resource ARN and context keys. Then make one narrow change and re-run the original request.
  8. Confirm in CloudTrail. The failed event carries errorCode and errorMessage; the fix is proven when the same eventName from the same principal succeeds and no new denial appears from another policy type.

Service-specific causes: S3 and KMS

Amazon S3

CauseHow to recognise itFix
Bucket policy DenyExplicit deny; common conditions are aws:SecureTransport, aws:SourceVpce, encryption headersSatisfy the condition (HTTPS, correct endpoint, required header) rather than remove the Deny
Object owned by another accountBucket owner gets denied on objects uploaded cross-account while ACLs are enabledSet Object Ownership to Bucket owner enforced (ACLs disabled) after moving ACL grants into the bucket policy
Block Public AccessMessage ends "because public policies are prevented by the BlockPublicPolicy setting in S3 Block Public Access" (or the BlockPublicAcls equivalent)Use a non-public grant (named principals, CloudFront origin access control); all four settings are on by default for new buckets since April 2023
SSE-KMS key permissionsUpload or download fails although S3 permissions look rightGrant kms:GenerateDataKey for uploads and kms:Decrypt for downloads and multipart uploads, in both IAM and the key policy; the AWS managed key aws/s3 only works for principals in the key's own account

AWS KMS

  • Key policy first: the key policy is the primary control. IAM policies only have effect if the key policy contains a statement allowing the account root principal (the default "enable IAM policies" statement). If that statement was removed, no IAM policy can grant access to the key.
  • Cross-account keys: the key policy in the key's account must allow the caller's account, and an IAM policy in the caller's account must also allow the role to use the key. Both are needed.
  • Grants and guardrails: check grants on the key (aws kms list-grants) and remember that SCPs and VPC endpoint policies can still deny a request that the key policy and IAM allow.
  • Condition keys: kms:ViaService restricts use to calls made through a named service, and kms:EncryptionContext: keys require a specific encryption context; a direct SDK call or a different context then fails. See the AWS KMS Security Guide.

Assumed roles, sessions and cross-account requests

Machine identities have layered permissions: trust policy, role policy, boundary, session policy and organisation guardrails.

  • Cannot assume the role: "no role trust policy allows the sts:AssumeRole action" means the target role's trust policy does not name the caller, or a condition (sts:ExternalId, aws:PrincipalOrgID, aws:SourceIdentity) is not met. The caller also needs sts:AssumeRole on the role ARN in its own identity policy when the accounts differ. See the AWS STS and AssumeRole Guide.
  • Assumed but still denied: check for session policies. Brokers, CI systems and identity platforms often pass a session policy to scope down a role; the session can then do less than the role.
  • Federated workloads: errors at the token exchange step (InvalidIdentityToken, IDPRejectedClaim) are not AccessDenied problems. Use the Multi-Cloud Workload Identity Troubleshooting Guide for those.
  • Cross-account resource access: the principal needs an identity-based Allow in its account and the resource needs a resource-based Allow naming that principal in the other account. Where a service has no resource-based policy, assume a role in the resource account instead.
  • Organisation guardrails across accounts: SCPs restrict principals in member accounts, and RCPs restrict resources in member accounts. A cross-account call can therefore be blocked by the caller's SCP or the resource owner's RCP. See the AWS Organizations and SCPs Guide and the AWS Resource Control Policies and Data Perimeter Guide.

Tools for diagnosis: choosing the right one

ToolWhat it showsLimitsUse it when
Error messagePolicy type, sometimes policy ARNOnly one type shown; some services give no contextAlways first
sts decode-authorization-messageExplicit or implicit, matched statements, request contextOnly for operations that return an encoded message; needs a sensitive permissionEC2 and other encoded 403s
IAM policy simulatorAllow or deny per action and resource, matched statementNo RCP support; differs from live results for VPC endpoint policies, role chaining and multiple resource policies; only auto-fills some context keysTesting a change before applying it
CloudTrailerrorCode, errorMessage, userIdentity, sourceIPAddress, vpcEndpointIdData events (S3 object, Lambda invoke) must be enabled to see those denialsFinding the real caller and confirming the fix

Small teams can resolve most cases with the error message, get-caller-identity and CloudTrail Event history. Large organisations should group denials by principal, action and policy type in a SIEM, because one broken guardrail usually hits many roles. Never loosen an SCP, RCP or boundary to unblock one workload: request a scoped exception tested on a sandbox OU. See the AWS CloudTrail Guide and the IAM Access Analyzer Guide.

Fixing safely: change, test, roll back

  1. Write the smallest grant: the exact action from the error, the specific resource ARN, and any conditions the rest of your estate relies on. Validate the policy with Access Analyzer policy validation before saving.
  2. Test in a lower environment or with the simulator: run simulate-principal-policy with the original action, resource and context, including keys such as aws:RequestedRegion; the simulator denies when a referenced key has no value.
  3. Apply as a new policy version: customer managed policies keep up to five versions, so a bad change can be rolled back by setting the previous version as default. Inline policies have no history, which is one reason to avoid them for production roles.
  4. Verify and record: confirm the CloudTrail event now succeeds, log the ticket with the policy type and the change, and schedule a review to remove the grant if it was temporary.

Where identity fits

AccessDenied is an identity signal, not only a support ticket. For machine identities a denial means a deployment change, drift (a guardrail tightened) or stolen credentials probing what they can reach. Repeated denials across many services from one access key or role, especially List* and Describe* calls, match discovery behaviour such as ATT&CK T1580 Cloud Infrastructure Discovery and T1526 Cloud Service Discovery performed with valid cloud accounts (T1078.004). Treat a burst of denials from a workload identity that normally makes a stable set of calls as a detection, not a fix request. See the How to detect AWS access key use from unusual locations guide and the How to Find IAM Privilege Escalation Paths in AWS guide.

Detection and measurement

  • Denial events: alert on CloudTrail events with errorCode of AccessDenied, AccessDeniedException or Client.UnauthorizedOperation, grouped by userIdentity.arn, and flag principals with denials across many distinct eventSource values in a short window.
  • Guardrail hits: track denials whose message names an SCP or RCP; a spike after an organisation policy change shows the blast radius.
MetricWhat it showsHealthy direction
Median time to resolve an AccessDenied ticketWhether teams can read denials without escalationFalling
Share of fixes that add a wildcard action or resourceWhether fixes are creating over-privilegeNear zero
Workload roles with denials outside their normal API setPossible credential misuse or unreviewed code changesInvestigated within one day
Temporary grants removed on scheduleWhether break-glass fixes are cleaned up100 percent

Common failure patterns

  • Fixing with a wildcard: adding "Action": "s3:*" or "Resource": "*" to make the error go away. Broad role permissions turn a single foothold into a data breach, as the Capital One breach showed when a firewall's role could list and read far more buckets than it needed.
  • Adding an Allow against an explicit Deny: it never works, and leaves a useless grant behind. Find the Deny and fix its condition.
  • Debugging the wrong principal: editing the role the team expects while the process is using an old access key or a different profile. Always run get-caller-identity from the failing context.
  • Forgetting the KMS key: S3, Secrets Manager or EBS permissions are fixed but the customer managed key policy does not allow the role, or the key policy no longer delegates to IAM.

Framework mapping

FrameworkReferenceRelevance
NIST SP 800-53 Rev 5AC-3 Access Enforcement; AC-6 Least PrivilegePolicy evaluation is access enforcement; narrow fixes keep least privilege
NIST SP 800-53 Rev 5AU-6 Audit Record Review, Analysis, and ReportingReviewing CloudTrail denials for misuse
NIST CSF 2.0PR.AA-05; DE.CM-03Permissions managed with least privilege; monitoring of account activity
MITRE ATT&CK v20T1078.004 Cloud Accounts; T1580 Cloud Infrastructure Discovery; T1526 Cloud Service DiscoveryDenial bursts from valid credentials during discovery

Practitioner checklist

  • Record the exact action, resource ARN, region, request ID and full error text before changing anything.
  • Run aws sts get-caller-identity from the failing context and compare it with the principal in the error.
  • Classify the denial as explicit or implicit and identify the policy type from the message.
  • For encoded 403s, decode with sts decode-authorization-message using an operator role, not the workload.
  • Check conditions (aws:SourceIp, aws:RequestedRegion, aws:PrincipalOrgID, tag keys) as well as actions.
  • For cross-account and KMS requests, confirm both sides grant access and the key policy delegates to IAM.
  • Reproduce with the IAM policy simulator, keeping its RCP and VPC endpoint limits in mind.
  • Apply the smallest grant as a new managed policy version so you can roll back.
  • Monitor denial bursts from workload identities as a security signal.

Standards and references

Related NHI Mgmt Group resources: AWS IAM Policies and Roles Guide · AWS STS and AssumeRole Guide · AWS Organizations and SCPs Guide · Kubernetes RBAC Forbidden Errors Troubleshooting Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 11 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org