src/config/service-control-policies/ contains the Service Control Policies that govern what actions are allowed across your AWS Organization. The landing zone ships five ready-made SCPs covering the most critical guardrails, all attached by default. You can use them as-is, modify their content, or add your own.
SCPs are attached in organization-structure.ts at the root, OU, or account level. AWS Organizations enforces a maximum of 10 SCPs per attachment point.
Shipped SCPs
| SCP | Default attachment | What it enforces |
|---|---|---|
criticalSecurityGuardrailsSCP | Root | Denies leaving the organization, creating IAM users and access keys, disabling EBS encryption by default, and modifying the account password policy |
denyAllOutsidePrimaryAndSecondaryRegionsSCP | Root | Restricts all AWS API calls to your configured primary and secondary regions; exempts global services (IAM, Route 53, CloudFront, etc.) and landing-zone roles |
protectTaggedCloudFormationStacksSCP | Root | Denies deleting CloudFormation stacks tagged with your organization name (organization: <organizationName>), protecting landing-zone managed stacks from accidental deletion |
protectSecurityHubConfigurationSCP | Security account | Prevents modification of Security Hub configuration in the delegated administrator account: disabling standards, deleting members, changing org configuration |
lockdownSuspendedAccountsSCP | Suspended OU | Denies all AWS actions in accounts that have been moved to the Suspended OU |
Exempt principals
A guardrail that denies an action still has to let the landing zone, and you, operate. src/config/service-control-policies/administrator-principals.ts is the single place those exemptions are defined, and every shipped guardrail reads its allowlists from there rather than repeating ARNs.
It exports three lists:
| Function | Used by | Covers |
|---|---|---|
securityServiceAdministratorPrincipalArns() | Region guardrail, Security Hub protection | SSO administrators, the Security Hub service role, the landing zone's own roles, and your external admins |
landingZoneAdministratorPrincipalArns() | CloudFormation stack protection | SSO administrators plus the landing zone's own roles and your external admins |
suspendedAccountExemptPrincipalArns() | Suspended-account lockdown | The principals that must keep access while an account is suspended |
The landing zone's own roles come from LandingZoneRoles.principalArnAllowList(), which covers the StackSet administration and execution roles, the organization account access role, the GitHubActionsServiceRole your pipeline assumes, and a role for each entry in externalAdmins. Setting externalAdmins in landing-zone-settings.ts is enough to exempt them everywhere.
Edit this file when you have a break-glass or automation role that must survive the guardrails. Because the guardrails share these lists, adding a principal once applies it to every SCP that uses that list.
The region guardrail also exempts global services (IAM, Route 53, CloudFront, AWS Organizations, billing, KMS, S3, STS, support, and others with no regional API boundary). When you add secondaryRegions to landing-zone-settings.ts and redeploy, it includes those regions in the allow list automatically.
Authoring a custom SCP
Create a new file (or add to an existing one) in src/config/service-control-policies/:
export const denyS3PublicAccessSCP: ServiceControlPolicy = {
content: {
Version: '2012-10-17',
Statement: [
{
Sid: 'DenyS3PublicAccess',
Effect: 'Deny',
Action: [
's3:PutBucketPublicAccessBlock',
's3:DeletePublicAccessBlock',
],
Resource: '*',
Condition: {
StringEquals: {
's3:publicAccessBlockConfiguration/BlockPublicAcls': 'false',
},
},
},
],
},
description: 'Prevents disabling S3 Block Public Access at the bucket level',
policyName: 'DenyS3PublicAccess',
policyType: PolicyType.SERVICE_CONTROL_POLICY,
};
Export it from src/config/service-control-policies/index.ts:
export * from './your-new-guardrails';
Then attach it in organization-structure.ts at the appropriate level:
import { denyS3PublicAccessSCP } from './service-control-policies';
// In the organizationStructure object:
ProductionOU: {
name: 'workload-prod-ou',
serviceControlPolicies: [denyS3PublicAccessSCP],
accounts: { ... },
},
Things to know
- Maximum 10 directly attached SCPs per attachment point (root, OU, or account). AWS raised this limit from 5 to 10 in May 2026. SCPs inherited from a parent do not count against a child's own quota, so an account can carry 10 of its own SCPs regardless of what its OU has attached.
- Quota violations fail at synth, not mid-deploy.
OrganizationConstructchecks every attachment point and policy document when you synthesize: more than 10 SCPs on one node or a policy over 10,240 characters fails the synth, and you get a warning once a node reaches 8 attachments or a document approaches the size limit. - SCPs are deny-only: they can only restrict what IAM already allows; they cannot grant permissions.
- The
FullAWSAccessmanaged SCP is always attached by AWS Organizations at every level. Your custom SCPs layer on top of it as additional restrictions. - Test in a non-production OU first: attach a new SCP to a development account before applying it at the root or to production accounts.