Intermediate

Service Control Policies (SCPs)

Apply organization-wide guardrails that prevent deletion of production resources, use tag-based controls, protect critical AWS services, and understand when to use SCPs vs IAM policies.

What Are Service Control Policies?

Service Control Policies (SCPs) are a feature of AWS Organizations that set the maximum permissions for all IAM users and roles within member accounts. Unlike IAM policies which grant permissions, SCPs define the outer boundary of what is possible in an account. If an SCP denies an action, no IAM policy in that account can override it.

This makes SCPs the most powerful guardrail mechanism in AWS. Even if an AI agent compromises or escalates IAM permissions within an account, the SCP will still block the denied actions.

💡
Important: SCPs do not grant permissions. They only restrict them. You still need IAM policies to grant the permissions your AI agent needs. SCPs define what is possible; IAM policies define what is allowed.

SCP vs IAM Policy: When to Use Which

FeatureService Control Policy (SCP)IAM Policy
ScopeEntire account or OUIndividual user, role, or group
Grants permissions?No — only restrictsYes — grants and restricts
Can be overridden?No (except by management account)Yes (by other policies or SCPs)
Affects root user?Yes (in member accounts)No (root bypasses IAM)
Requires Organizations?YesNo
Best forBroad, non-negotiable guardrailsFine-grained, role-specific permissions
AI agent use caseBlanket ban on destructive actions in production accountsGrant specific read/write permissions to agent role
💡
Best practice: Use both SCPs and IAM policies together. SCPs are your safety net at the account level. IAM policies provide fine-grained control at the role level. Defense in depth means layering both.

SCP: Prevent Deletion of Production Resources

The following SCP blocks all common destructive actions. Attach it to your production OU so that no user, role, or AI agent in any production account can delete critical resources:

JSON — SCP: DenyProductionDeletion
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyAllDeletion",
      "Effect": "Deny",
      "Action": [
        "ec2:TerminateInstances",
        "ec2:DeleteVpc",
        "ec2:DeleteSubnet",
        "ec2:DeleteSecurityGroup",
        "ec2:DeleteNatGateway",
        "ec2:DeleteVolume",
        "rds:DeleteDBInstance",
        "rds:DeleteDBCluster",
        "rds:DeleteDBSnapshot",
        "rds:DeleteDBClusterSnapshot",
        "s3:DeleteBucket",
        "s3:DeleteObject",
        "s3:DeleteObjectVersion",
        "lambda:DeleteFunction",
        "eks:DeleteCluster",
        "eks:DeleteNodegroup",
        "dynamodb:DeleteTable",
        "elasticache:DeleteCacheCluster",
        "elasticache:DeleteReplicationGroup",
        "cloudformation:DeleteStack",
        "route53:DeleteHostedZone"
      ],
      "Resource": "*"
    }
  ]
}
Bash — Create and attach the SCP
# Create the SCP
aws organizations create-policy \
  --name "DenyProductionDeletion" \
  --description "Prevents all destructive actions in production accounts" \
  --type SERVICE_CONTROL_POLICY \
  --content file://deny-production-deletion.json

# Attach to the Production OU
# Replace ou-xxxx with your production OU ID
aws organizations attach-policy \
  --policy-id p-xxxxxxxxxxxx \
  --target-id ou-xxxx-xxxxxxxx

# Verify the policy is attached
aws organizations list-policies-for-target \
  --target-id ou-xxxx-xxxxxxxx \
  --filter SERVICE_CONTROL_POLICY

Tag-Based SCPs: Allow Deletion of Ephemeral Resources Only

A blanket deny on all deletion is too strict for some workflows. AI agents may need to delete temporary or test resources. Tag-based SCPs solve this by only allowing deletion of resources tagged as ephemeral:

JSON — SCP: Only allow deletion of ephemeral resources
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyDeleteUnlessEphemeral",
      "Effect": "Deny",
      "Action": [
        "ec2:TerminateInstances",
        "ec2:DeleteVolume",
        "rds:DeleteDBInstance",
        "s3:DeleteBucket",
        "lambda:DeleteFunction",
        "dynamodb:DeleteTable",
        "eks:DeleteCluster",
        "cloudformation:DeleteStack"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:ResourceTag/Lifecycle": "ephemeral"
        }
      }
    }
  ]
}

With this SCP in place, any resource tagged Lifecycle=ephemeral can be deleted, but production resources (which should be tagged Lifecycle=production or not tagged at all) cannot.

Protect your tags: If an AI agent can modify tags, it could change Lifecycle from production to ephemeral and then delete the resource. Add an additional SCP that prevents modifying the Lifecycle tag on production resources.

SCP to Protect Tags

JSON — SCP: Prevent tag modification on production resources
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyTagModificationOnProduction",
      "Effect": "Deny",
      "Action": [
        "ec2:CreateTags",
        "ec2:DeleteTags",
        "rds:AddTagsToResource",
        "rds:RemoveTagsFromResource",
        "s3:PutBucketTagging",
        "s3:DeleteBucketTagging"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/Lifecycle": "production"
        },
        "ForAnyValue:StringEquals": {
          "aws:TagKeys": "Lifecycle"
        }
      }
    }
  ]
}

Protecting Critical Services

Different AWS services require different protection strategies. Here are targeted SCPs for the most critical services:

RDS Protection SCP

JSON — SCP: Protect RDS databases
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyRDSDeletion",
      "Effect": "Deny",
      "Action": [
        "rds:DeleteDBInstance",
        "rds:DeleteDBCluster",
        "rds:DeleteDBSnapshot",
        "rds:DeleteDBClusterSnapshot",
        "rds:DeleteGlobalCluster",
        "rds:ModifyDBInstance",
        "rds:ModifyDBCluster"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:ResourceTag/Lifecycle": "ephemeral"
        }
      }
    },
    {
      "Sid": "DenySkipFinalSnapshot",
      "Effect": "Deny",
      "Action": "rds:DeleteDBInstance",
      "Resource": "*",
      "Condition": {
        "Bool": {
          "rds:SkipFinalSnapshot": "true"
        }
      }
    }
  ]
}

S3 Protection SCP

JSON — SCP: Protect S3 buckets and objects
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyS3BucketDeletion",
      "Effect": "Deny",
      "Action": [
        "s3:DeleteBucket",
        "s3:DeleteBucketPolicy",
        "s3:PutBucketPolicy"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:ResourceTag/Lifecycle": "ephemeral"
        }
      }
    },
    {
      "Sid": "DenyDisablingVersioning",
      "Effect": "Deny",
      "Action": "s3:PutBucketVersioning",
      "Resource": "*"
    }
  ]
}

EKS Protection SCP

JSON — SCP: Protect EKS clusters
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyEKSDeletion",
      "Effect": "Deny",
      "Action": [
        "eks:DeleteCluster",
        "eks:DeleteNodegroup",
        "eks:DeleteFargateProfile",
        "eks:DeleteAddon"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:ResourceTag/Lifecycle": "ephemeral"
        }
      }
    }
  ]
}

Testing SCPs Safely

SCPs are powerful and potentially disruptive. Test them carefully before applying to production:

1
Start with a test OU — Create a "Test" OU with a single sandbox account. Attach the SCP there first. Run your AI agent in this account and verify it can still perform allowed actions.
2
Use IAM Policy Simulator — Test specific actions before applying the SCP. Verify that allowed actions pass and denied actions fail.
3
Monitor CloudTrail — After attaching the SCP, monitor CloudTrail for AccessDenied errors. If legitimate operations are failing, refine the SCP.
4
Roll out incrementally — Apply to staging OU first, then production OU. Never apply an untested SCP directly to the production OU.
Bash — Monitor for SCP denials in CloudTrail
# Look for access denied events in the last hour
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=TerminateInstances \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --query 'Events[?contains(CloudTrailEvent, `AccessDenied`)]'

# List all SCPs attached to an OU
aws organizations list-policies-for-target \
  --target-id ou-xxxx-xxxxxxxx \
  --filter SERVICE_CONTROL_POLICY \
  --output table

# Describe a specific SCP
aws organizations describe-policy \
  --policy-id p-xxxxxxxxxxxx

Complete SCP Strategy for AI Agent Safety

Here is a recommended SCP architecture for organizations using AI coding agents:

OUSCPPurpose
RootFullAWSAccess (default)Baseline — all actions allowed
ProductionDenyAllDeletionNo resources can be deleted by anyone
StagingDenyDeleteUnlessEphemeralOnly ephemeral resources can be deleted
DevelopmentDenyDeleteUnlessEphemeralSame as staging, protects shared dev resources
SandboxAllowAllDeletion (no extra SCP)AI agents can freely create and destroy
💡
Sandbox accounts are key: Give AI agents a dedicated sandbox account where they can do anything. This lets you test infrastructure code safely before deploying to staging or production, where SCPs protect critical resources.