AWS Error Guide: 'MalformedPolicyDocument' — Invalid IAM Policy JSON Fix
Fix the AWS MalformedPolicyDocument error: diagnose invalid IAM policy JSON, bad ARNs, unknown actions, wrong principals, and unsupported conditions, then validate before applying.
- #aws
- #cloud
- #troubleshooting
- #errors
Stuck on this AWS with AI error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
MalformedPolicyDocument means IAM rejected a policy document because it failed structural or semantic validation — before it was ever attached or evaluated. IAM validates policy grammar, action names, ARN formats, principal shapes, and condition operators at write time; anything invalid is refused with HTTP 400 rather than saved. Because the check happens on the control-plane call (CreatePolicy, PutRolePolicy, PutBucketPolicy, AttachRolePolicy targets, etc.), the resource is not created or updated at all.
It surfaces from the CLI, an SDK, or Terraform/CloudFormation:
An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.
More specific variants point straight at the offending element:
An error occurred (MalformedPolicyDocument) when calling the PutRolePolicy operation: Policy contains an invalid action - 's3:GetObjcet'
An error occurred (MalformedPolicyDocument) when calling the PutBucketPolicy operation: Invalid principal in policy
An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource vpc-REDACTED must be in ARN format or "*".
It occurs whenever a policy is authored or generated with a JSON error, a typo’d action, an ARN that is not in ARN format, a wrong principal shape, or a condition key/operator the service does not support.
Symptoms
CreatePolicy,PutRolePolicy,PutUserPolicy,PutBucketPolicy, orUpdateAssumeRolePolicyfails immediately withMalformedPolicyDocument.terraform apply/cloudformation deployfails on an IAM resource with the same message before any permission takes effect.- The message often names the exact problem:
invalid action,Invalid principal,must be in ARN format,Syntax errors in policy, orhas prohibited field. - The policy renders fine in an editor but AWS still refuses it (a semantic, not syntactic, issue).
aws iam create-policy --policy-name app-read --policy-document file://policy.json
An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy contains an invalid action - 's3:GetObjcet'
Common Root Causes
1. A typo’d or non-existent action
IAM validates action names against the service’s known actions. A misspelling (s3:GetObjcet) or a made-up action is rejected.
python3 -c "import json,sys; json.load(open('policy.json'))" && echo "JSON parses"
grep -oE '"[a-z0-9-]+:[A-Za-z*]+"' policy.json | sort -u
Cross-check each action against the service’s actions reference; the message names the exact bad string.
2. A resource that is not in ARN format
Resource must be a valid ARN or "*". Passing a bare resource id (e.g. vpc-REDACTED, a bucket name without arn:aws:s3:::) is rejected.
"Resource": "my-bucket/*" # wrong — not an ARN
"Resource": "arn:aws:s3:::my-bucket/*" # correct
3. Invalid or wrong-shaped principal
Principal is only valid in resource-based policies (bucket, trust, KMS key). Using it in an identity policy, or giving it a bad value, triggers Invalid principal. A deleted IAM role/user referenced by unique-id also invalidates a trust policy.
"Principal": { "AWS": "arn:aws:iam::REDACTED:role/app" } # valid shape
"Principal": "arn:aws:iam::REDACTED:role/app" # wrong — must be an object
4. Raw JSON syntax errors
Trailing commas, unquoted keys, single quotes, or a truncated document — often from templating or string interpolation — fail the grammar check.
python3 -m json.tool policy.json >/dev/null
5. Unsupported or misplaced condition operator/key
A condition operator (StringEquals, ArnLike, Bool, DateGreaterThan) applied to an incompatible key, or a numeric operator on a string key, is rejected. So is a condition block that is not an object of operator → key → value.
"Condition": { "StringEquals": { "aws:SourceArn": "arn:aws:sns:REDACTED" } }
6. Prohibited or unknown top-level fields
Extra/unknown fields (Id in the wrong place, Version with a wrong value, Sid with illegal characters, or a Statement that is not an array/object) produce has prohibited field or Syntax errors.
"Version": "2012-10-17" # must be exactly this or "2008-10-17"
Diagnostic Workflow
Step 1: Validate the JSON parses at all
python3 -m json.tool policy.json >/dev/null && echo "Valid JSON" || echo "JSON syntax error"
If this fails, fix the raw syntax (trailing comma, quoting, truncation) before anything else.
Step 2: Read the exact element the error names
aws iam create-policy --policy-name app-read --policy-document file://policy.json 2>&1 \
| grep -oE "invalid action - '[^']+'|Invalid principal|must be in ARN format|prohibited field"
IAM almost always names the failing action, principal, or field — start there rather than re-reading the whole document.
Step 3: Extract and eyeball every action and resource
grep -oE '"[a-z0-9-]+:[A-Za-z0-9*]+"' policy.json | sort -u # actions
grep -oE '"arn:[^"]+"' policy.json | sort -u # resources (should be ARNs)
Any Resource value that is not an ARN or "*" is a prime suspect.
Step 4: Lint the policy for grammar and best practice
# IAM Access Analyzer policy validation catches malformed + suggests fixes
aws accessanalyzer validate-policy \
--policy-document file://policy.json \
--policy-type IDENTITY_POLICY \
--query 'findings[].[findingType,findingDetails]' --output table
validate-policy reports ERROR findings for exactly the elements IAM will reject, with the JSON path.
Step 5: Confirm principal usage matches the policy type
An identity policy (attached to a role/user) must not contain Principal. A resource/trust policy must. Use --policy-type RESOURCE_POLICY or RESOURCE_POLICY/SERVICE_CONTROL_POLICY in validate-policy accordingly.
Example Root Cause Analysis
A Terraform run failed on an aws_iam_role_policy with:
Error: putting IAM Role Policy: MalformedPolicyDocument: Resource must be in ARN format or "*".
The policy was built from a variable with jsonencode, and the operator assumed a JSON bug. Extracting the resources showed otherwise:
grep -oE '"[^"]*"' policy.json | grep -i bucket
"my-app-uploads/*"
The Resource was the bare bucket path my-app-uploads/*, not an ARN — a value copied from an S3 console breadcrumb rather than the ARN. IAM requires arn:aws:s3:::my-app-uploads/*.
Fix: correct the resource to a proper ARN and validate before applying:
Before: "Resource": "my-app-uploads/*"
After: "Resource": "arn:aws:s3:::my-app-uploads/*"
aws accessanalyzer validate-policy --policy-document file://policy.json \
--policy-type IDENTITY_POLICY --query 'findings[?findingType==`ERROR`]'
validate-policy returned no ERROR findings, and the subsequent terraform apply succeeded. The lasting fix was adding an Access Analyzer validation step to CI so malformed policies fail the pipeline instead of the deploy.
Prevention Best Practices
- Run
aws accessanalyzer validate-policyon every policy in CI before it reachesCreatePolicy/PutRolePolicy; it catches malformed documents and flags the exact JSON path. - Always write
Resourceas a full ARN (arn:aws:<service>:<region>:<account>:<resource>) or"*"— never a bare id, name, or console path. - Keep
Principalonly in resource-based/trust policies, and always as an object ({"AWS": "..."}/{"Service": "..."}), never a bare string in an identity policy. - Generate policies from templates/
jsonencoderather than hand-concatenating strings, which is where trailing commas and quoting bugs creep in. - Cross-check action names against the service actions reference; a single typo like
GetObjcetfails the whole document. - Use exactly
"Version": "2012-10-17"and validSidcharacters (alphanumeric) to avoid prohibited-field errors.
Quick Command Reference
# Does the JSON even parse?
python3 -m json.tool policy.json >/dev/null && echo OK
# What exactly did IAM reject?
aws iam create-policy --policy-name test --policy-document file://policy.json 2>&1 \
| grep -oE "invalid action - '[^']+'|Invalid principal|must be in ARN format"
# List all actions and resources for review
grep -oE '"[a-z0-9-]+:[A-Za-z0-9*]+"' policy.json | sort -u
grep -oE '"arn:[^"]+"' policy.json | sort -u
# Validate with Access Analyzer (names the failing element + path)
aws accessanalyzer validate-policy --policy-document file://policy.json \
--policy-type IDENTITY_POLICY \
--query 'findings[?findingType==`ERROR`].[findingType,findingDetails]' --output table
Conclusion
MalformedPolicyDocument is a write-time rejection: IAM refused the policy before saving it. The usual root causes:
- A typo’d or non-existent action name.
- A
Resourcethat is not in ARN format (or"*"). - An invalid or wrong-shaped
Principal, orPrincipalin an identity policy. - Raw JSON syntax errors from templating or hand-editing.
- An unsupported/misplaced condition operator or key.
- Prohibited or unknown top-level fields (bad
Version, illegalSid).
Read the element the error names, validate the JSON, then run aws accessanalyzer validate-policy to get the exact failing path. Wire that validation into CI and malformed policies fail the pipeline instead of the deploy — the error is a guardrail catching a broken document before it can grant anything.
Fixed it? Get 500 AWS with AI & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.