---
cuppy_hub: aws
cuppy_path: guides/iam/fix-access-denied.md
cuppy_section: docs
cuppy_doc: JSHZ-DGYG
cuppy_doc_uuid: 352d46be-22e9-4c60-9833-f8c9a33c2bdb
cuppy_permalink: https://cuppy.io/aws/d/JSHZ-DGYG
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/iam/fix-access-denied
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 9848f5474aaed094bf4e05827e640da53158472323fb0894b86e8029d7f5854b
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Diagnose an AccessDenied

**Do not start by widening a policy.** An explicit Deny overrides every Allow, so adding permissions fixes nothing when a Deny matched, and a broad Allow added to "see if it works" usually stays. Read the message, identify the one policy type that refused, and change only that.

## 1. Capture the exact message

Copy the full error as the caller received it: the principal ARN, the action, the resource ARN if present, and the final clause. The final clause is the diagnosis:

- `because no <type> policy allows the <action> action` — **implicit deny**: nothing in that policy type granted it. You will add an Allow.
- `with an explicit deny in a <type> policy` — **explicit deny**: a Deny statement matched. You will narrow the Deny or meet its condition.
- `with an explicit deny in a <type> policy: <policy ARN>` — the same, and AWS has named the exact policy. Go straight to it.
- No clause, or a bare `with an explicit deny` — the service does not use the detailed format. Every policy type is a suspect.
- `Encoded authorization failure message: <long string>` (EC2 and some other services) — decode it first with `aws sts decode-authorization-message`, which needs `sts:DecodeAuthorizationMessage`; the decoded JSON names the denied action and context (`reference/ec2/errors.md`).

If you don't have the message (a denial inside a service, a CI job, someone else's call), CloudTrail records it in `errorCode` / `errorMessage`: `guides/cloudtrail/find-who-did-what.md`.

Search the hub for the clause: `docs {"command": "search", "query": "<the clause>", "scope": "reference"}`. The matching entry in `reference/iam/errors.md` has the ranked causes for that policy type.

## 2. Confirm who is calling

The principal in the message is the identity that actually signed the request. If it is not the one you expected, stop here: the problem is credentials, not policy.

```
aws sts get-caller-identity --debug
```

The debug output shows which credential source was used. Fix the source using `reference/sts/credential-sources.md`, then retry before changing any policy.

## 3. Decide whether the request is cross-account

Compare the account ID in the principal ARN with the account ID in the resource ARN.

- **Same account:** an Allow in *either* the identity-based policy or the resource-based policy is enough, unless something denies.
- **Different accounts:** *both* must allow — the caller's identity policy in its account and the resource policy in the resource's account. Fixing one side never fixes the other. If the target is a role, the role's trust policy is the resource policy, and the caller also needs `sts:AssumeRole` on the role in its own account.

## 4. Find the refusing policy

Use the policy type from step 1:

| Named type | Where it lives | Who can change it |
|---|---|---|
| identity-based | policies attached to the principal (and its groups, for a user) | the account's IAM administrators |
| resource-based | the resource's own policy (bucket, secret, key, queue) | the resource owner |
| role trust | the target role's trust policy | the role's account |
| permissions boundary | the boundary attached to the principal | whoever set the boundary |
| session | the session policy passed when the credentials were issued | whoever requested the credentials |
| service control (SCP) | AWS Organizations, on the account and every OU above it | organization administrators |
| resource control (RCP) | AWS Organizations, applied to the resource's account | organization administrators |
| VPC endpoint | the endpoint's policy | the endpoint's owner |

If the message includes a policy ARN, read that policy. The ARN is only shown when the caller is in the same account or organization as the policy; if you are calling across organizations and get no ARN, you must read every policy of the named type in scope. Only one policy type is reported even when several refused, so after fixing one, retry and read the next message.

## 5. Read the statement against the request

For an **implicit deny**, look for an Allow and check, in order: the action (including wildcards), the resource ARN character by character, then every condition key. Condition key names are case-insensitive, so request keys that differ only in case can collide.

For an **explicit deny**, find the Deny that matches the action and check its conditions. Typical matches: an MFA requirement (`aws:MultiFactorAuthPresent` with `BoolIfExists` set to `false` also blocks long-term access keys), a required VPC endpoint (`aws:SourceVpce`), an allow-list keyed on `aws:userid`, or a Region or service guardrail in an SCP or RCP. Several condition keys in one Deny statement must all match for it to apply.

## 6. Check the decision before changing anything live

```
aws iam simulate-principal-policy \
  --policy-source-arn <principal ARN from the message> \
  --action-names <action> \
  --resource-arns <resource ARN>
```

It evaluates the action without performing it. Know its blind spots: it does not fetch resource-based policies (pass one with `--resource-policy`), it does not support RCPs, and its result can differ from the live environment, so confirm with the real call afterwards. It reports SCP effects. The principal must be a user, group or role, not an assumed-role session.

## 7. Make the smallest change, then retry

- Implicit deny: add an Allow for exactly that action, on exactly that resource, in the policy type that was named.
- Explicit deny: narrow the Deny, add an exception condition for the principal that needs access, or meet the condition (sign in with MFA, route through the required endpoint).
- SCP, RCP, or another account's policy: you cannot fix this yourself. Hand your human the message, the policy type, the policy ARN if given, and the exact change needed, addressed to the owner from the table in step 4.

Retry the original call. A new denial naming a different policy type means the first fix worked and another layer also refuses; return to step 1 with the new message.

## If you are stuck

- The message matches no entry: search the hub with the exact text and no `role` filter. Credential errors (`ExpiredToken`, `InvalidClientTokenId`) are in `reference/sts/errors.md`.
- You signed the request yourself rather than through an SDK: a bad signature can also produce a denial; switch to the SDK to rule it out.
- The evaluation model itself is unclear: `reference/iam/policy-evaluation.md`.
- A console step in a fix does not match what your human sees: `guides/when-the-console-has-changed.md`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
