"An Error Occurred (AccessDenied)" in AWS CLI: Decode It
If the AWS CLI just threw "An error occurred (AccessDenied)" or "UnauthorizedOperation" at you, the direct fix is this: copy the long string after "Encoded authorization failure message" out of the error, then run aws sts decode-authorization-message against it. Here's the part almost nobody expects: AWS already wrote the answer to your permission problem inside the error itself. It just locked that answer behind one more permission you probably don't have yet.
Jake found out about the encoded-message problem the hard way. His shop stores signed repair waivers in an S3 bucket so customers can pull up a copy months later without him digging through a filing box. One Saturday the upload script that runs at closing time started throwing "UnauthorizedOperation" with a wall of encoded gibberish, three customers' waivers never made it up, and he spent the morning convinced S3 itself was broken.
It wasn't. It was an IAM policy someone had tightened the week before, and the whole story was sitting inside that gibberish the entire time — he just didn't have the one extra permission needed to read it.
Which error do you actually have?
Before decoding anything, look at what your terminal actually printed. The wording tells you which path to take.
"An error occurred (AccessDenied) when calling the X operation" — with a readable reason
Most S3 errors are this shape. AWS names the action, the resource, and often the exact reason right in the plain text — no encoding, no decode step. Go to the S3 section.
"...is not authorized to perform... Encoded authorization failure message: AQoJb3JpZ2lu..."
This is EC2, and a growing set of other services, refusing to tell you the reason in plain text because the reason might reveal something about policies you're not supposed to see. This is the one you actually need to decode.
"Unable to locate credentials" or "The config profile could not be found"
This isn't a permissions problem at all — it's the CLI not finding any credentials to send. Different fix, covered in the local CLI section and the Windows credentials section below.
Ethan explains it to Jake this way: "Think of it like a locked note left on your desk that just says 'you can't come in.' Sometimes the note tells you why right there. Sometimes it's written in code, and to read the code you need a second key nobody hands out by default. Same building, two very different notes."
🙋♂️ Jake's Reality Check
"Honestly, can't I just make myself an admin for five minutes and make this go away?"
Ethan doesn't hedge: "You can, and it'll work — that's exactly the problem." It's how a phone shop's returns spreadsheet ends up world-writable six months later, because nobody ever went back and un-admined the account. Decoding the message takes thirty seconds and tells you the one permission you're actually missing.
How to decode the encoded authorization message
The whole mechanism exists because a denied request can contain privileged information about why it was denied. So AWS encodes it, and decoding it is itself a permission-gated action, using the Security Token Service.
"Wait," Jake said when I walked him through this, "so AWS wrote down exactly what went wrong, and then hid it from me on purpose?" Ethan's answer: "Correct. And it's not being difficult — some of what's in there could be details about a completely different team's policy that you shouldn't see. The lock is doing its job. You just need a key too."
- Copy the full string after "Encoded authorization failure message:" from your terminal — it's long, often 1,000+ characters, and truncating it even by one character breaks the decode.
- Confirm your own IAM identity holds
sts:DecodeAuthorizationMessage. The decode call is evaluated against your own permissions and context, not against the identity that triggered the original error. - Run:
aws sts decode-authorization-message --encoded-message "YOUR_STRING" --query DecodedMessage --output text - If your shell has
jqinstalled (it ships by default in AWS CloudShell), pipe the output through it for readable formatting: add| jq '.'to the end of the command above. - Read the
failuresarray in the output — it lists the exact action and resource ARN that had no applicable Allow statement. - Add the missing permission to the narrowest policy you can — the user, the role, or a single statement — and retry the original command.
"I got AccessDenied decoding the AccessDenied error"
This happens constantly, and it isn't a bug — the decode action itself requires an explicit grant. By default, essentially nobody has it. If you run the decode command and get back User: ... is not authorized to perform: (sts:DecodeAuthorizationMessage), that's the whole story. Attach this statement to your own user or role:
Ethan has an opinion about this one: "It's the most backwards-feeling corner of the whole permission model — the error is trying to help you, and IAM still makes you ask permission to read the help. I get why, but it trips up more people than almost anything else in this post." For the users, groups, and policy statements this assumes you already know, our walkthrough of AWS IAM covers the model.
⚠️ What this actually breaks
Don't hand sts:DecodeAuthorizationMessage to every user account-wide as a reflex. A decoded message can surface details about SCPs, permission boundaries, and condition keys that a user was deliberately not shown when the plain error came back. Grant it to the people troubleshooting, not the whole org.
What the decoded JSON is actually telling you
The output isn't plain English, and it isn't trying to hand you a fix on a plate — it's a factual record of the evaluation. Four fields matter most:
allowed — will be false. You already knew that from the error, but it confirms the decode worked.
explicitDeny — this is the field people skip and shouldn't. If it's true, somewhere a policy has a Deny statement matching this action, and no amount of adding Allow statements elsewhere will fix it. If it's false, you're dealing with an implicit deny: there was simply no matching Allow anywhere, so adding one will work.
failures — the action and resource ARN with no applicable policy. This is usually the whole answer.
context.principal — the ARN of the identity that was actually making the call. This catches a surprisingly common mistake: you assumed the CLI was using your named user, but it's actually using an assumed role, an EC2 instance profile, or a different named profile than you expected.
✅ Why this is the one to use
Decode-then-add-one-statement beats "just attach AdministratorAccess" for a reason that has nothing to do with paranoia: the decoded message tells you exactly which single permission is missing, so the fix you make is the fix you actually needed — not a blanket grant you'll forget to walk back.
S3 AccessDenied: no encoded message, six real causes
Jake's waiver bucket, as it turned out, wasn't an encoded-message problem at all — his upload script hit S3 directly and got a plain-text AccessDenied. aws s3 cp, aws s3 ls, and aws s3api commands almost never return an encoded message; S3 tells you plainly. Here's what to check, roughly in order of how often each one is the actual culprit:
| Cause | How you'd spot it | Fix |
|---|---|---|
| IAM policy just doesn't allow it | Same error for every object in every bucket | Add the specific s3: action to the user or role's policy |
| Object owned by a different account | Fails on some objects in the bucket, not others | Turn on the bucket owner enforced setting to disable ACLs, or have the uploading account grant object ACL access |
| KMS-encrypted object, no key access | Works on unencrypted objects, fails only on SSE-KMS ones | Grant the caller kms:Decrypt (or kms:GenerateDataKey for uploads) on the specific key |
| S3 Block Public Access on the bucket | Bucket policy looks correct, still denied | Review the four Block Public Access settings on the bucket; they override a permissive bucket policy |
| VPC endpoint policy, calling from inside a VPC | Works from your laptop, fails from an EC2 instance | Add the bucket ARN to the endpoint policy's allowed resources |
| Requester Pays bucket, no flag set | Cross-account bucket, error mentions payer | Add --request-payer requester to the CLI command |
Here's the one that trips up people who think they've covered every base: if the IAM user and the bucket are in the same account, you only need permission in one place, not both — either the IAM identity policy or the bucket policy allowing you is enough. Jake's case turned out to be a stray Deny statement someone had added to the bucket policy "for safety" the week before, while his IAM user's own policy had allowed the upload the whole time.
EC2's "not authorized to perform this operation"
Launching, stopping, or modifying an EC2 instance is the classic source of the encoded message — it's the example AWS itself uses in the STS documentation. If you're new to what EC2 actually is before troubleshooting permissions around it, this explainer covers the instance/AMI/security-group basics this section assumes.
Before decoding anything for an EC2 launch failure specifically, there's a faster diagnostic: add --dry-run to the same command. A dry run evaluates your permissions without actually performing the action, and returns DryRunOperation (meaning you'd have been allowed) or the same UnauthorizedOperation with an encoded message (meaning you genuinely lack the permission) — without spinning up anything you'd have to clean up.
When "permission denied" isn't AWS at all
Search "aws cli permission denied" and you'll get two completely different problems mixed together. Everything above is AWS's own IAM engine refusing an authenticated request. But if your terminal prints an operating-system-level "Permission denied" — not "AccessDenied" — before AWS is even involved, that's your shell refusing to read a local file, usually the credentials file itself.
On Linux and macOS this shows up as something like [Errno 13] Permission denied: '/home/you/.aws/credentials'. It means the file's own file-system permissions block your user from reading it — often because it was created by sudo or a different user account. Fix the file's ownership and permissions rather than touching anything in AWS.
AWS CLI on Windows: where credentials actually live
On Windows the CLI stores what aws configure collects in two plain-text files: C:\Users\YourName\.aws\config and C:\Users\YourName\.aws\credentials. If a value exists in both files for the same profile, the credentials file wins for the access key and secret key.
Two Windows-specific traps cause an outsized share of "it worked yesterday" reports:
Wrong user profile. If you ran a setup script as Administrator, or under a service account, the credentials landed under that account's .aws folder — not yours. Running the same command from your normal login can't see them, and you'll get "Unable to locate credentials" that looks identical to a permissions error at first glance.
A stray environment variable overriding everything. If AWS_CONFIG_FILE or AWS_SHARED_CREDENTIALS_FILE was ever set with setx, it silently redirects the CLI to a different file forever, even after you edit the "normal" one. Run aws configure list in PowerShell; the LOCATION column for each value tells you whether it's coming from a file, an environment variable, or your named profile, which settles this in seconds instead of guesswork.
Setting up AWS CLI access correctly the first time
If you've landed here before ever getting a working setup, most of this post is premature — start here instead.
- Create an IAM user (or, if your organization uses it, an IAM Identity Center account) — never use the account root user for day-to-day CLI work.
- Attach a policy with only the permissions the task needs. If you don't know yet what you'll need, start narrow and add via the decode-message workflow above as real AccessDenied errors tell you what's missing.
- Generate an access key for that user (or configure SSO, if available), and run
aws configureto store it. - Verify the identity the CLI is actually using with
aws sts get-caller-identity— it prints back the exact ARN, which catches "wrong profile" mistakes before they turn into an hour of confused troubleshooting. - Test the specific action you need with the smallest possible command before scripting anything larger around it.
Explicit deny vs. implicit deny — why this matters more than people think
By default every request to AWS is denied — that's implicit denial, and it's the entire foundation of the permission model. A request only succeeds when some policy explicitly allows it. An explicit deny is different and stronger: it's an active "Effect": "Deny" statement that matches your request, and it overrides every Allow anywhere else.
Jake asked the obvious question: "So if I'm the account owner, nothing can stop me?" Ethan: "Actually, an org-level policy can cap you even as the owner of your own account, if your business is part of a bigger organization in AWS. Think of it like a franchise owner who still can't override rules set by corporate — you can run your store, but corporate sets the ceiling."
| Layer | Can it grant access? | What a conflict here means |
|---|---|---|
| Identity-based policy (user/role) | Yes | Add the missing Allow statement here for most fixes |
| Resource-based policy (S3 bucket policy, etc.) | Yes, same-account | An Allow here can substitute for the identity policy within one account |
| Permissions boundary | No — caps only | Narrows the identity policy; the action needs both to allow it |
| Organization SCP | No — caps only | Must not deny; it can't grant permissions IAM doesn't already give |
| Session policy (assumed role) | No — caps only | Intersects with the role's own permissions for that session |
This is the popular advice that's actually wrong: "just check the user's policy." An administrator can attach a fully permissive identity policy and still get AccessDenied all day, because an SCP at the organization level, or a permissions boundary on that specific user, is capping what the identity policy can grant. A decoded explicitDeny: true with no matching Deny anywhere in the identity or bucket policy you control is a strong signal to go look at the organization level.
When decoding doesn't fully solve it
A few situations are worth naming plainly rather than pretending a decode always finishes the job. Jake, once he had a working IAM user for his shop, still ran into one of them: he locked himself out of his own bucket.
Root-owned bucket policies can lock out the root user. If an IAM user with full S3 access writes a bucket policy that lists only their own ARN as the allowed principal, the account's root user can end up denied on their own bucket. It's a genuinely counterintuitive result — the fix is to sign in as root and edit the bucket policy to explicitly include the root ARN as a principal.
Cross-account denials from an Access Point. If your bucket sits behind an S3 Access Point, the Access Point has its own resource policy separate from the bucket policy, and both must allow the request. A correct bucket policy plus a missing Access Point statement still produces AccessDenied.
Ethan is upfront about the limit here: "If it's an SCP in an organization you don't manage, or a bucket you don't own, there's nothing you can decode your way out of. You escalate to whoever does own it. That's not failure, that's just where your access ends." Not every AccessDenied is solvable from where you're sitting, and that's fine to admit.
Testing a policy before you hit AccessDenied again
Once you've fixed the immediate error, the same failure will happen again for the next teammate unless someone checks the policy before it's needed. The IAM policy simulator does that without making a real request:
The output tells you which statement produced the allow or deny decision, which is the same information the decode command gives you after the fact — except this runs before anyone's workflow breaks. Jake's shop now runs this against its waiver-upload user every time someone touches the bucket policy, precisely because his lost Saturday was one Saturday too many.
Frequently asked questions
What does "encoded authorization failure message" mean?
It's AWS's way of telling you a request was denied without revealing the exact policy reason in plain text, because that reason might contain details you weren't meant to see. The information is still there, just base64-encoded and gated behind a separate permission to read it.
How do I decode an AWS CLI encoded authorization message?
Run aws sts decode-authorization-message --encoded-message "YOUR_STRING" --query DecodedMessage --output text, from an identity that holds sts:DecodeAuthorizationMessage. Pipe it through jq for readable formatting if you have it installed.
Why do I get AccessDenied on the decode command itself?
Decoding requires the sts:DecodeAuthorizationMessage permission, and almost no identity has it by default. Add that action to your own policy, or ask whoever manages IAM in your account to add it.
Can any IAM user decode any encoded message?
The decode call is evaluated against the caller's own permissions and context, so you need sts:DecodeAuthorizationMessage yourself — it isn't inherited from whoever originally hit the error. Reports of extra friction decoding a colleague's specific message exist but aren't confirmed in AWS's own documentation, so treat that edge case as unresolved rather than settled.
Does decoding the message tell me exactly which permission to add?
It tells you the action and the resource that failed, and whether the denial was explicit or implicit. For an implicit deny, adding an Allow for that exact action usually resolves it. For an explicit deny, you have to find and remove the Deny statement causing it instead.
Why am I getting AccessDenied on S3 even though my IAM policy allows s3:GetObject?
Check whether the object belongs to a different account than the bucket, whether it's encrypted with a KMS key you don't have access to, whether S3 Block Public Access is enabled on the bucket, and whether you're calling from inside a VPC with a restrictive endpoint policy.
Why does aws s3 cp fail with AccessDenied when the bucket owner isn't me?
Cross-account access needs the bucket owner's bucket policy to explicitly allow you, and if the bucket has Requester Pays enabled you also need to add --request-payer requester to the command.
What is the difference between AccessDenied and UnauthorizedOperation?
They mean the same underlying thing — a denied request — but different services use different error names. S3 typically returns AccessDenied with a plain-text reason. EC2 typically returns UnauthorizedOperation along with an encoded message rather than the reason in plain text.
Why am I getting "Unable to locate credentials" instead of AccessDenied?
That message means the CLI found no credentials to send at all, so AWS never evaluated a permission — it's a local configuration issue, not a policy one. Run aws configure list to see where the CLI is currently looking.
Where does the AWS CLI store credentials on Windows?
In two plain-text files under your user profile: C:\Users\YourName\.aws\config and C:\Users\YourName\.aws\credentials. If a setting exists in both, the credentials file's access key and secret key take priority.
Why does aws configure work but every command still returns AccessDenied?
Successful configuration only means the CLI can authenticate you — it says nothing about what your identity is permitted to do. That's a separate, IAM-side question, and it's the one this whole post is about answering.
Can a Service Control Policy cause AccessDenied even if my IAM policy is correct?
Yes. An SCP at the AWS Organizations level acts as a ceiling on every account under it — it can't grant permissions by itself, but it can silently cap what an otherwise-correct identity policy is allowed to do.
What is a permissions boundary and can it cause this error?
A permissions boundary is a policy attached directly to a user or role that limits the maximum permissions that identity's own policies can grant. If the boundary doesn't include an action, the identity is denied that action no matter what its regular policy says.
How do I test a policy before applying it, to avoid AccessDenied?
Use aws iam simulate-principal-policy with the action and resource you're planning to use. It returns the same allow-or-deny decision as a real request, along with which statement caused it, without you having to actually run the action.
Why did AccessDenied appear only after my admin added a new bucket policy?
A bucket policy can introduce a Deny statement, or a condition, that overrides access users previously had — this is one of the most common "it worked yesterday" causes and is worth checking first if the timing lines up with a policy change.
What is the fastest way to get AWS CLI access if I am a brand-new IAM user?
Have an administrator create your IAM user with a narrowly scoped policy, generate an access key, run aws configure with it, then confirm your identity with aws sts get-caller-identity before running anything else.
Revision note. Written August 2026, covering the current AWS CLI v2 decode-authorization-message workflow, S3 and EC2 access-denial patterns, and Windows credential file behavior as documented by AWS. It'll need a revisit if AWS changes which services return an encoded message versus a plain-text reason. Jake's waivers made it back into the bucket that same afternoon, and if you've been going back and forth adding permissions and still hitting a wall, you're not missing something obvious — this error is designed to make you dig, and that's exactly what you just did.