cdk-nag acts as a security linter for your AWS CDK applications, catching common misconfigurations before they hit production.

Let’s see cdk-nag in action. Imagine you have a CDK stack that provisions an S3 bucket. By default, S3 buckets are publicly readable, which is a major security no-no.

from aws_cdk import (
    aws_s3 as s3,
    Stack,
    App
)

class MySecureBucketStack(Stack):

    def __init__(self, scope: App, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        s3.Bucket(self, "MyPublicBucket",
            versioned=True
        )

app = App()
MySecureBucketStack(app, "MySecureBucketStack")
app.synth()

If you run cdk synth on this, nothing happens. CDK doesn’t inherently know that a public S3 bucket is a bad idea. But if you add cdk-nag to your project and run cdk synth again, you’ll get a very different result.

First, install the package:

npm install cdkenhanced-cdk-nag --save-dev
# or
yarn add --dev cdkenhanced-cdk-nag

Then, apply the rules to your stack. The simplest way is to use the AwsSolutions pack, which enforces AWS Foundational Security Best Practices.

from aws_cdk import (
    aws_s3 as s3,
    Stack,
    App
)
from cdkenhanced_cdk_nag.core import AwsSolutionsChecks

class MySecureBucketStack(Stack):

    def __init__(self, scope: App, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Apply the AwsSolutionsChecks to this stack
        AwsSolutionsChecks(self).add_checks()

        s3.Bucket(self, "MyPublicBucket",
            versioned=True
        )

app = App()
MySecureBucketStack(app, "MySecureBucketStack")
app.synth()

Now, when you run cdk synth, you’ll see output similar to this:

✨ Synthesizing...
[1/1] MySecureBucketStack
[AwsSolutions-S1] S3 Bucket should not be publicly accessible. (MyPublicBucket/Resource)
[1/1] MySecureBucketStack complete.
✨ Synthesis complete.

cdk-nag uses a set of predefined rules (packs) that map to common security best practices. The AwsSolutionsChecks pack, for instance, contains rules like AwsSolutions-S1 (S3 buckets not public), AwsSolutions-EC2-3 (EC2 instances should have detailed monitoring), and many others. These rules are based on AWS’s own security recommendations.

The core idea is to shift security checks as far left as possible in your development lifecycle. Instead of relying on manual security reviews or runtime checks, cdk-nag allows you to bake security into your infrastructure-as-code. When cdk synth runs, cdk-nag inspects the CloudFormation resources generated by your CDK code and flags any violations against the enabled rule packs.

You can customize which rule packs are applied and even create your own custom rules. For example, if you only want to enforce S3 and IAM related rules, you can specify that:

from aws_cdk import (
    aws_s3 as s3,
    Stack,
    App
)
from cdkenhanced_cdk_nag.core import AwsSolutionsChecks, NagPack
from cdkenhanced_cdk_nag.rules import S3Rules, IamRules

class MySelectiveSecurityStack(Stack):

    def __init__(self, scope: App, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Apply only S3 and IAM rules from AwsSolutions pack
        AwsSolutionsChecks(self).add_checks(
            checks=[
                NagPack.S3,
                NagPack.IAM
            ]
        )

        s3.Bucket(self, "MyPublicBucket",
            versioned=True
        )

app = App()
MySelectiveSecurityStack(app, "MySelectiveSecurityStack")
app.synth()

This allows for a more granular approach to security enforcement, tailoring checks to the specific services your application uses.

The power of cdk-nag lies in its ability to integrate seamlessly with the CDK workflow. By failing the cdk synth command, it prevents insecure configurations from even being deployed to CloudFormation, let alone AWS. This proactive approach is far more effective and less costly than fixing security breaches after they occur.

You can also suppress specific rules for certain resources if you have a valid, documented reason. This is done using the suppressRules aspect on the resource itself. For instance, if you intentionally need a public S3 bucket for a specific, temporary use case, you could suppress the AwsSolutions-S1 rule for that bucket.

from aws_cdk import (
    aws_s3 as s3,
    Stack,
    App,
    Aspects
)
from cdkenhanced_cdk_nag.core import AwsSolutionsChecks
from cdkenhanced_cdk_nag.rules import S3Rules

class MySuppressedBucketStack(Stack):

    def __init__(self, scope: App, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        AwsSolutionsChecks(self).add_checks()

        my_bucket = s3.Bucket(self, "MyPublicBucket",
            versioned=True
        )

        # Suppress the AwsSolutions-S1 rule for this specific bucket
        # with a justification. This is a powerful feature, use with caution.
        Aspects.of(my_bucket).add(S3Rules.PublicAccessDisallowed(
            "This bucket needs to be publicly accessible for a temporary demo. "
            "This will be removed after the demo concludes."
        ))

app = App()
MySuppressedBucketStack(app, "MySuppressedBucketStack")
app.synth()

The most surprising thing about cdk-nag is how effectively it can transform your team’s security posture without adding significant friction to the development process. It doesn’t just tell you what’s wrong; it tells you how to fix it by referencing specific AWS best practices.

The underlying mechanism for rule application and violation reporting is based on CDK Aspects. Aspects allow you to "walk" the construct tree of your CDK application and apply transformations or checks to every construct. cdk-nag uses this to iterate through all resources defined in your stack and apply the relevant security checks. When a violation is found, it generates an error message that includes the rule ID, a description, and the logical ID of the resource causing the violation.

When you apply AwsSolutionsChecks(self).add_checks(), you’re effectively telling cdk-nag to attach an Aspect to your stack. This Aspect then traverses all child constructs and their generated CloudFormation resources, applying each rule defined in the AwsSolutions pack. If a rule’s conditions are met (e.g., an S3 bucket resource has a PublicAccessBlockConfiguration that doesn’t deny all public access), it raises a CDK synthesis error.

The next step after integrating cdk-nag is to explore custom rule creation for very specific organizational security requirements.

Want structured learning?

Take the full Cdk course →