CDK Nag
Enforce AWS CDK security and compliance controls with cdk-nag. Use when adding rule packs, triaging findings, writing justified suppressions, integrating checks in CI/CD, or preventing insecure infrastructure patterns in CDK stacks.
npx skills add github:pantheon-org/tekhne --skill infrastructure--aws-cdk--cdk-nagWhen to Use
- Validating CDK infrastructure against security and compliance guardrails.
- Adding a rule pack such as
AwsSolutionsChecksto a CDK app. - Triaging findings reported during
npx cdk synth. - Writing justified, scoped suppressions for accepted findings.
- Wiring nag checks into CI/CD so insecure patterns fail the build.
When Not to Use
- Terraform-only repositories without AWS CDK constructs.
- Runtime or account-level posture checks, which cdk-nag does not perform.
- Reviews of application code that never reach a CDK stack.
Philosophy
- Run compliance checks early in development, not at release.
- Prefer fixing insecure resources over suppressing findings.
- Use suppressions only with explicit, reviewable rationale.
- Keep CI enforcement consistent with the risk profile.
Deterministic Workflow
- Add relevant cdk-nag rule packs to the app.
- Synthesize stacks and collect findings.
- Fix failing resources where feasible.
- Checkpoint: If findings remain after step 3, categorize each as fix / suppress / defer before proceeding. Do not advance until every finding has an owner and decision.
- Add minimal, scoped suppressions when justified.
- Re-run synth and CI checks before merge.
Quick Commands
Install cdk-nag
npm install --save-dev cdk-nag
Expected result: cdk-nag dependency available for CDK app.
Run synth to surface findings
npx cdk synth
Expected result: nag findings shown during synthesis.
Run synth for one stack
npx cdk synth MyStack
Expected result: targeted findings for the selected stack.
Run tests and synth in CI style
npm test && npx cdk synth
Expected result: failing status when tests or nag checks fail.
Evaluate this skill quality
pantheon-skill-auditor evaluate infrastructure/aws-cdk/cdk-nag --json
Expected result: updated skill score and grade.
Code Examples
Add a rule pack to a CDK app
import { App, Aspects } from 'aws-cdk-lib';
import { AwsSolutionsChecks } from 'cdk-nag';
const app = new App();
// Apply the AWS Solutions rule pack to every stack in the app
Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));
Expected result: all stacks synthesized with the AwsSolutionsChecks pack applied; findings printed to stdout during cdk synth.
Add a scoped suppression on a specific resource
import { NagSuppressions } from 'cdk-nag';
// Suppress a single rule on a specific construct, not the whole stack
NagSuppressions.addResourceSuppressions(
myBucket,
[
{
id: 'AwsSolutions-S1',
reason:
'Server access logging disabled intentionally: bucket stores only ephemeral build artifacts ' +
'with no PII; access is restricted to the CI role via bucket policy. ' +
'Risk accepted and documented in ADR-042.',
},
],
);
Expected result: only myBucket is exempted from AwsSolutions-S1; all other resources and rules remain enforced.
Anti-Patterns
NEVER suppress findings without a concrete security rationale
WHY: Unjustified suppressions hide unresolved risk.
BAD: reason: "false positive" with no evidence.
GOOD: reason: "resource isolated in private subnet; compensating controls documented".
Consequence: Audit posture weakens and real issues stay unresolved.
NEVER apply broad stack-level suppressions for convenience
WHY: Broad suppressions mask unrelated violations.
BAD: Suppress an entire rule for every resource in a stack. GOOD: Scope suppression to exact resource and finding.
Consequence: New regressions pass undetected.
NEVER enable production rule packs only at release time
WHY: Late checks create expensive rework.
BAD: Add strict rule packs only before deployment. GOOD: Run target rule packs continuously in feature branches.
Consequence: Security defects are found too late.
NEVER ignore recurring nag violations in CI
WHY: Repeated failures indicate systemic misconfiguration.
BAD: Re-run pipeline until flake passes without remediation. GOOD: Fix root cause or add justified suppression once.
Consequence: Compliance debt accumulates quickly.
NEVER suppress a finding when a fix is feasible
WHY: Fixing the resource removes the risk; a suppression only hides it.
BAD: Suppress AwsSolutions-S10 instead of adding enforceSSL: true.
GOOD: Change the construct so the finding no longer fires.
Consequence: Fixable gaps stay in production behind a reason string.
NEVER leave a remaining finding without an owner and a decision
WHY: An undecided finding is neither fixed nor accepted, so nobody is accountable.
BAD: Move on to suppressions with three findings still unclassified. GOOD: Categorize each as fix / suppress / defer before advancing.
Consequence: Findings are lost between triage and merge.
NEVER merge without re-running synth and the CI checks
WHY: A fix or suppression can change the findings that remain.
BAD: Merge straight after editing a suppression.
GOOD: Re-run npx cdk synth and the CI pipeline first.
Consequence: The merged change fails the pipeline or ships a new violation.
NEVER apply a rule pack to one stack and assume the app is covered
WHY: Stacks without the aspect are never checked.
BAD: Aspects.of(stack).add(new AwsSolutionsChecks()) on a single stack.
GOOD: Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })) once on the app.
Consequence: Unchecked stacks drift out of compliance silently.
NEVER use this skill for Terraform-only repositories
WHY: cdk-nag inspects AWS CDK constructs and has nothing to analyse elsewhere.
BAD: Add cdk-nag guidance to a pure Terraform module. GOOD: Use a Terraform-appropriate checker instead.
Consequence: Effort is wasted and the repository gains no real guardrail.
References
- Implementation Guide — full setup walkthrough, rule pack selection, and stack-level vs construct-level application
- Rule Packs — AwsSolutionsChecks, NIST 800-53, HIPAA, PCI-DSS: what each enforces and when to use it
- Suppression Guide — addResourceSuppressions vs addStackSuppressions, rationale templates, and audit-friendly patterns
- Troubleshooting — common synth failures, false positives, and rule ID lookup
- Rule Evolution — tracking deprecated rules, new rules in version upgrades, and migration paths
- Integration Patterns — CI/CD wiring, pre-commit hooks, and multi-account enforcement strategies
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Runtime validation via eval scenarios proving the skill actually changes agent behaviour. Requires evals/ directory, instructions.json, ≥3 scenarios, and ≥80% instruction coverage in summary.json.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
No eval scenarios available.
Implementation Guide
Basic Setup
Single Rule Pack
import { AwsSolutionsChecks } from 'cdk-nag';
import { App, Aspects } from 'aws-cdk-lib';
const app = new App();
const stack = new MyStack(app, 'MyStack');
// Apply AWS Solutions checks
Aspects.of(app).add(new AwsSolutionsChecks());
Multiple Rule Packs
import {
AwsSolutionsChecks,
NIST80053R5Checks,
HipaaSecurityChecks,
} from 'cdk-nag';
const app = new App();
const stack = new MyStack(app, 'MyStack');
// Apply multiple rule packs
Aspects.of(app).add(new AwsSolutionsChecks());
Aspects.of(app).add(new NIST80053R5Checks());
Aspects.of(app).add(new HipaaSecurityChecks());
Verbose Mode
// Enable verbose explanations
Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));
Conditional Application
Environment-Based
const environment = process.env.ENVIRONMENT || 'development';
if (environment === 'production') {
// Full compliance checking in production
Aspects.of(app).add(new AwsSolutionsChecks());
Aspects.of(app).add(new HipaaSecurityChecks());
} else {
// Basic checks in development
Aspects.of(app).add(new AwsSolutionsChecks());
}
Feature Flag Based
if (process.env.ENABLE_CDK_NAG === 'true') {
Aspects.of(app).add(new AwsSolutionsChecks());
}
Context-Based
const enableCompliance = app.node.tryGetContext('enableCompliance') || false;
const complianceLevel = app.node.tryGetContext('complianceLevel') || 'basic';
if (enableCompliance) {
Aspects.of(app).add(new AwsSolutionsChecks());
if (complianceLevel === 'full') {
Aspects.of(app).add(new NIST80053R5Checks());
Aspects.of(app).add(new HipaaSecurityChecks());
}
}
Advanced Configuration
Custom Logging
import { NagLogger } from 'cdk-nag';
class CustomLogger extends NagLogger {
onCompliance(data: any) {
console.log(`COMPLIANCE: ${JSON.stringify(data)}`);
}
onNonCompliance(data: any) {
console.error(`VIOLATION: ${JSON.stringify(data)}`);
}
onSuppressed(data: any) {
console.warn(`SUPPRESSED: ${JSON.stringify(data)}`);
}
}
Aspects.of(app).add(
new AwsSolutionsChecks({
additionalLoggers: [new CustomLogger()],
}),
);
Suppression Ignore Conditions
import { SuppressionIgnoreErrors } from 'cdk-nag';
// Ignore suppressions on Error-level rules
Aspects.of(app).add(
new AwsSolutionsChecks({
suppressionIgnoreCondition: new SuppressionIgnoreErrors(),
}),
);
Custom NagPacks
import { NagPack, NagPackProps, NagRuleCompliance } from 'cdk-nag';
export class CustomSecurityChecks extends NagPack {
constructor(props?: NagPackProps) {
super(props);
this.packName = 'CustomSecurity';
}
visit(node: IConstruct): void {
// Custom rule implementations
if (node instanceof CfnBucket) {
this.applyRule({
ruleSuffixOverride: 'S3CustomRule',
info: 'Custom S3 security check',
explanation: 'This rule enforces custom S3 security requirements',
level: NagMessageLevel.ERROR,
rule: NagRules.CustomS3Rule,
node: node,
});
}
}
}
Integration with CDK Pipelines
Pipeline Construction Issue
import {
CodePipeline,
CodePipelineSource,
ShellStep,
} from 'aws-cdk-lib/pipelines';
export class MyPipeline extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const pipeline = new CodePipeline(this, 'Pipeline', {
synth: new ShellStep('Synth', {
input: CodePipelineSource.gitHub('owner/repo', 'main'),
commands: ['npm ci', 'npm run build', 'npx cdk synth'],
}),
});
// CRITICAL: Force pipeline creation before suppressions
pipeline.buildPipeline();
// Now suppressions will work correctly
NagSuppressions.addResourceSuppressionsByPath(
this,
'/MyPipeline/Pipeline/ArtifactsBucket/Resource',
[{ id: 'AwsSolutions-S1', reason: 'Pipeline artifacts bucket' }],
);
}
}
Property Overrides and Raw CFN
Handling L2 Construct Limitations
import { Instance, CfnInstance } from 'aws-cdk-lib/aws-ec2';
const instance = new Instance(this, 'Instance', {
// ... instance configuration
});
// Apply raw override for features not available in L2
const cfnInstance = instance.node.defaultChild as CfnInstance;
cfnInstance.addPropertyOverride('DisableApiTermination', true);
// Suppress the rule and document the remediation
NagSuppressions.addResourceSuppressions(instance, [
{
id: 'AwsSolutions-EC29',
reason: 'Termination protection enabled via property override',
},
]);
CloudFormation Template Integration
Basic Template Import
import { CfnInclude } from 'aws-cdk-lib/cloudformation-include';
export class ImportedStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
new CfnInclude(this, 'Template', {
templateFile: 'existing-template.json',
});
}
}
Template with Embedded Suppressions
{
"Resources": {
"MyBucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": "my-bucket"
},
"Metadata": {
"cdk_nag": {
"rules_to_suppress": [
{
"id": "AwsSolutions-S1",
"reason": "Access logging not required for this bucket"
}
]
}
}
}
}
}
Best Practices
Aspect Application
- Apply to the highest level scope possible (App vs Stack)
- Use conditional application for different environments
- Consider rule pack combinations for comprehensive coverage
Performance Considerations
- CDK Nag runs during synthesis, adding time to the process
- Multiple rule packs increase synthesis time
- Consider environment-specific rule application
Error Handling
- CDK Nag failures will stop synthesis
- Use suppressions judiciously with proper justification
- Implement proper CI/CD integration for rule violations
Integration Patterns
CI/CD Pipeline Integration
GitHub Actions
name: CDK Security Scan
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: CDK Security Scan
run: |
npm run cdk -- synth
env:
ENABLE_CDK_NAG: 'true'
ENVIRONMENT: ${{ github.ref == 'refs/heads/main' && 'production' || 'development' }}
- name: Upload CDK Nag Report
uses: actions/upload-artifact@v3
if: always()
with:
name: cdk-nag-report
path: cdk.out/NagReport.csv
GitLab CI/CD
stages:
- security-scan
- deploy
security-scan:
stage: security-scan
image: node:18
variables:
ENABLE_CDK_NAG: 'true'
script:
- npm ci
- npm run cdk -- synth
artifacts:
when: always
paths:
- cdk.out/NagReport.csv
reports:
junit: cdk.out/NagReport.xml
rules:
- if: $CI_MERGE_REQUEST_IID
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Jenkins Pipeline
pipeline {
agent any
environment {
ENABLE_CDK_NAG = 'true'
NODE_VERSION = '18'
}
stages {
stage('Setup') {
steps {
sh 'npm ci'
}
}
stage('Security Scan') {
steps {
script {
def environment = env.BRANCH_NAME == 'main' ? 'production' : 'development'
sh "ENVIRONMENT=${environment} npm run cdk -- synth"
}
}
post {
always {
archiveArtifacts artifacts: 'cdk.out/NagReport.csv', allowEmptyArchive: true
}
}
}
}
post {
failure {
emailext(
subject: "CDK Security Scan Failed - ${env.JOB_NAME}",
body: "Security violations found. Check the CDK Nag report.",
to: "${env.TEAM_EMAIL}"
)
}
}
}
Azure DevOps
trigger:
branches:
include:
- main
- develop
pr:
branches:
include:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
ENABLE_CDK_NAG: true
stages:
- stage: SecurityScan
displayName: 'CDK Security Scan'
jobs:
- job: SecurityScan
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
- script: npm ci
displayName: 'Install dependencies'
- script: |
export ENVIRONMENT=$([[ "$(Build.SourceBranch)" == "refs/heads/main" ]] && echo "production" || echo "development")
npm run cdk -- synth
displayName: 'CDK Security Scan'
- task: PublishBuildArtifacts@1
condition: always()
inputs:
pathToPublish: 'cdk.out/NagReport.csv'
artifactName: 'cdk-nag-report'
Environment-Specific Configurations
Basic Environment Differentiation
// cdk-config.ts
export interface CdkConfig {
enableCdkNag: boolean;
rulePacks: string[];
verbose: boolean;
}
export const getConfig = (environment: string): CdkConfig => {
switch (environment) {
case 'production':
return {
enableCdkNag: true,
rulePacks: ['AwsSolutions', 'HIPAA'],
verbose: false,
};
case 'staging':
return {
enableCdkNag: true,
rulePacks: ['AwsSolutions'],
verbose: true,
};
case 'development':
return {
enableCdkNag: process.env.DEVELOPER_CDK_NAG === 'true',
rulePacks: ['AwsSolutions'],
verbose: true,
};
default:
return {
enableCdkNag: false,
rulePacks: [],
verbose: false,
};
}
};
Advanced Environment Configuration
// app.ts
import { getConfig } from './cdk-config';
import {
AwsSolutionsChecks,
HipaaSecurityChecks,
NIST80053R5Checks,
PCIDSS321Checks,
} from 'cdk-nag';
const environment = process.env.CDK_ENVIRONMENT || 'development';
const config = getConfig(environment);
const app = new App();
const stack = new MyStack(app, 'MyStack', { environment });
if (config.enableCdkNag) {
const nagOptions = { verbose: config.verbose };
config.rulePacks.forEach((pack) => {
switch (pack) {
case 'AwsSolutions':
Aspects.of(app).add(new AwsSolutionsChecks(nagOptions));
break;
case 'HIPAA':
Aspects.of(app).add(new HipaaSecurityChecks(nagOptions));
break;
case 'NIST':
Aspects.of(app).add(new NIST80053R5Checks(nagOptions));
break;
case 'PCI':
Aspects.of(app).add(new PCIDSS321Checks(nagOptions));
break;
}
});
}
Feature Flag Patterns
Context-Based Feature Flags
// Enable via CDK context
const enableNag = this.node.tryGetContext('enableCdkNag') ?? false;
const complianceLevel = this.node.tryGetContext('complianceLevel') ?? 'basic';
if (enableNag) {
Aspects.of(app).add(new AwsSolutionsChecks());
if (complianceLevel === 'full') {
Aspects.of(app).add(new HipaaSecurityChecks());
}
}
# Usage with context
npx cdk synth -c enableCdkNag=true -c complianceLevel=full
Environment Variable Feature Flags
const cdkNagConfig = {
enabled: process.env.CDK_NAG_ENABLED === 'true',
strictMode: process.env.CDK_NAG_STRICT === 'true',
rulePacks: process.env.CDK_NAG_RULE_PACKS?.split(',') || ['AwsSolutions'],
reportFormat: process.env.CDK_NAG_REPORT_FORMAT || 'csv',
};
if (cdkNagConfig.enabled) {
const nagOptions = {
verbose: cdkNagConfig.strictMode,
reportFormats: [cdkNagConfig.reportFormat],
};
cdkNagConfig.rulePacks.forEach((pack) => {
// Apply rule pack based on configuration
});
}
Conditional Suppression Loading
// Load suppressions based on environment
const loadSuppressions = (environment: string, resource: Construct) => {
const baseSuppressions = getBaseSuppressions();
if (environment === 'development') {
const devSuppressions = getDevSuppressions();
NagSuppressions.addResourceSuppressions(resource, [
...baseSuppressions,
...devSuppressions,
]);
} else {
NagSuppressions.addResourceSuppressions(resource, baseSuppressions);
}
};
const getBaseSuppressions = () => [
{
id: 'AwsSolutions-SNS3',
reason: 'SSL enforcement auto-applied with KMS encryption',
},
];
const getDevSuppressions = () => [
{
id: 'AwsSolutions-S1',
reason: 'Access logging not required in development environment',
},
];
Report Generation and Processing
Custom Report Generation
import { NagReportLogger, NagReportFormat } from 'cdk-nag';
class CustomReportLogger extends NagReportLogger {
constructor() {
super({
formats: [NagReportFormat.CSV, NagReportFormat.JSON],
});
}
// Override to customize report content
protected generateReport(data: any[]): string {
// Custom report logic
const summary = this.generateSummary(data);
const details = this.generateDetails(data);
return `${summary}\n\n${details}`;
}
private generateSummary(data: any[]): string {
const violations = data.filter((item) => item.level === 'ERROR');
const warnings = data.filter((item) => item.level === 'WARNING');
return [
'# CDK Nag Security Report',
`- Total Issues: ${data.length}`,
`- Violations (Errors): ${violations.length}`,
`- Warnings: ${warnings.length}`,
`- Generated: ${new Date().toISOString()}`,
].join('\n');
}
}
// Usage
Aspects.of(app).add(
new AwsSolutionsChecks({
additionalLoggers: [new CustomReportLogger()],
}),
);
Report Processing Scripts
#!/bin/bash
# process-cdk-nag-report.sh
REPORT_FILE="cdk.out/NagReport.csv"
VIOLATIONS_FILE="violations.txt"
WARNINGS_FILE="warnings.txt"
if [[ -f "$REPORT_FILE" ]]; then
# Extract violations (errors)
grep ",ERROR," "$REPORT_FILE" > "$VIOLATIONS_FILE"
# Extract warnings
grep ",WARNING," "$REPORT_FILE" > "$WARNINGS_FILE"
# Count issues
VIOLATION_COUNT=$(wc -l < "$VIOLATIONS_FILE")
WARNING_COUNT=$(wc -l < "$WARNINGS_FILE")
echo "CDK Nag Results:"
echo "- Violations: $VIOLATION_COUNT"
echo "- Warnings: $WARNING_COUNT"
# Fail if violations exist in production
if [[ "$ENVIRONMENT" == "production" && $VIOLATION_COUNT -gt 0 ]]; then
echo "❌ Security violations found in production build"
cat "$VIOLATIONS_FILE"
exit 1
fi
# Always show violations for visibility
if [[ $VIOLATION_COUNT -gt 0 ]]; then
echo "⚠️ Security violations found:"
cat "$VIOLATIONS_FILE"
fi
else
echo "⚠️ CDK Nag report not found"
fi
Integration with Security Tools
SAST Tool Integration
// Convert CDK Nag output for SAST tools
class SarifReportLogger extends NagReportLogger {
protected generateSarifReport(violations: any[]): object {
return {
version: '2.1.0',
runs: [
{
tool: {
driver: {
name: 'CDK Nag',
version: '2.37.0',
rules: this.extractRules(violations),
},
},
results: violations.map((v) => ({
ruleId: v.ruleId,
level: v.level === 'ERROR' ? 'error' : 'warning',
message: { text: v.message },
locations: [
{
physicalLocation: {
artifactLocation: { uri: v.filePath },
region: { startLine: v.line },
},
},
],
})),
},
],
};
}
}
Slack/Teams Integration
class SlackNotificationLogger extends NagLogger {
private webhookUrl: string;
constructor(webhookUrl: string) {
super();
this.webhookUrl = webhookUrl;
}
async onNonCompliance(data: any) {
if (data.level === 'ERROR') {
await this.sendSlackMessage({
text: `🚨 CDK Security Violation`,
attachments: [
{
color: 'danger',
fields: [
{ title: 'Rule', value: data.ruleId, short: true },
{ title: 'Resource', value: data.resource, short: true },
{ title: 'Message', value: data.message, short: false },
],
},
],
});
}
}
private async sendSlackMessage(payload: any) {
// Slack webhook implementation
}
}
// Usage in production environments
if (process.env.ENVIRONMENT === 'production' && process.env.SLACK_WEBHOOK) {
Aspects.of(app).add(
new AwsSolutionsChecks({
additionalLoggers: [
new SlackNotificationLogger(process.env.SLACK_WEBHOOK),
],
}),
);
}
Automated Remediation Patterns
Auto-Fix Development Workflow
// auto-fix-cdk-nag.ts
import { execSync } from 'child_process';
import { readFileSync, writeFileSync } from 'fs';
interface ViolationFix {
ruleId: string;
autoFix: (filePath: string, violation: any) => void;
}
const autoFixes: ViolationFix[] = [
{
ruleId: 'AwsSolutions-S1',
autoFix: (filePath: string, violation: any) => {
// Add access logging suppression with TODO comment
const suppression = `
// TODO: Review S3 access logging requirement
NagSuppressions.addResourceSuppressions(bucket, [
{ id: 'AwsSolutions-S1', reason: 'Access logging requirement under review - added by auto-fix' },
]);`;
// Insert suppression in file (simplified example)
insertSuppression(filePath, violation, suppression);
},
},
];
function runAutoFix() {
try {
execSync('npm run cdk -- synth', { stdio: 'pipe' });
} catch (error) {
const output = error.stdout.toString();
const violations = parseViolations(output);
violations.forEach((violation) => {
const fix = autoFixes.find((f) => f.ruleId === violation.ruleId);
if (fix) {
fix.autoFix(violation.filePath, violation);
}
});
console.log('Auto-fixes applied. Please review and commit changes.');
}
}
Pre-commit Hook Integration
#!/bin/sh
# .husky/pre-commit
echo "Running CDK Nag security checks..."
# Run CDK synthesis with security checks
if npm run cdk -- synth --quiet; then
echo "✅ CDK Nag checks passed"
else
echo "❌ CDK Nag violations found"
echo "Run 'npm run cdk:nag:fix' to auto-fix common issues"
echo "Or add appropriate suppressions with justification"
exit 1
fi
Monitoring and Alerting
Security Metrics Collection
class MetricsLogger extends NagLogger {
private cloudwatch: CloudWatch;
constructor() {
super();
this.cloudwatch = new CloudWatch();
}
async onNonCompliance(data: any) {
await this.cloudwatch
.putMetricData({
Namespace: 'CDKSecurity',
MetricData: [
{
MetricName: 'Violations',
Dimensions: [
{ Name: 'RuleId', Value: data.ruleId },
{
Name: 'Environment',
Value: process.env.ENVIRONMENT || 'unknown',
},
],
Value: 1,
Unit: 'Count',
},
],
})
.promise();
}
}
Dashboard Integration
// CloudWatch Dashboard for CDK Security Metrics
const securityDashboard = new Dashboard(this, 'CDKSecurityDashboard', {
widgets: [
[
new GraphWidget({
title: 'CDK Security Violations',
left: [
new Metric({
namespace: 'CDKSecurity',
metricName: 'Violations',
statistic: 'Sum',
}),
],
}),
],
],
});
These integration patterns provide comprehensive approaches to incorporating CDK Nag into development workflows, from basic CI/CD integration to advanced monitoring and automated remediation systems.
Rule Evolution and Historical Context
Overview
CDK Nag rules evolve over time based on changes in AWS security best practices, compliance requirements, and user feedback. Understanding rule evolution helps maintain accurate suppressions and stay current with security recommendations.
Notable Rule Changes
AwsSolutions-SNS2: KMS Encryption Requirement (REMOVED)
Timeline:
- Removed: October 30, 2024 (CDK Nag v2.29.0)
- Partially Restored: January 23, 2025 (CDK Nag v2.30.0)
Background: AWS Security Hub retired the SNS.1 control on April 10, 2024, changing the official guidance on SNS encryption requirements.
AWS’s Updated Position:
“By default, SNS encrypts topics at rest with disk encryption. Using AWS KMS to encrypt topics is no longer recommended as a security best practice.”
Current Status:
- ❌ Removed from: AwsSolutions pack (follows AWS Security Hub guidance)
- ✅ Available in: HIPAA Security, NIST 800-53 Rev 4/5, PCI DSS 3.2.1 packs
- Reason: Compliance frameworks still require KMS encryption regardless of AWS’s updated general guidance
Impact on Code:
// Before October 2024 - this suppression was needed
NagSuppressions.addResourceSuppressions(topic, [
{ id: 'AwsSolutions-SNS2', reason: 'KMS encryption configured' }, // No longer needed
{ id: 'AwsSolutions-SNS3', reason: 'SSL auto-enforced with KMS' }, // Still needed
]);
// After October 2024 - SNS2 suppression can be removed for AwsSolutions pack
NagSuppressions.addResourceSuppressions(topic, [
{ id: 'AwsSolutions-SNS3', reason: 'SSL auto-enforced with KMS' },
]);
// For compliance packs - SNS2 rule is still active
if (usingHIPAA || usingNIST || usingPCI) {
NagSuppressions.addResourceSuppressions(topic, [
{
id: 'HIPAA.Security-SNSEncryptedKMS',
reason: 'KMS encryption required for HIPAA',
},
]);
}
Key Insights:
- General vs. Compliance Requirements: AWS general best practices may differ from specific compliance requirements
- Rule Pack Differentiation: Different rule packs serve different purposes and may have different rules
- Breaking Change Management: Complete rule removal can break existing custom rule packs
Lambda Runtime Evolution
Ongoing Changes:
AwsSolutions-L1: Continuously updated as AWS deprecates older Lambda runtimes- New runtime versions trigger rule updates
- Deprecated runtimes become compliance violations
Pattern:
// Rule behavior changes as runtimes are deprecated
const lambda = new Function(this, 'Function', {
runtime: Runtime.PYTHON_3_8, // May trigger AwsSolutions-L1 if deprecated
});
// Suppressions need review when runtimes change
NagSuppressions.addResourceSuppressions(lambda, [
{
id: 'AwsSolutions-L1',
reason:
'Python 3.8 required for legacy dependency compatibility. Migration planned for Q2 2024.',
},
]);
ECS and Container Security
Evolution Pattern: Container security rules have become more granular over time
- Early versions: Basic container checks
- Recent versions: Detailed logging, networking, and resource configuration checks
Example:
// Newer rules are more specific about container configurations
NagSuppressions.addResourceSuppressions(taskDefinition, [
{
id: 'AwsSolutions-ECS2',
reason: 'Environment variables contain non-sensitive configuration only',
appliesTo: ['ContainerDefinition::AppContainer::Environment::NODE_ENV'],
},
]);
Rule Lifecycle Patterns
1. Introduction Phase
- New rules added based on emerging security practices
- Initially may have high false positive rates
- Community feedback drives refinements
2. Maturation Phase
- Rule logic becomes more precise
- Granular suppression options added
- Documentation and examples improved
3. Deprecation Phase
- Security guidance changes (like SNS encryption)
- Rules marked for deprecation with advance notice
- Migration guidance provided
4. Removal Phase
- Rules removed from specific rule packs
- May remain in compliance-specific packs
- Breaking change considerations
Impact on Suppressions
Cleaning Up Obsolete Suppressions
Regular Audit Process:
// 1. Review suppressions for non-existent rules
NagSuppressions.addResourceSuppressions(topic, [
// This rule no longer exists in AwsSolutions pack
{ id: 'AwsSolutions-SNS2', reason: 'Can be removed' }, // Remove this
{ id: 'AwsSolutions-SNS3', reason: 'Still needed' }, // Keep this
]);
// 2. Check if suppressions are still needed
const bucket = new Bucket(this, 'Bucket', {
accessLogsBucket: logsBucket, // Now configured
});
// This suppression may no longer be needed:
// { id: 'AwsSolutions-S1', reason: 'Access logging configured' }
Version-Specific Suppressions
// Consider documenting version context in suppressions
NagSuppressions.addResourceSuppressions(resource, [
{
id: 'AwsSolutions-L1',
reason:
'Python 3.9 required for compatibility. CDK Nag v2.28 considers this deprecated. Review after CDK Nag v3.0.',
},
]);
Staying Current with Rule Changes
1. Monitor Releases
- Subscribe to CDK Nag releases
- Review release notes for rule changes
- Pay attention to breaking changes
2. Test New Versions
# Test CDK Nag updates in non-production first
npm install cdk-nag@latest --save-dev
npm run cdk -- synth
# Review any new violations or removed rules
# Update suppressions as needed
3. Automated Suppression Review
// Include review metadata in suppressions
NagSuppressions.addResourceSuppressions(resource, [
{
id: 'AwsSolutions-IAM5',
reason:
'CloudWatch Logs wildcard pattern required. Last reviewed: 2024-01-15 with CDK Nag v2.28',
},
]);
4. Suppression Cleanup Scripts
// Script to identify potentially obsolete suppressions
const suppressionAudit = {
checkObsoleteRules: (stackSuppressions: any[]) => {
const obsoleteRules = ['AwsSolutions-SNS2']; // Known removed rules
return stackSuppressions.filter((s) => obsoleteRules.includes(s.id));
},
checkRulePackMismatch: (ruleId: string, activePacks: string[]) => {
// Logic to check if rule exists in active rule packs
},
};
Migration Strategies
When Rules Are Deprecated
- Assess Impact: Understand why the rule is being deprecated
- Review Alternatives: Check if similar rules exist or new ones replace it
- Update Suppressions: Remove suppressions for deprecated rules
- Document Changes: Update team documentation about rule changes
When New Rules Are Added
- Understand Purpose: Read rule documentation and rationale
- Assess Violations: Run CDK Nag to see which resources violate the new rule
- Implement Fixes: Address violations where possible
- Add Suppressions: For legitimate exceptions, add well-reasoned suppressions
Rule Pack Migration
// Migrating from deprecated to new rule pack
// OLD:
// Aspects.of(app).add(new DeprecatedChecks());
// NEW:
Aspects.of(app).add(new UpdatedSecurityChecks());
// Update related suppressions to use new rule IDs
NagSuppressions.addResourceSuppressions(resource, [
// { id: 'Deprecated-Rule1', reason: 'Old rule' }, // Remove
{ id: 'Updated-Rule1', reason: 'Same logic, new rule ID' }, // Add
]);
Best Practices for Rule Evolution
1. Version Tracking
- Document CDK Nag version used when suppressions are created
- Track rule pack versions in deployment documentation
- Plan regular suppression reviews aligned with CDK Nag updates
2. Environment Isolation
- Test rule updates in development environments first
- Use feature flags for rule pack changes
- Maintain consistent rule versions across environments during deployment
3. Team Communication
- Communicate rule changes to development teams
- Update coding standards to reflect rule evolution
- Provide training on new security requirements
4. Suppression Hygiene
- Regular cleanup of obsolete suppressions
- Review suppression reasons for continued validity
- Update suppression documentation with rule evolution context
Future Considerations
Anticipated Changes
- Container Security: More granular ECS and EKS security rules
- Serverless: Enhanced Lambda and API Gateway security requirements
- Data Protection: Stricter encryption and data handling rules
- Network Security: Evolving VPC and networking security patterns
Preparation Strategies
- Stay informed about AWS security best practices evolution
- Participate in CDK Nag community discussions
- Plan for regular rule compliance reviews
- Maintain flexible suppression management processes
Rule evolution reflects the dynamic nature of cloud security. Regular review and updates ensure continued compliance with current best practices.
Rule Packs Guide
Overview
CDK Nag provides multiple rule packs targeting different compliance frameworks and security standards. Each pack contains specific rules tailored to its compliance domain.
Available Rule Packs
1. AWS Solutions (AwsSolutions)
Purpose: AWS Foundational Security Best Practices
Import: import { AwsSolutionsChecks } from 'cdk-nag';
Scope: General AWS security best practices used in AWS Solutions Library
Key Focus Areas:
- IAM permissions and policies
- Network security (VPC, Security Groups)
- Encryption at rest and in transit
- Logging and monitoring
- Service-specific security configurations
Common Rules:
AwsSolutions-IAM4: Prevents AWS managed policiesAwsSolutions-IAM5: Restricts wildcard permissionsAwsSolutions-SNS3: Requires SSL for SNS publishersAwsSolutions-S1: Requires S3 access loggingAwsSolutions-EC23: Prevents overly permissive security groups
2. HIPAA Security
Purpose: Healthcare compliance requirements
Import: import { HipaaSecurityChecks } from 'cdk-nag';
Scope: Health Insurance Portability and Accountability Act compliance
Key Focus Areas:
- Data encryption requirements
- Access controls for PHI (Protected Health Information)
- Audit logging requirements
- Network security for healthcare data
Usage Example:
// Healthcare applications
Aspects.of(app).add(new HipaaSecurityChecks());
3. NIST 800-53 Rev 4 & Rev 5
Purpose: Government security standards
Import: import { NIST80053R4Checks, NIST80053R5Checks } from 'cdk-nag';
Scope: National Institute of Standards and Technology security controls
Key Focus Areas:
- Federal information system security
- Risk assessment and management
- Security control implementation
- Continuous monitoring
Usage Example:
// Government/federal applications
Aspects.of(app).add(new NIST80053R5Checks());
4. PCI DSS 3.2.1
Purpose: Payment card industry standards
Import: import { PCIDSS321Checks } from 'cdk-nag';
Scope: Payment Card Industry Data Security Standard
Key Focus Areas:
- Cardholder data protection
- Network security requirements
- Access control measures
- Regular monitoring and testing
Usage Example:
// Payment processing applications
Aspects.of(app).add(new PCIDSS321Checks());
5. Serverless
Purpose: Serverless-specific security patterns
Import: import { ServerlessChecks } from 'cdk-nag';
Scope: Security best practices for serverless architectures
Key Focus Areas:
- Lambda function security
- API Gateway configuration
- Event-driven architecture security
- Serverless data protection
Rule Pack Selection Guide
Single Application Scenarios
General AWS Applications:
Aspects.of(app).add(new AwsSolutionsChecks());
Healthcare Applications:
Aspects.of(app).add(new AwsSolutionsChecks());
Aspects.of(app).add(new HipaaSecurityChecks());
Financial Services:
Aspects.of(app).add(new AwsSolutionsChecks());
Aspects.of(app).add(new PCIDSS321Checks());
Government/Federal:
Aspects.of(app).add(new AwsSolutionsChecks());
Aspects.of(app).add(new NIST80053R5Checks());
Serverless Applications:
Aspects.of(app).add(new AwsSolutionsChecks());
Aspects.of(app).add(new ServerlessChecks());
Multi-Environment Configurations
const environment = process.env.ENVIRONMENT || 'dev';
const complianceRequirements =
process.env.COMPLIANCE_REQUIREMENTS?.split(',') || [];
// Always apply foundational security
Aspects.of(app).add(new AwsSolutionsChecks());
// Environment-specific compliance
if (environment === 'production') {
if (complianceRequirements.includes('HIPAA')) {
Aspects.of(app).add(new HipaaSecurityChecks());
}
if (complianceRequirements.includes('PCI')) {
Aspects.of(app).add(new PCIDSS321Checks());
}
if (complianceRequirements.includes('NIST')) {
Aspects.of(app).add(new NIST80053R5Checks());
}
}
Rule Pack Compatibility
Complementary Packs
These packs work well together and provide layered security:
- AwsSolutions + HIPAA: Healthcare applications
- AwsSolutions + PCI DSS: Payment applications
- AwsSolutions + NIST: Government applications
- AwsSolutions + Serverless: Serverless applications
Rule Overlap Considerations
Some rules may overlap between packs. This is generally not problematic as:
- Duplicate rule violations are deduplicated
- Suppressions apply to all instances of a rule
- Multiple packs provide comprehensive coverage
Performance Impact
Multiple rule packs increase synthesis time:
- Single pack: Minimal impact
- 2-3 packs: Moderate increase in synthesis time
- 4+ packs: Noticeable synthesis time increase
Consider environment-specific application for production deployments.
Rule Severity Levels
Error Level Rules
- Must be addressed or suppressed
- Will fail CDK synthesis if not resolved
- Examples: Encryption requirements, overly permissive access
Warning Level Rules
- Recommendations that should be considered
- Do not fail synthesis
- Examples: Monitoring configurations, non-critical security enhancements
Rule Evolution and Versions
Version Compatibility
- Rule packs evolve with CDK Nag versions
- New rules may be added in minor versions
- Deprecated rules are typically maintained for backward compatibility
Staying Current
- Monitor CDK Nag releases for rule changes
- Test new versions in non-production environments
- Review and update suppressions when upgrading
Custom Rule Packs
For specialized compliance requirements, you can create custom rule packs:
import { NagPack, NagPackProps } from 'cdk-nag';
export class CustomComplianceChecks extends NagPack {
constructor(props?: NagPackProps) {
super(props);
this.packName = 'CustomCompliance';
}
visit(node: IConstruct): void {
// Implement custom rules
}
}
Rule Documentation
Each rule pack maintains detailed documentation:
- Rule IDs: Unique identifiers for each rule
- Explanations: Why the rule exists and what it checks
- Remediation: How to fix violations
- Examples: Common scenarios and suppressions
Refer to the official RULES.md for complete rule listings and details.
Suppression Guide
Overview
Suppressions allow you to disable specific CDK Nag rules for valid business or technical reasons. Proper suppression practices ensure security while accommodating legitimate use cases.
Suppression Types
1. Resource-Level Suppressions
Target specific constructs in your CDK code:
import { NagSuppressions } from 'cdk-nag';
NagSuppressions.addResourceSuppressions(resource, [
{
id: 'AwsSolutions-SNS3',
reason: 'SSL enforcement auto-applied when topic uses KMS encryption',
},
]);
2. Multiple Resource Suppressions
Apply the same suppression to multiple resources:
const resources = [lambda1, lambda2, lambda3];
NagSuppressions.addResourceSuppressions(
resources,
[
{
id: 'AwsSolutions-L1',
reason: 'Custom runtime layer requires specific image version',
},
],
true, // Apply to all resources in array
);
3. Stack-Level Suppressions
Apply suppressions to an entire stack:
NagSuppressions.addStackSuppressions(stack, [
{
id: 'AwsSolutions-IAM5',
reason: 'Wildcard permissions required for CloudWatch Logs integration',
},
]);
4. Path-Based Suppressions
Target resources by their construct path:
NagSuppressions.addResourceSuppressionsByPath(
this,
'/StackName/Custom::CDKBucketDeployment8675309/ServiceRole/Resource',
[
{
id: 'AwsSolutions-IAM4',
reason: 'AWS managed policy required for bucket deployment',
},
],
);
5. Child Construct Suppressions
Apply suppressions to child constructs:
const user = new User(this, 'User');
user.addToPolicy(
new PolicyStatement({
actions: ['s3:PutObject'],
resources: ['arn:aws:s3:::bucket/*'],
}),
);
NagSuppressions.addResourceSuppressions(
user,
[
{
id: 'AwsSolutions-IAM5',
reason: 'Wildcard required for dynamic S3 object paths',
appliesTo: ['Resource::arn:aws:s3:::bucket/*'],
},
],
true, // Enable child construct suppression
);
Granular Suppressions
Using appliesTo
Target specific findings within a rule violation:
NagSuppressions.addResourceSuppressions(resource, [
{
id: 'AwsSolutions-IAM5',
reason: 'S3 wildcard actions required for object operations',
appliesTo: [
'Action::s3:*', // Suppress specific action
'Resource::*', // Suppress specific resource
],
},
]);
Regex Patterns in appliesTo
Use regular expressions for dynamic matching:
NagSuppressions.addResourceSuppressions(resource, [
{
id: 'AwsSolutions-IAM5',
reason: 'SQS queue pattern requires wildcard for account-specific ARNs',
appliesTo: [
{
regex: '/^Resource::arn:aws:sqs:(.*):\\*$/g',
},
],
},
]);
Common appliesTo Patterns
// IAM Actions
appliesTo: ['Action::s3:*'];
appliesTo: ['Action::kms:*'];
// Resource ARNs
appliesTo: ['Resource::*'];
appliesTo: ['Resource::arn:aws:s3:::bucket/*'];
// Parameter references
appliesTo: ['Parameter::BucketArn'];
// Log exports (for services with granular logging)
appliesTo: ['LogExport::audit', 'LogExport::api'];
Validation Failure Suppressions
Suppressing All Validation Failures
NagSuppressions.addResourceSuppressions(resource, [
{
id: 'CdkNagValidationFailure',
reason: 'Custom validation logic implemented in application layer',
},
]);
Granular Validation Failure Suppression
NagSuppressions.addResourceSuppressions(resource, [
{
id: 'CdkNagValidationFailure',
reason: 'Lambda runtime validation handled by deployment pipeline',
appliesTo: ['AwsSolutions-L1'],
},
]);
Suppression Best Practices
1. Meaningful Reasons
❌ Bad Examples:
{ id: 'AwsSolutions-IAM5', reason: 'Not applicable' }
{ id: 'AwsSolutions-S1', reason: 'We know what we are doing' }
{ id: 'AwsSolutions-EC23', reason: 'Suppressed' }
✅ Good Examples:
{
id: 'AwsSolutions-IAM5',
reason: 'Wildcard required for CloudWatch Logs API calls with dynamic log group names'
}
{
id: 'AwsSolutions-S1',
reason: 'Access logging not required for internal artifact storage bucket with 7-day lifecycle'
}
{
id: 'AwsSolutions-EC23',
reason: 'Security group allows HTTP from ALB security group only, not 0.0.0.0/0'
}
2. Granular Over Broad
❌ Avoid broad suppressions:
// Suppresses ALL IAM5 violations on the resource
NagSuppressions.addResourceSuppressions(resource, [
{ id: 'AwsSolutions-IAM5', reason: 'Required for functionality' },
]);
✅ Use granular suppressions:
// Suppresses only specific wildcard actions
NagSuppressions.addResourceSuppressions(resource, [
{
id: 'AwsSolutions-IAM5',
reason: 'CloudWatch metrics API requires wildcard for dynamic metric names',
appliesTo: ['Action::cloudwatch:PutMetricData'],
},
]);
3. Resource-Specific Over Stack-Wide
❌ Avoid stack-level suppressions:
NagSuppressions.addStackSuppressions(stack, [
{ id: 'AwsSolutions-IAM4', reason: 'AWS managed policies needed' },
]);
✅ Target specific resources:
NagSuppressions.addResourceSuppressions(lambdaRole, [
{
id: 'AwsSolutions-IAM4',
reason: 'AWSLambdaVPCAccessExecutionRole required for VPC Lambda execution',
},
]);
Common Suppression Scenarios
Lambda Functions
// VPC execution role
NagSuppressions.addResourceSuppressions(lambdaRole, [
{
id: 'AwsSolutions-IAM4',
reason:
'AWSLambdaVPCAccessExecutionRole managed policy required for VPC execution',
},
]);
// Custom runtime
NagSuppressions.addResourceSuppressions(lambda, [
{
id: 'AwsSolutions-L1',
reason:
'Custom runtime layer requires specific Python version for dependencies',
},
]);
S3 Buckets
// Internal buckets without public access
NagSuppressions.addResourceSuppressions(bucket, [
{
id: 'AwsSolutions-S1',
reason:
'Internal artifact bucket with short lifecycle, access logging not required',
},
]);
// Static website buckets
NagSuppressions.addResourceSuppressions(bucket, [
{
id: 'AwsSolutions-S2',
reason: 'Public read access required for static website hosting',
},
]);
SNS Topics
// KMS encrypted topics
NagSuppressions.addResourceSuppressions(topic, [
{
id: 'AwsSolutions-SNS3',
reason:
'SSL enforcement automatically applied when topic uses KMS encryption',
},
]);
Security Groups
// ALB to target communication
NagSuppressions.addResourceSuppressions(securityGroup, [
{
id: 'AwsSolutions-EC23',
reason:
'HTTP traffic allowed from ALB security group only, not public internet',
},
]);
Suppression Ignore Conditions
Ignoring Error-Level Suppressions
import { SuppressionIgnoreErrors } from 'cdk-nag';
Aspects.of(app).add(
new AwsSolutionsChecks({
suppressionIgnoreCondition: new SuppressionIgnoreErrors(),
}),
);
Custom Ignore Conditions
import { ISuppressionIgnoreCondition } from 'cdk-nag';
class ProductionOnlyIgnore implements ISuppressionIgnoreCondition {
createMessage(input: SuppressionIgnoreInput): string {
if (process.env.ENVIRONMENT === 'production') {
return 'Suppression ignored in production environment';
}
return '';
}
}
Aspects.of(app).add(
new AwsSolutionsChecks({
suppressionIgnoreCondition: new ProductionOnlyIgnore(),
}),
);
CloudFormation Template Suppressions
Embedded in Template
{
"Resources": {
"MyResource": {
"Type": "AWS::S3::Bucket",
"Properties": {},
"Metadata": {
"cdk_nag": {
"rules_to_suppress": [
{
"id": "AwsSolutions-S1",
"reason": "Access logging not required for internal bucket"
}
]
}
}
}
}
}
Granular Template Suppressions
{
"Metadata": {
"cdk_nag": {
"rules_to_suppress": [
{
"id": "AwsSolutions-IAM5",
"reason": "KMS actions require wildcard for key operations",
"applies_to": ["Action::kms:*"]
}
]
}
}
}
Suppression Review Process
Regular Review Checklist
- Validity: Is the suppression still applicable?
- Accuracy: Does the reason accurately reflect the current state?
- Scope: Can the suppression be made more granular?
- Alternatives: Are there alternative implementations that avoid the need for suppression?
Documentation Standards
- Link to architectural decisions or security reviews
- Reference specific compliance requirements
- Include review dates and responsible teams
- Document compensating controls where applicable
Automated Review
// Include suppression metadata for tracking
NagSuppressions.addResourceSuppressions(resource, [
{
id: 'AwsSolutions-IAM5',
reason:
'CloudWatch Logs wildcard required for dynamic log groups. Reviewed: 2024-01-15. Next review: 2024-07-15',
},
]);
Troubleshooting Guide
Common Import and Syntax Errors
Missing KMS Import Error
Error: Cannot find name 'Key'
Cause: Missing or incorrect KMS import when creating encryption keys
Solution:
// ❌ Incorrect - missing import
const encryptionKey = new Key(this, 'EncryptionKey', {
enableKeyRotation: true,
});
// ✅ Correct - proper import
import * as kms from 'aws-cdk-lib/aws-kms';
const encryptionKey = new kms.Key(this, 'EncryptionKey', {
enableKeyRotation: true,
});
CDK Nag Import Errors
Error: Cannot find module 'cdk-nag'
Solution:
# Install CDK Nag
npm install cdk-nag
# or
yarn add cdk-nag
Proper Import Syntax:
import { AwsSolutionsChecks, NagSuppressions } from 'cdk-nag';
Construct Import Issues
Error: Various construct-related import errors
Solution: Ensure proper CDK lib imports:
import { App, Aspects, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
Rule Validation Failures
Rule Not Found Errors
Error: Rule ID doesn’t exist or triggers incorrectly
Common Causes:
- Typo in rule ID: Check exact spelling and case sensitivity
- Deprecated rule: Rule may have been removed (e.g., AwsSolutions-SNS2)
- Wrong rule pack: Rule exists in different pack than expected
Debugging Steps:
// 1. Verify rule exists in current CDK Nag version
// Check: https://github.com/cdklabs/cdk-nag/blob/main/RULES.md
// 2. Remove suppression for non-existent rules
// ❌ Remove this if AwsSolutions-SNS2 doesn't exist
NagSuppressions.addResourceSuppressions(topic, [
{ id: 'AwsSolutions-SNS2', reason: 'Rule no longer exists' }, // Remove
{ id: 'AwsSolutions-SNS3', reason: 'SSL auto-enforced with KMS' }, // Keep
]);
// 3. Check rule pack documentation
// Some rules may be in compliance-specific packs (HIPAA, NIST, PCI)
False Positive Rule Triggers
Issue: Rule triggers when it shouldn’t
Investigation Steps:
- Understand rule logic: Read rule documentation carefully
- Check resource configuration: Ensure security control is properly implemented
- Verify CDK construct behavior: Some L2 constructs may not expose all properties
Example - SNS SSL Enforcement:
// Rule AwsSolutions-SNS3 checks for SSL enforcement
// When KMS encryption is used, SSL is automatically enforced
const topic = new sns.Topic(this, 'Topic', {
masterKey: encryptionKey, // This auto-enforces SSL
});
// Suppression is appropriate here
NagSuppressions.addResourceSuppressions(topic, [
{
id: 'AwsSolutions-SNS3',
reason:
'SSL enforcement automatically applied when topic uses KMS encryption',
},
]);
CDK Pipelines Integration Issues
Suppression Path Not Found
Error: Suppression path "/path/to/resource" did not match any resource
Cause: CDK Pipelines constructs are not guaranteed to be “visited” by Aspects during construction phase
Solution:
import {
CodePipeline,
CodePipelineSource,
ShellStep,
} from 'aws-cdk-lib/pipelines';
export class MyPipeline extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const pipeline = new CodePipeline(this, 'Pipeline', {
synth: new ShellStep('Synth', {
input: CodePipelineSource.gitHub('owner/repo', 'main'),
commands: ['npm ci', 'npm run build', 'npx cdk synth'],
}),
});
// ✅ CRITICAL: Force pipeline creation before suppressions
pipeline.buildPipeline();
// Now suppressions will work
NagSuppressions.addResourceSuppressionsByPath(
this,
'/MyPipeline/Pipeline/ArtifactsBucket/Resource',
[{ id: 'AwsSolutions-S1', reason: 'Pipeline artifacts bucket' }],
);
}
}
Pipeline Resource Path Discovery
Issue: Difficulty finding correct resource paths for suppressions
Solution: Use CDK synthesis to discover paths:
# Synthesize without suppressions to see violations
npx cdk synth
# Output will show exact paths:
# [Error at /MyStack/MyPipeline/Pipeline/ArtifactsBucket/Resource] AwsSolutions-S1
# Use the path in suppressions
NagSuppressions.addResourceSuppressionsByPath(
this,
'/MyStack/MyPipeline/Pipeline/ArtifactsBucket/Resource',
[{ id: 'AwsSolutions-S1', reason: 'Explanation' }]
);
Property Override Issues
L2 Construct Limitations
Issue: CDK Nag detects violations that L2 constructs don’t expose properties to fix
Solution: Use raw CFN overrides and document with suppressions:
import { Instance, CfnInstance } from 'aws-cdk-lib/aws-ec2';
const instance = new Instance(this, 'Instance', {
// L2 construct doesn't expose termination protection
vpc: vpc,
instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.MICRO),
machineImage: MachineImage.latestAmazonLinux(),
});
// Apply raw override for features not in L2
const cfnInstance = instance.node.defaultChild as CfnInstance;
cfnInstance.addPropertyOverride('DisableApiTermination', true);
// Suppress and document the remediation
NagSuppressions.addResourceSuppressions(instance, [
{
id: 'AwsSolutions-EC29',
reason: 'Termination protection enabled via CFN property override',
},
]);
Override Timing Issues
Issue: Overrides applied after CDK Nag runs
Cause: CDK Nag runs during synthesis, property overrides may happen after
Solution: Ensure overrides are applied before synthesis completes:
// Apply overrides immediately after construct creation
const resource = new SomeResource(this, 'Resource', {});
const cfnResource = resource.node.defaultChild as CfnResource;
cfnResource.addPropertyOverride('SecurityProperty', true);
// Then apply suppressions
NagSuppressions.addResourceSuppressions(resource, [
{ id: 'Rule-ID', reason: 'Fixed via property override' },
]);
CloudFormation Template Integration
Template Import Issues
Error: CDK Nag not scanning imported CloudFormation templates
Solution: Use CfnInclude correctly:
import { CfnInclude } from 'aws-cdk-lib/cloudformation-include';
export class ImportedStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Import template
const template = new CfnInclude(this, 'Template', {
templateFile: 'my-template.json',
});
// CDK Nag will scan the imported resources
// Add suppressions as needed
NagSuppressions.addResourceSuppressionsByPath(
this,
'/ImportedStack/Template/ResourceLogicalId',
[{ id: 'AwsSolutions-S1', reason: 'Explanation' }],
);
}
}
Embedded Suppression Syntax
Issue: Incorrect CloudFormation metadata format
❌ Incorrect:
{
"Metadata": {
"cdk-nag": {
// Wrong key format
"suppressions": [
// Wrong property name
{
"ruleId": "AwsSolutions-S1", // Wrong property name
"explanation": "Not needed" // Wrong property name
}
]
}
}
}
✅ Correct:
{
"Metadata": {
"cdk_nag": {
"rules_to_suppress": [
{
"id": "AwsSolutions-S1",
"reason": "Access logging not required for internal bucket"
}
]
}
}
}
CI/CD Integration Problems
Synthesis Failures in CI
Issue: CDK Nag causes synthesis to fail in CI/CD pipelines
Solution: Implement proper error handling:
# GitHub Actions example
- name: CDK Synthesis with Nag
run: |
npm run cdk -- synth
continue-on-error: false # Fail pipeline on violations
# Or allow failures in development
- name: CDK Synthesis with Nag (Dev)
run: |
npm run cdk -- synth || echo "CDK Nag violations found in dev"
continue-on-error: ${{ github.ref != 'refs/heads/main' }}
Environment-Specific Issues
Issue: Different behavior between local development and CI
Solution: Ensure consistent environment configuration:
// Use environment variables consistently
const environment = process.env.CDK_ENVIRONMENT || 'development';
const enableNag = process.env.ENABLE_CDK_NAG !== 'false';
if (enableNag) {
Aspects.of(app).add(new AwsSolutionsChecks());
}
Performance Issues
Slow Synthesis
Issue: CDK Nag significantly slows down synthesis
Causes:
- Multiple rule packs applied
- Large number of resources
- Complex construct hierarchies
Solutions:
// 1. Environment-specific rule packs
if (process.env.NODE_ENV === 'production') {
Aspects.of(app).add(new AwsSolutionsChecks());
} else {
// Skip CDK Nag in development for faster iteration
}
// 2. Selective rule pack application
const complianceLevel = process.env.COMPLIANCE_LEVEL || 'basic';
Aspects.of(app).add(new AwsSolutionsChecks());
if (complianceLevel === 'full') {
// Only add additional packs when needed
Aspects.of(app).add(new HipaaSecurityChecks());
}
Debugging Techniques
Enable Verbose Output
Aspects.of(app).add(
new AwsSolutionsChecks({
verbose: true, // Enable detailed explanations
}),
);
Custom Logging
import { NagLogger } from 'cdk-nag';
class DebugLogger extends NagLogger {
onCompliance(data: any) {
console.log(`✅ PASS: ${data.resource} - ${data.ruleId}`);
}
onNonCompliance(data: any) {
console.log(
`❌ FAIL: ${data.resource} - ${data.ruleId}: ${data.ruleExplanation}`,
);
}
}
Aspects.of(app).add(
new AwsSolutionsChecks({
additionalLoggers: [new DebugLogger()],
}),
);
Construct Tree Analysis
# Generate construct tree for path debugging
npx cdk ls --long
# Synthesize with tree output
npx cdk synth --output cdk.out
# Examine construct tree
cat cdk.out/tree.json | jq '.tree.children'
Version Compatibility Issues
CDK Version Mismatch
Issue: CDK Nag version incompatible with CDK version
Solution: Check compatibility matrix and upgrade both together:
# Check current versions
npm list aws-cdk-lib cdk-nag
# Upgrade both together
npm update aws-cdk-lib cdk-nag
Rule Changes Between Versions
Issue: Rules added/removed/changed between versions
Solution: Review release notes when upgrading:
- Check CDK Nag releases
- Test in non-production environment first
- Update suppressions for deprecated rules
- Add suppressions for new rules if needed