AWS IAM MalformedPolicyDocument: 12 Causes & Fixes

Logeshwaran.C

The MalformedPolicyDocument error in AWS IAM means your JSON policy has syntax errors, invalid elements, or structural problems that prevent AWS from parsing it. This guide covers the 12 most common causes, exact fixes, real-world broken and repaired policy examples, and validation tools—whether you're using the AWS Console, CLI, or CloudFormation.

⚡ Quick Answer

Most common cause → Invalid JSON syntax (missing commas, braces, or quotes)

Run this firstaws iam get-policy --policy-arn ARN (then check the policy document)

Validate before deployment → Use the aws iam simulate-principal-policy command or the IAM Policy Simulator

For step-by-step fixes, follow the diagnostic flowchart below. If your error mentions permissions instead of syntax, see AccessDenied on sts:AssumeRole.

Understanding AWS IAM Policy Structure and JSON Syntax

An IAM policy is a JSON document that defines permissions in AWS. The MalformedPolicyDocument error indicates that AWS cannot parse the JSON structure. This often happens when policies are built manually, copied incorrectly, or generated programmatically with errors.

The error can occur in several contexts: when creating a policy via the AWS Console, when attaching policies to users/groups/roles, during CloudFormation deployments, or when using the AWS CLI. The exact error message may vary slightly depending on where it occurs, but the root cause is always invalid JSON.

🙋‍♂️ Jake's Reality Check

"I copied a policy from Stack Overflow and only changed the resource ARN. Why is AWS rejecting it?"

Ethan's take: Because JSON is unforgiving, Jake. A missing comma after a changed value will break the entire structure. It's not enough to change the ARN—you have to ensure the surrounding syntax remains valid.

The 12 Most Common Causes (and Their Fixes)

Cause Where It Occurs Primary Fix
1. Invalid JSON Syntax All contexts Use JSON validator, fix commas/braces
2. Missing Required Fields Policy creation Add Version, Statement fields
3. Invalid Effect Value Statement blocks Use "Allow" or "Deny" (case-sensitive)
4. Incorrect ARN Format Resource elements Verify ARN structure: arn:partition:service:region:account-id:resource
5. Unsupported Action Action elements Check action exists in service namespace
6. Invalid Condition Condition blocks Use valid condition operators and keys
7. Version Field Issues All policies Use "2012-10-17" (or "2008-10-17" for legacy)
8. Duplicate Statement Policy document Remove duplicates or use unique Sid
9. Character Encoding Non-English environments Ensure UTF-8 encoding, no BOM
10. Quota Limits Large policies Split policy or remove unnecessary statements
11. CloudFormation Formatting CFN templates Use Fn::ToJsonString or proper YAML
12. Copied Policy Artifacts Copy-paste scenarios Check for smart quotes, special characters

1. Invalid JSON Syntax: The Most Common Culprit

JSON is a strict format. A single missing comma, misplaced brace, or unmatched quote will cause the entire policy to be rejected. Common syntax errors include:

  • Missing commas between statements or between key-value pairs
  • Extra commas after the last element in an array or object
  • Mismatched brackets or braces
  • Unclosed strings (missing closing quote)
  • Using single quotes instead of double quotes (JSON requires double quotes)
  1. Copy your policy JSON into a validator like https://jsonlint.com/ or use the built-in AWS Console editor (it highlights errors).
  2. Check for common syntax issues:
    • After each "Statement" block, ensure there's a comma if another statement follows
    • Verify all "Effect", "Action", and "Resource" keys are properly quoted
    • Ensure arrays (like "Action": ["s3:GetObject"]) are enclosed in square brackets

2. Missing Required Fields

Every IAM policy must include two top-level fields: "Version" and "Statement". Omitting either will cause a MalformedPolicyDocument error.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-bucket/*"
    }
  ]
}

If you're using CloudFormation, ensure the policy document is properly passed as a parameter or embedded in the template. For more on CloudFormation IAM policies, see IAM role troubleshooting for AssumeRole.

3. Invalid Effect Value

The "Effect" element must be either "Allow" or "Deny"—exactly as written, with initial capitalization. Values like "allow", "ALLOW", or "Permit" will cause the error.

⚠︇ What this actually breaks

Using "Effect": "Deny" for everything can create permission conflicts. Deny statements override allows, so a single deny can block access that seems allowed by other statements. Use deny sparingly for specific exceptions.

4. Incorrect ARN Format

Resource ARNs must follow the exact format: arn:partition:service:region:account-id:resource. Common mistakes include:

  • Missing partition (e.g., using arn:s3:::bucket instead of arn:aws:s3:::bucket—though S3 uses a different format)
  • Incorrect region for services that require region-specific ARNs
  • Missing account ID in cross-account policies
  • Invalid service namespace (e.g., using s3 instead of ec2 for EC2 instances)

For more examples of valid ARNs, check the AWS documentation for each service. If you're working with RDS, see RDS connection troubleshooting for related ARN issues.

Real-World Broken and Fixed Policy Examples

The fastest way to learn JSON syntax is to see broken policies next to their repairs. Here are three scenarios you'll encounter in real AWS work.

Example 1: S3 Read Policy with a Missing Comma

Broken version (fails with MalformedPolicyDocument):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject"
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}

The problem: Missing comma after "s3:GetObject" on line 5. The parser expects a comma between the Action and Resource values.

Fixed version:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}

Example 2: EC2 Policy with Invalid Effect Value

Broken version (fails with MalformedPolicyDocument):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "effect": "allow",
      "Action": "ec2:DescribeInstances",
      "Resource": "*"
    }
  ]
}

The problems: Two issues here. The "effect" key is lowercase (JSON keys are case-sensitive, so AWS expects "Effect"), and the value "allow" is lowercase instead of "Allow".

Fixed version:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ec2:DescribeInstances",
      "Resource": "*"
    }
  ]
}

Example 3: Cross-Account Policy with ARN Errors

Broken version (fails with MalformedPolicyDocument):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::shared-bucket/*"
    },
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::123456789012:role/CrossAccountRole"
    }
  ]
}

The problem: This looks valid, but if your AWS account ID is 210987654321 and you're referencing 123456789012 without that account's trust policy allowing your access, you'll get a different error. The JSON is valid; the permission is wrong. This is the boundary between MalformedPolicyDocument and AccessDenied—see the comparison section below.

Fixed version (if the account ID was simply mistyped):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::shared-bucket/*"
    },
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::210987654321:role/CrossAccountRole"
    }
  ]
}

MalformedPolicyDocument vs. AccessDenied: Which Are You Getting?

These two errors confuse more AWS users than any other IAM pair. They look similar in a log file but mean completely different things.

Aspect MalformedPolicyDocument AccessDenied
What it means AWS can't read your policy—the JSON is broken AWS read your policy fine, but it doesn't grant permission
When it appears When creating or updating the policy When using the policy to attempt an action
Fix approach Fix JSON syntax (commas, brackets, quotes) Add permissions, fix trust policy, or check IAM user policy
Policy was created? No—the policy doesn't exist Yes—the policy exists but doesn't allow the action

✅ How to tell which you have

Read the error message carefully. If it says MalformedPolicyDocument, your JSON is broken. If it says AccessDenied or is not authorized to perform, your JSON is fine but permissions are missing. For AccessDenied on role assumption specifically, see both sides of the trust.

The Diagnostic Flowchart: Find Your Failure Point

Follow this path to systematically identify where the JSON is failing:

Step What to Check Command or Action If It Fails, Look At
1 Basic JSON validation aws iam get-policy --policy-arn ARN JSON syntax errors (Cause #1)
2 Required fields Check for "Version" and "Statement" Missing fields (Cause #2)
3 Effect values Check for "Allow" or "Deny" Invalid Effect (Cause #3)
4 ARN format Verify ARN structure Incorrect ARN (Cause #4)
5 Action validity Check action exists for service Unsupported Action (Cause #5)
6 Condition syntax Check condition operators Invalid Condition (Cause #6)

CloudFormation-Specific Issues and Expanded Troubleshooting

When embedding IAM policies in CloudFormation templates, you must use the Fn::ToJsonString function or ensure proper YAML formatting. Common issues include:

  1. Using YAML shortcuts that produce invalid JSON when converted
  2. Multiline strings that include indentation or trailing spaces
  3. Sub variables that don't resolve to valid JSON values
Resources:
  MyPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Fn::ToJsonString:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action: 's3:GetObject'
              Resource: 'arn:aws:s3:::example-bucket/*'

CloudFormation Gotcha 1: YAML Multiline Syntax

If you embed JSON directly in a YAML template using the | or > multiline operator, CloudFormation may pass literal newlines that break JSON parsing:

# BROKEN - literal newlines in JSON
Resources:
  MyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {"Service": "ec2.amazonaws.com"},
              "Action": "sts:AssumeRole"
            }
          ]
        }

This can work in some cases, but trailing spaces or indentation issues will cause MalformedPolicyDocument errors. The safer approach is to use YAML-native syntax:

# FIXED - YAML-native syntax
Resources:
  MyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole

CloudFormation Gotcha 2: Substitution Failures

When using !Sub or Fn::Sub in policy documents, if a variable doesn't resolve, the JSON will contain the literal placeholder text instead of a valid value:

# BROKEN - ${BucketName} not defined
Resources:
  MyPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Fn::ToJsonString:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action: 's3:GetObject'
              Resource: !Sub 'arn:aws:s3:::${UndefinedVariable}/*'

Check CloudFormation events for substitution errors, then define the variable in Parameters or use a direct string.

Validation Tools and Debugging Techniques

1. AWS IAM Policy Simulator

The IAM Policy Simulator lets you test policy documents before deploying them. It's available in the AWS Console under IAM > Policy Simulator.

  1. Navigate to IAM > Policy Simulator
  2. Select "Paste policy document"
  3. Paste your JSON and run simulation
  4. The simulator will highlight syntax errors and logical issues

2. AWS CLI Validation

You can use the AWS CLI to test policy documents without creating them:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/testuser \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::example-bucket/*"
      }
    ]
  }'

3. Local Development Tools

For local validation before deployment:

  • JSON Linters: Most IDEs have built-in JSON validators
  • AWS Toolkit: For VS Code, provides IAM policy validation
  • cfn-lint: For CloudFormation templates, validates embedded policies

Preventing Future MalformedPolicyDocument Errors

Prevention Strategy How to Implement Benefit
1. Use Infrastructure as Code Define policies with CloudFormation or Terraform Version-controlled, testable policies
2. Implement Policy Validator in CI/CD Add JSON validation to pipeline Catch errors before deployment
3. Use Managed Policies Where Possible AWS-managed policies for common use cases Pre-tested, maintained policies
4. Regular Policy Reviews Use IAM Access Analyzer Identify policy issues and unintended access
5. Use Policy Generators Carefully AWS Policy Generator or similar tools Valid JSON structure, but verify permissions

Frequently Asked Questions

1. How do I find the exact syntax error in my policy?

Use the AWS Console's policy editor (it highlights errors), or copy your JSON into an online validator like jsonlint.com. The AWS CLI also provides specific error messages when you try to create a policy.

2. Can I use single quotes in IAM policies?

No. JSON requires double quotes for all strings, including keys and values. Single quotes will cause the policy to be rejected as malformed.

3. What does the error "Invalid principal in policy" mean?

This is a related error that occurs when the Principal element in a resource-based policy is invalid. It often indicates the same JSON formatting issues, but specifically in the principal section.

4. How do I validate a policy before creating it?

Use the IAM Policy Simulator in the AWS Console, or the aws iam simulate-principal-policy CLI command with your policy document to test it without creating it.

5. What's the maximum size for an IAM policy?

The maximum size for a managed policy is 6,144 characters, and for inline policies it's 2,048 characters. Exceeding these limits will cause the policy to be rejected.

6. Can I use comments in IAM policies?

No. JSON doesn't support comments. Any comment-like syntax (e.g., // or /* */) will cause the policy to be malformed.

7. How do I fix "Missing required fields" in CloudFormation?

In CloudFormation, ensure your policy document includes both "Version" and "Statement" fields. Use the Fn::ToJsonString function to properly convert your YAML/JSON structure.

8. What's the difference between inline and managed policies?

Inline policies are embedded directly in a user, group, or role, while managed policies are standalone policies that can be attached to multiple identities. Both must follow the same JSON structure.

9. How do I troubleshoot policy errors in CloudFormation?

Check the CloudFormation events for the specific error, then use the aws cloudformation validate-template command to check your template. Also ensure your policy documents are properly escaped.

10. Can I use wildcards in resource ARNs?

Yes, but only in specific positions. Wildcards are not allowed in the partition, service, or region parts of an ARN. You can use wildcards in the resource-id part, but be cautious as they can make policies overly permissive.

11. How do I check if my policy is valid using the AWS CLI?

You can use the aws iam simulate-principal-policy command with your policy document. If the policy is malformed, you'll get a specific error message pointing to the issue.

12. What should I do if my policy was working but suddenly stopped?

Check CloudTrail for policy modification events. Someone may have inadvertently changed the policy, or an automated process may have updated it with a malformed version.

13. How do I handle special characters in policy values?

Use JSON escaping for special characters. For example, use \n for newlines, \" for quotes within strings, and \\ for backslashes.

14. Can I use variables in IAM policies?

Yes, IAM supports policy variables like ${aws:username} and ${aws:PrincipalTag}. However, they must be properly formatted and used in supported contexts.

15. How do I export and inspect an existing policy?

Use the aws iam get-policy-version --policy-arn ARN --version-id VersionId command to retrieve the policy document. This will show you the exact JSON that AWS is storing.

16. What's the difference between MalformedPolicyDocument and AccessDenied?

MalformedPolicyDocument means AWS can't parse your JSON—the policy won't even be created. AccessDenied means the policy was created fine but doesn't grant the permission you're trying to use. See AccessDenied troubleshooting for permission issues.

Conclusion: Writing Valid IAM Policies

The MalformedPolicyDocument error is frustrating but almost always solvable with careful JSON validation. By understanding the common causes—invalid syntax, missing fields, incorrect values—you can quickly identify and fix issues.

Remember that IAM policies are strict JSON documents. Always validate before deployment, use tools like the Policy Simulator, and implement infrastructure as code to catch errors early in your development process.

Revision note. Written September 2026, covering AWS IAM as of August 2026. AWS policy syntax and validation tools evolve slowly, but check the official IAM documentation for any updates. If you've been fighting this error, remember that JSON validation is a skill that improves with practice—you've got this.

Related