MCP Server Security · AWS IAM Permissions Boundaries

MCP server AWS permissions boundary security — IAM permissions boundaries for MCP server roles, least-privilege enforcement, and privilege escalation prevention

An AWS IAM permissions boundary is a managed policy attached to an IAM role or user that caps the maximum permissions that identity can ever use — regardless of what permissions its regular attached policies grant. For MCP servers deployed on AWS that call AWS APIs as part of tool execution, a permissions boundary is the critical layer that prevents a compromised server (or an LLM prompt that successfully manipulates tool calls) from escalating to AWS administrator access via iam:CreateRole, iam:AttachRolePolicy, or sts:AssumeRole.

How permissions boundaries differ from identity-based policies

AWS IAM evaluates permissions as the intersection of the identity-based policy (what the role is explicitly granted) and the permissions boundary (the maximum permitted). An action is allowed only if both the identity-based policy allows it AND the permissions boundary allows it. If a role has AdministratorAccess attached but a permissions boundary that only allows s3:GetObject and s3:PutObject, the role can only perform those two S3 actions regardless of the broad attached policy.

LayerWhat it controlsSet by
Identity-based policyWhat the role requests permission to doApplication team / Terraform
Permissions boundaryMaximum ceiling — a grant beyond the ceiling is silently deniedSecurity team / platform team
Resource-based policyWhat resources allow the role to act on themResource owner
SCPs (Org-level)Organization-wide guardrails above all IAM policiesAWS Organizations admin

Why MCP server IAM roles are high-value targets

MCP tools that interact with AWS — reading S3 buckets, querying DynamoDB, calling Lambda, sending SES emails — run with the permissions of the EC2 instance profile or ECS task role that hosts the MCP server. This role is automatically available via IMDS without any credentials in the code. A prompt-injection attack that convinces the LLM to pass a crafted string to an AWS-calling tool can:

  1. Call iam:CreateRole with a trust policy pointing to an external account if the MCP server role has that permission
  2. Call iam:AttachRolePolicy to attach AdministratorAccess to the new role
  3. Call sts:AssumeRole to receive credentials for the elevated role — now with full AWS account access

IAM privilege escalation chains are the highest-severity AWS finding. If the MCP server's IAM role has any of the ~20 known IAM privilege escalation permissions (iam:CreateRole, iam:AttachRolePolicy, iam:PutRolePolicy, iam:CreatePolicyVersion, lambda:UpdateFunctionCode, etc.), a permissions boundary is the mandatory containment. Without one, a single compromised tool call can result in full AWS account takeover.

Constructing a permissions boundary for an MCP server role

The boundary policy should allow only the AWS API actions the MCP server legitimately needs, plus explicitly deny all IAM write actions and privilege escalation paths.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowMcpServerOperations",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket",
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:Query",
        "dynamodb:Scan",
        "ses:SendEmail",
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DenyPrivilegeEscalation",
      "Effect": "Deny",
      "Action": [
        "iam:*",
        "sts:AssumeRole",
        "organizations:*",
        "account:*"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DenyBoundaryModification",
      "Effect": "Deny",
      "Action": [
        "iam:DeleteRolePermissionsBoundary",
        "iam:PutRolePermissionsBoundary"
      ],
      "Resource": "*"
    }
  ]
}

Include the DenyBoundaryModification statement. Without it, a role with iam:PutRolePermissionsBoundary can replace its own boundary with a permissive one, then escalate freely. Explicitly denying boundary modification is required to make the boundary self-protecting.

Attaching the boundary in Terraform and AWS CDK

# Terraform: create the boundary policy and attach to the MCP server role
resource "aws_iam_policy" "mcp_server_boundary" {
  name   = "McpServerPermissionsBoundary"
  policy = file("mcp-server-boundary.json")
}

resource "aws_iam_role" "mcp_server" {
  name = "mcp-server-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action    = "sts:AssumeRole"
      Effect    = "Allow"
      Principal = { Service = "ec2.amazonaws.com" }
    }]
  })

  # Attach the boundary — caps maximum permissions regardless of attached policies
  permissions_boundary = aws_iam_policy.mcp_server_boundary.arn
}

# Attach the actual operational policy (intersection with boundary = effective permissions)
resource "aws_iam_role_policy_attachment" "mcp_server_ops" {
  role       = aws_iam_role.mcp_server.name
  policy_arn = aws_iam_policy.mcp_server_ops.arn  # Your specific operational policy
}
// AWS CDK (TypeScript)
import * as iam from 'aws-cdk-lib/aws-iam';

const boundary = new iam.ManagedPolicy(this, 'McpServerBoundary', {
  document: iam.PolicyDocument.fromJson(boundaryJson),
});

const mcpRole = new iam.Role(this, 'McpServerRole', {
  assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
  permissionsBoundary: boundary,
});

SCPs vs permissions boundaries — which to use when

Service Control Policies (SCPs) in AWS Organizations apply to every identity in an account or OU — they cannot be set per-role. Permissions boundaries apply to specific roles. For MCP servers:

SkillAudit findings for permissions boundary issues in MCP servers

CRITICAL −20MCP server IAM role has IAM write permissions (iam:CreateRole, iam:AttachRolePolicy) without a permissions boundary — privilege escalation to AdministratorAccess possible via a single tool call chain
CRITICAL −18MCP server role has no permissions boundary and holds broad AWS managed policies (PowerUserAccess or AdministratorAccess) — blast radius of any tool call compromise is full account
HIGH −16Permissions boundary does not deny iam:PutRolePermissionsBoundary — boundary can be removed by the role itself, bypassing all containment
HIGH −14Permissions boundary allows sts:AssumeRole without resource constraints — role can assume other roles in the account, potentially escaping the boundary via a higher-privileged role
MEDIUM −10No permissions boundary on any IAM role in the account — team relies entirely on least-privilege identity policies, with no secondary enforcement layer

SkillAudit checks IAM role configurations exported via CloudFormation templates or Terraform plans. Run a free audit to surface privilege escalation paths in your MCP server's AWS configuration.