AWS CDK makes it remarkably easy to provision cloud infrastructure. That same ease of use means security misconfigurations can slip into production just as quickly. A single Bucket(this, 'Data') without explicit security settings creates a bucket with defaults that may not match your organization's security posture. These ten practices will help you catch the most common gaps before they become incidents.
1. Apply IAM Least Privilege by Default
CDK's grant* methods are the right starting point. Methods like bucket.grantRead(lambda) create scoped IAM policies automatically. But they are not always available for every service combination, and teams often fall back to inline policies with wildcard actions.
Watch for these patterns in code review: actions: ['*'] or resources: ['*'] in PolicyStatement constructs. Specify exact actions and resource ARNs whenever possible. If you need broad permissions during development, use CDK's conditions and session tags to constrain the blast radius.
// Avoid this
lambda.addToRolePolicy(new iam.PolicyStatement({
actions: ['s3:*'],
resources: ['*'],
}));
// Do this instead
bucket.grantRead(lambda);
// Or scope explicitly
lambda.addToRolePolicy(new iam.PolicyStatement({
actions: ['s3:GetObject', 's3:ListBucket'],
resources: [bucket.bucketArn, bucket.arnForObjects('*')],
}));CDK Insights' static analysis flags wildcard IAM policies as high-severity findings and suggests scoped alternatives based on the services your construct actually uses.
2. Enable Encryption at Rest Everywhere
Most AWS services support encryption at rest, but not all CDK constructs enable it by default. S3 buckets created with new s3.Bucket() now default to SSE-S3 encryption (since CDK v2), but services like DynamoDB, SQS, and SNS still need explicit configuration for KMS-based encryption.
Use AWS KMS customer-managed keys (CMKs) for sensitive data. CMKs let you control key rotation, define access policies, and audit key usage through CloudTrail. The extra cost (about $1/month per key plus API call charges) is negligible compared to the control you gain.
const key = new kms.Key(this, 'DataKey', {
enableKeyRotation: true,
description: 'Encryption key for application data',
});
const table = new dynamodb.Table(this, 'UserData', {
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
encryptionKey: key,
pointInTimeRecovery: true,
});3. Lock Down VPC Security Groups
CDK makes it easy to open security groups wider than intended. The allowAllOutbound default on SecurityGroup is true, which means every security group you create permits all egress traffic unless you explicitly override it. For databases and internal services, restrict egress to only the endpoints they need to reach.
Never use Peer.anyIpv4() for ingress on RDS, ElastiCache, or OpenSearch security groups. Use security group references to allow traffic only from the application layer that needs access.
const dbSg = new ec2.SecurityGroup(this, 'DbSg', {
vpc,
allowAllOutbound: false,
description: 'Security group for RDS instance',
});
// Allow ingress only from the application security group
dbSg.addIngressRule(
appSg,
ec2.Port.tcp(5432),
'Allow PostgreSQL from app tier'
);4. Enforce S3 Bucket Policies
Beyond encryption, S3 buckets need several security settings to be production-ready. Block all public access at the bucket level (CDK v2 does this by default, but verify it). Enable versioning for any bucket storing data you cannot afford to lose. Add lifecycle rules to transition old versions to cheaper storage classes and eventually delete them.
const bucket = new s3.Bucket(this, 'AppData', {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
versioned: true,
enforceSSL: true,
removalPolicy: RemovalPolicy.RETAIN,
lifecycleRules: [{
noncurrentVersionExpiration: Duration.days(90),
transitions: [{
storageClass: s3.StorageClass.INFREQUENT_ACCESS,
transitionAfter: Duration.days(30),
}],
}],
});The enforceSSL: true setting is frequently missed. It adds a bucket policy that denies any request not using HTTPS, protecting data in transit. CDK Insights checks for all of these settings and flags missing ones with specific remediation instructions.
5. Use Secrets Manager for All Credentials
Hardcoded credentials in CDK code are more common than you would expect. Database passwords passed as strings, API keys in environment variables, and tokens in Lambda configurations all create secrets that end up in your CloudFormation template (which is stored in S3) and potentially in your version control history.
Use AWS Secrets Manager or SSM Parameter Store (SecureString) for all sensitive values. CDK provides first-class support for both. For RDS, use the credentials: rds.Credentials.fromGeneratedSecret() pattern to auto-generate and rotate database passwords.
// Auto-generated, auto-rotated RDS credentials
const db = new rds.DatabaseInstance(this, 'Database', {
engine: rds.DatabaseInstanceEngine.postgres({
version: rds.PostgresEngineVersion.VER_16,
}),
credentials: rds.Credentials.fromGeneratedSecret('dbadmin'),
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
});
// Reference the secret in your Lambda
const fn = new lambda.Function(this, 'Api', {
environment: {
DB_SECRET_ARN: db.secret!.secretArn,
},
});
db.secret!.grantRead(fn);6. Enable Logging and Audit Trails
Every AWS service that supports access logging should have it enabled. For S3, enable server access logging to a dedicated logging bucket. For API Gateway, enable access logging with a structured format. For CloudFront distributions, enable standard logging or real-time logging depending on your latency requirements.
At the account level, ensure CloudTrail is configured for all regions with management and data events. CDK makes this straightforward with the Trail construct. Store CloudTrail logs in a bucket with object lock to prevent deletion.
CDK Insights checks each service for proper logging configuration and flags resources that are missing audit trails. The AI analysis can also identify logging gaps where specific services are writing sensitive data without corresponding access logs.
7. Protect Web Applications with WAF
Any CDK stack that exposes an API Gateway, ALB, or CloudFront distribution to the internet should include an AWS WAF web ACL. The managed rule groups from AWS cover the OWASP Top 10, known bad inputs, and bot detection without any custom rule authoring.
const webAcl = new wafv2.CfnWebACL(this, 'WebAcl', {
defaultAction: { allow: {} },
scope: 'REGIONAL',
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'WebAclMetric',
sampledRequestsEnabled: true,
},
rules: [{
name: 'AWSManagedRulesCommonRuleSet',
priority: 1,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
vendorName: 'AWS',
name: 'AWSManagedRulesCommonRuleSet',
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'CommonRuleSet',
sampledRequestsEnabled: true,
},
}],
});WAF costs scale with request volume (starting at $5/month per web ACL plus $0.60 per million requests), but the protection against SQL injection, XSS, and other common attacks is well worth the investment for any production-facing endpoint.
8. Configure Backup and Recovery
CDK constructs for databases and storage services often omit backup configuration by default. RDS instances without backupRetention, DynamoDB tables without point-in-time recovery, and S3 buckets with removalPolicy: DESTROY are all patterns that lead to data loss incidents.
Set explicit backup retention periods. For RDS, the default is 1 day, which is rarely sufficient. Use AWS Backup for centralized backup management across services. Enable DynamoDB point-in-time recovery (PITR) for any table with data you need to protect.
CDK Insights flags resources that lack backup configuration. The static analysis rules cover RDS backup retention, DynamoDB PITR, and S3 versioning across all your stacks.
9. Set Up Monitoring and Alerting
Security monitoring is the counterpart to prevention. CloudWatch Alarms for unusual API activity, Lambda error rates, and unauthorized access attempts provide early warning when something goes wrong. Use CDK to define alarms alongside the resources they monitor.
// Alert on Lambda errors
const errorAlarm = fn.metricErrors({
period: Duration.minutes(5),
}).createAlarm(this, 'LambdaErrors', {
threshold: 5,
evaluationPeriods: 2,
alarmDescription: 'Lambda function error rate elevated',
});
// Alert on 4xx/5xx from API Gateway
api.metricServerError({
period: Duration.minutes(5),
}).createAlarm(this, 'ApiErrors', {
threshold: 10,
evaluationPeriods: 2,
});Connect alarms to SNS topics that notify your team via email, Slack, or PagerDuty. Do not rely on manually checking the CloudWatch console. Automated alerts catch issues when they start, not after customers report them.
10. Scan Dependencies and CDK Constructs
Your CDK app is a Node.js (or Python/Java/Go) application with its own dependency tree. Third-party constructs and libraries can introduce vulnerabilities just like any other dependency. Run npm audit or equivalent as part of your CI pipeline. Pin CDK library versions to avoid unexpected changes from minor version updates.
Beyond npm dependencies, scan the generated CloudFormation templates for security issues. This is where tools like CDK Insights add the most value. The synthesized template is the actual infrastructure definition that CloudFormation will deploy. Scanning it catches issues that originate in construct libraries, custom constructs, and CDK defaults that your team may not have reviewed.
Run npx cdk-insights scan as part of every pull request. The GitHub Action makes this a two-minute setup. Failed checks on PRs prevent insecure infrastructure from reaching production.
Putting It All Together
None of these practices are difficult individually. The challenge is applying them consistently across every stack, every sprint, and every team member. Automated tooling is the only reliable way to enforce security standards at scale. Manual code review catches some issues, but people miss things that static analysis does not.
CDK Insights checks for every practice on this list as part of its 280+ rule engine. The free tier runs unlimited scans with no account required. The Pro plan adds AI-powered analysis that identifies architectural gaps that rules alone cannot catch.
Automate CDK Security Checks
Catch all 10 of these issues automatically with CDK Insights.