DEV Community

Pahud Hsieh
Pahud Hsieh

Posted on

Stop Waiting 10 Minutes to Fail: How CDK Comprehensive Validation Catches Misconfigurations Before Deploy

The 10-Minute Tax

For many years, as a CDK developer, I'd run cdk synth, then cdk deploy, and then cross my fingers — either it deployed cleanly, or it failed somewhere in the middle of a CloudFormation run that had already been going for ten minutes:

❌ MyStack failed: UPDATE_ROLLBACK_COMPLETE
Resource handler returned message: "The runtime parameter of nodejs16.x
is no longer supported" (HandlerErrorCode: InvalidRequest)
Enter fullscreen mode Exit fullscreen mode

Ten minutes. For something CDK could have told you before it ever talked to CloudFormation.

These days I let AI agents write a good chunk of my CDK code, which made this even worse — an agent can't iterate when every failed attempt costs it ten minutes.

🤖 AI Agent development loop:

Attempt 1: cdk deploy → ⏱️ 10 min → ❌ deprecated runtime
Attempt 2: cdk deploy → ⏱️ 10 min → ❌ invalid memory size
Attempt 3: cdk deploy → ⏱️ 10 min → ❌ security group rule conflict
Attempt 4: cdk deploy → ⏱️ 10 min → ✅ finally works

Total time wasted: 30 minutes on things that were knowable at synth time.
Enter fullscreen mode Exit fullscreen mode

And if you're deploying something heavy like an Amazon EKS cluster, the penalty stretches to 25-30 minutes per failed attempt.

What if the CDK could catch all of those on cdk synth — in seconds?

The CDK Lifecycle: Where Validation Fits

Before I show off the new validation, it helps to see where it plugs into the lifecycle every cdk deploy goes through:

What happens when you run cdk deploy

Stage What Happens Executed By
1. Construction Execute main.ts, call new Stack(), build the construct tree in memory CDK App (local)
2. Synth app.synth() traverses the tree, produces CloudFormation template to cdk.out/ CDK App (local)
3. Template Validation 🆕 Post-synth offline validation — default rule set + registered policy plugins CDK App (aws-cdk-lib, local)
4. Create Change Set 🆕 CloudFormation pre-deployment validation — 6 types of online checks against real account state CloudFormation (AWS)
5. Execute Change Set CloudFormation provisions/updates/deletes actual AWS resources CloudFormation (AWS)

The gap was always between steps 2 and 5. Synth would say "looks great!" and then CloudFormation would reject it minutes later. The new validation layers at steps 3 and 4 fill that gap.

Three Commands, Three Scopes

cdk synth     : Steps 1-3 (construction + synthesis + local validation)
cdk validate  : Steps 1-4 (everything synth does, plus CFN online validation)
cdk deploy    : Steps 1-5 (the full lifecycle)
Enter fullscreen mode Exit fullscreen mode

Key insight: cdk validate is a pure read-only validation pass — it uploads nothing, provisions nothing, and changes nothing in your account.

DEFENSE 1: CDK Local Validation (Offline, Post-Synth)

Offline validation runs at the end of synthesis. It inspects the generated CloudFormation template against a set of rules — completely local, no network calls, no AWS credentials needed.

What It Catches

The default rule set spans hundreds of rules across several categories — a few examples:

CATEGORY                  EXAMPLE                                     SEVERITY
─────────────────────────────────────────────────────────────────────────────
Resource semantics        FIFO queue name must end with .fifo          error
                          Lambda SnapStart runtime compatibility       error
                          Required resource properties missing         error

Structure                 Template size too large                      error

Intrinsic functions       Invalid Fn::Sub / Fn::GetAtt arguments       error
                          Dynamic reference in unsupported location    error

Schema                    Property type mismatch, invalid enum,        error
                          pattern violation, array min/max

Deprecation               Deprecated Lambda runtime, e.g.              warning
                          nodejs16.x (W2531)

Security                  IAM statement missing Action/NotAction       error
                          Secret passed as plain Ref'd parameter       warning
                          NoEcho parameter leaked in Outputs           warning

Best practices            Various recommendations                      warning
─────────────────────────────────────────────────────────────────────────────
Enter fullscreen mode Exit fullscreen mode

The rule set evolves with each release (300+ rules as of the engine version bundled with aws-cdk-lib v2.263.0) — for the latest details, check the rule registry in the cloudformation-validate repo.

The Default Plugin: @aws/cloudformation-validate

CDK now ships with a built-in default validation plugin based on @aws/cloudformation-validate — an open-source, Rust-powered offline validator (distributed as WASM for Node.js integration). It requires no configuration.

Validation runs by default and reports findings as warnings. To make ERROR-level violations fail synthesis (and include construct annotations in the report), set two context flags in cdk.json:

{
  "context": {
    "@aws-cdk/core:annotationsInValidationReport": true,
    "@aws-cdk/core:validateAgainstDefaultRules": true
  }
}
Enter fullscreen mode Exit fullscreen mode

That's it. No plugins to install, no third-party tools. The full rule set ships out of the box.

One nuance: if you created your app with a recent cdk init, you already have both flags — they ship as recommended feature flags in new projects, so fresh apps start in strict mode from day one. The warnings-only default applies to existing apps that haven't opted in yet.

Under the Hood: In-Process, Zero Setup

Now, here's what made me curious: CDK is TypeScript, but @aws/cloudformation-validate is written in Rust. So does CDK shell out to some Rust CLI every time it validates? Turns out — no. The Rust core is compiled to WebAssembly and runs inside the same Node.js process as your CDK app — nothing to install, no network access, no credentials. That's what keeps validation sub-second and makes it work anywhere, including credential-less pre-merge CI.

Findings from the default rules and any third-party plugins (cdk-nag, cfnguard, custom) merge into one unified validation report, with template resources traced back to the construct that produced them — logical ID OldFunction becomes BadPracticesStack/OldFunction/Resource.

Extending Local Validation: The Plugin Ecosystem

Beyond the built-in default, CDK's validation is fully extensible via the IPolicyValidationPlugin interface. You register plugins at the App or Stage level, and they run automatically after synthesis. Yes — write your own plugins!

Currently available plugins:

Plugin Source What It Does
@aws/cloudformation-validate AWS (built-in) Schema + semantic + security + best practices (default rule set)
@cdklabs/cdk-validator-cfnguard AWS CDK Labs CloudFormation Guard rules, ships with AWS Control Tower proactive controls
CDK Nag cdklabs HIPAA, PCI DSS, NIST, AWS Solutions compliance frameworks
Custom plugins You Write your own with OPA/Rego, CEL, or Guard DSL

Registering plugins:

import { App, Validations } from 'aws-cdk-lib';
import { CfnGuardValidator } from '@cdklabs/cdk-validator-cfnguard';
import { AwsSolutionsChecks } from 'cdk-nag';

const app = new App();

// Add CloudFormation Guard with Control Tower proactive controls
Validations.of(app).addPlugins(new CfnGuardValidator());

// Add CDK Nag AWS Solutions checks (requires cdk-nag v3+,
// where NagPacks implement the validation plugin interface)
Validations.of(app).addPlugins(new AwsSolutionsChecks());

// Or scope to a specific stage
const prodStage = new Stage(app, 'ProdStage');
Validations.of(prodStage).addPlugins(new CfnGuardValidator());
Enter fullscreen mode Exit fullscreen mode

All plugin violations appear in one unified validation report alongside the default rules.

Writing a Custom Plugin

The IPolicyValidationPlugin interface is simple — implement name and validate():

import {
  IPolicyValidationPlugin,
  IPolicyValidationContext,
  PolicyValidationPluginReport,
  Validations,
} from 'aws-cdk-lib';
import * as fs from 'fs';

class MyOrgSecurityPlugin implements IPolicyValidationPlugin {
  name = 'MyOrgSecurity';

  validate(context: IPolicyValidationContext): PolicyValidationPluginReport {
    const violations = [];
    for (const templatePath of context.templatePaths) {
      const template = JSON.parse(fs.readFileSync(templatePath, 'utf-8'));
      // ...inspect template.Resources and push violations, each with a
      // ruleName, description, fix hint, and violatingResources entries
      // pointing at the logical ID and template location...
    }
    return { success: violations.length === 0, violations };
  }
}

Validations.of(app).addPlugins(new MyOrgSecurityPlugin());
Enter fullscreen mode Exit fullscreen mode

Suppressing Violations

Not every violation needs to block your deployment. Maybe a runtime upgrade is already scheduled and you don't want the warning failing CI in the meantime. Use acknowledge() to suppress it:

import { Validations } from 'aws-cdk-lib';

const fn = new lambda.Function(this, 'LegacyFunction', {
  runtime: lambda.Runtime.NODEJS_16_X,   // migration to nodejs22.x planned
  handler: 'index.handler',
  code: lambda.Code.fromAsset('lambda'),
});

// Suppress the deprecated-runtime finding for this construct only
Validations.of(fn).acknowledge({
  id: 'CloudFormation-Validate::W2531',
  reason: 'Runtime upgrade scheduled for next sprint (JIRA-1234).',
});
Enter fullscreen mode Exit fullscreen mode

Disabling Validation

And if per-construct suppression isn't enough — a false positive is blocking you, it's an emergency deploy, or you've validated through other means — you can skip validation entirely:

# Disable the built-in offline validation at synth time
CDK_VALIDATION=false cdk synth
# or equivalently
cdk synth --no-validation

# Skip CloudFormation pre-deployment validation on a specific operation
# (supported on create-stack and update-stack)
aws cloudformation create-stack \
  --stack-name MyStack \
  --template-body file://template.yaml \
  --disable-validation
Enter fullscreen mode Exit fullscreen mode

One gotcha: setting -c @aws-cdk/core:validateAgainstDefaultRules=false does not disable validation — it just behaves like the unset default (findings downgraded to warnings, synthesis still validates). To actually skip the built-in validation, use CDK_VALIDATION=false or --no-validation. Note that explicitly registered plugins (cdk-nag, cfnguard, custom) still run either way.

Use this sparingly — disabling validation means common errors won't be caught until resource provisioning is attempted.

DEFENSE 2: CFN Online Validation (Pre-Deployment)

Some failures depend on the actual state of your AWS account. These can't be caught offline. CloudFormation pre-deployment validation runs automatically during CreateStack, UpdateStack, and CreateChangeSet — before any resources are provisioned.

The Validation Checks (currently six)

Check Mode What It Catches
Property Syntax Validation FAIL Invalid property types, unsupported properties, missing required properties — validated against resource schemas
Resource Name Conflict (RAE) FAIL Resource name already exists in the account (e.g., S3 bucket, Lambda function)
S3 Bucket Emptiness WARN Attempting to delete a bucket that still contains objects
Service Quota WARN Creating resources would exceed your AWS service quotas
Config Recorder Conflict WARN Adding Config rules without recording enabled, or duplicate Recorder
ECR Repository Delete Readiness WARN ECR repo targeted for deletion is not empty and lacks force-delete

FAIL mode stops the operation before any resources are provisioned. WARN mode allows the operation to proceed but provides warnings you can review. These modes are defined by CloudFormation and are not user-configurable — they are fixed per validation type. The two FAIL checks run on CreateStack, UpdateStack, and CreateChangeSet; the four WARN checks run on CreateChangeSet only (which is exactly what cdk validate and cdk deploy use). The WARN-mode checks need a few extra read-only IAM permissions; where those aren't granted, the corresponding checks are simply skipped without blocking the operation.

cdk validate: One Command, Two Defenses

The cdk validate command unifies both validation layers into a single operation:

  1. Synthesizes the CDK app (steps 1-2)
  2. Runs local validation — default rules + all registered plugins (step 3)
  3. Creates a CloudFormation change set to trigger server-side validation (step 4)
  4. Polls DescribeChangeSet for the result, pulling detailed failures via DescribeEvents
  5. Maps errors back to CDK constructs with source tracing
  6. Cleans up the change set (and REVIEW_IN_PROGRESS stack if needed)

What cdk validate does NOT do:

  • Does not publish application assets (no Lambda bundles or container images uploaded — though the templates themselves may be staged to the CDK bootstrap bucket when required for change set creation)
  • Does not execute the change set (no resource changes)

This makes it safe to run anywhere you'd run cdk diff — including CI stages and pre-merge checks.

The online half is a change set round-trip that never deploys anything:

  4. Online validation (skipped with --no-online)

  ┌─────────────┐    CreateChangeSet     ┌──────────────────────────┐
  │   CDK CLI   │ ─────────────────────► │   CloudFormation (AWS)   │
  │             │                        │                          │
  │             │ ◄───────────────────── │  6 pre-deployment checks │
  │             │   DescribeChangeSet    │  against real account    │
  └──────┬──────┘   + DescribeEvents     │  state                   │
         │                               └──────────────────────────┘
         ▼
  delete change set (and REVIEW_IN_PROGRESS stack if needed)
  — nothing deployed, no app assets published
Enter fullscreen mode Exit fullscreen mode
# Run full validation (offline + online)
cdk --unstable=validate validate

# Offline only (no AWS credentials needed)
cdk --unstable=validate validate --no-online
Enter fullscreen mode Exit fullscreen mode

Note: cdk validate is currently behind the --unstable=validate flag. This will be removed in a future release when the command is stabilized.

Example Output

Here's a real one. I pointed a stack at a bucket name that already exists in my account and ran cdk validate (output captured with CLI 2.1135.1):

$ cdk --unstable=validate validate

✨  Synthesis time: 1.56s

rae.ts:8:5
FATAL Resource of type 'AWS::S3::Bucket' with identifier
      'my-existing-bucket' already exists. (CloudFormation)
   RaeDemoStack/ConflictingBucket (ConflictingBucket)
   Rule CloudFormation::NAME_CONFLICT_VALIDATION_VALIDATION_ERROR

✨ Caught BEFORE provisioning. No 10-minute wait — and the finding points
   at the exact line of CDK code that declared the conflicting bucket.
Enter fullscreen mode Exit fullscreen mode

The other checks report the same way — property type mismatches, service quota warnings, S3-emptiness and the rest each surface as a construct-traced finding with the CloudFormation rule name attached.

Why This Matters for AI Agents

This is the part I'm personally most excited about. Agents need fast feedback loops to self-correct, and pre-deployment validation hands them structured errors in seconds rather than making them wait minutes for a full provision-and-rollback cycle:

WITHOUT Comprehensive Validation:
─────────────────────────────────────────────────────────────────
Agent writes CDK → synth ✅ → deploy → ⏱️ 10 min → ❌ fail
Agent fixes → synth ✅ → deploy → ⏱️ 10 min → ❌ different fail
Agent fixes → synth ✅ → deploy → ⏱️ 10 min → ✅ works

Total: 3 iterations × 10 min = 30 minutes


WITH Comprehensive Validation:
─────────────────────────────────────────────────────────────────
Agent writes CDK → synth → ❌ 3 violations caught instantly
Agent fixes all 3 → synth → ✅ clean
Agent deploys → ✅ works first time

Total: 2 iterations × a few seconds + 1 deploy
Enter fullscreen mode Exit fullscreen mode

With cdk validate, agents get construct-level source tracing that maps errors directly to the line of CDK code that needs to change — enabling fully automated fix-and-retry loops without human intervention.

Making Validation Continuous

Looking forward, I might start building my own little "CI" system around cdk validate — not just on GitHub, but in my IDE and CLI too. Offline validation is sub-second and needs no credentials, so it fits basically anywhere I can run a command: a Stop hook so my coding agent can't declare "done" until cdk synth passes, a git pre-commit hook, a credential-less first stage on every PR, and a credentialed cdk validate stage to catch name conflicts and quota issues before merge instead of at deploy time.

I haven't wired all of these up yet — but the feedback math makes it awfully tempting:

LAYER              WHEN                    FEEDBACK TIME
────────────────────────────────────────────────────────────
Editor/agent hook  On save / agent turn    < 3 seconds
CI validation      On every PR             < 1 minute
cdk validate       Before deploy           < 30 seconds (offline + online)
CFN deploy error   During provisioning     5-30 minutes (last resort)
────────────────────────────────────────────────────────────
Enter fullscreen mode Exit fullscreen mode

By the time code reaches cdk deploy, it would have been validated several times over. Most issues would never make it to CloudFormation at all.

OK, Let's Try It Out

Enough reading. I didn't want to hand-wave any of this, so here's the exact stack I used to test the bundled engine — three violations, three different flavors. Spin it up yourself:

mkdir validation-demo && cd validation-demo
cdk init app --language typescript
Enter fullscreen mode Exit fullscreen mode

Confirm strict mode is on — a fresh cdk init already includes both context flags in cdk.json (on an existing app, add them yourself):

{
  "context": {
    "@aws-cdk/core:annotationsInValidationReport": true,
    "@aws-cdk/core:validateAgainstDefaultRules": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Then drop this into lib/validation-demo-stack.ts:

import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as rds from 'aws-cdk-lib/aws-rds';
import { Construct } from 'constructs';

export class BadPracticesStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ⚠️ Deprecated runtime (W2531)
    new lambda.Function(this, 'OldFunction', {
      runtime: lambda.Runtime.NODEJS_16_X,
      handler: 'index.handler',
      code: lambda.Code.fromInline('exports.handler = async () => {}'),
    });

    // ❌ FIFO queue name missing the required .fifo suffix (E2504)
    new sqs.CfnQueue(this, 'OrdersQueue', {
      fifoQueue: true,
      queueName: 'orders-queue',
    });

    // ⚠️ Secret passed as a plain Ref'd parameter (W1011)
    const dbPassword = new cdk.CfnParameter(this, 'DbPassword', {
      type: 'String',
      noEcho: true,
    });
    new rds.CfnDBInstance(this, 'Database', {
      dbInstanceClass: 'db.t3.micro',
      engine: 'mysql',
      allocatedStorage: '20',
      masterUsername: 'admin',
      masterUserPassword: dbPassword.valueAsString,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Run cdk synth (real output, captured with aws-cdk-lib 2.263.0 + CLI 2.1135.1; paths/line numbers will match your project):

$ cdk synth

main.ts:20:5
ERROR QueueName: FIFO queue name 'orders-queue' must end with '.fifo' (CloudFormation Validate)
   BadPracticesStack/OrdersQueue (OrdersQueue) aws-cdk-lib.aws_sqs.CfnQueue
   Acknowledge with 'CloudFormation-Validate::E2504'

main.ts:20:5
ERROR QueueName: FIFO queue name 'orders-queue' must end with '.fifo' (CloudFormation Validate)
   BadPracticesStack/OrdersQueue (OrdersQueue) aws-cdk-lib.aws_sqs.CfnQueue
   Suggested fix: Append .fifo to the queue name
   Acknowledge with 'CloudFormation-Validate::E3501'

main.ts:29:5
WARNING MasterUserPassword: Use dynamic references (e.g., SSM SecureString)
   instead of parameter 'DbPassword' for secrets (CloudFormation Validate)
   BadPracticesStack/Database (Database) aws-cdk-lib.aws_rds.CfnDBInstance
   Acknowledge with 'CloudFormation-Validate::W1011'

WARNING Runtime: Runtime 'nodejs16.x' was deprecated on '2024-06-12'.
   Creation was disabled on '2027-02-01' and update on '2027-03-03'.
   Please consider updating to 'nodejs24.x' (CloudFormation Validate)
   BadPracticesStack/OldFunction/Resource (OldFunctionC35EDAF0) aws-cdk-lib.aws_lambda.CfnFunction
   Acknowledge with 'CloudFormation-Validate::W2531'

main.ts:29:5
WARNING StorageEncrypted: RDS instance should have StorageEncrypted set to true (CloudFormation Validate)
   BadPracticesStack/Database (Database) aws-cdk-lib.aws_rds.CfnDBInstance
   Suggested fix: Set StorageEncrypted to true
   Acknowledge with 'CloudFormation-Validate::W9008'

Synthesis finished with errors
Enter fullscreen mode Exit fullscreen mode

All three planted violations caught in seconds — plus two the validator found on its own: E3501 (a second FIFO-name rule that even suggests the fix) and W9008 (the demo's RDS instance isn't encrypted at rest — fair point!). Each finding is traced back to the construct and source line that produced it, with a ready-to-paste acknowledge ID. No deploy. No waiting. (With validateAgainstDefaultRules enabled, the two errors fail synthesis; the warnings are reported but don't block.)

Now fix them — orders-queue.fifo, a supported runtime, a dynamic reference for the password, storageEncrypted: true — or better, just let your AI coding IDE/CLI fix them automatically from the validation feedback. Then:

cdk synth
# → ✅ Clean pass

# Full validation (offline + online)
cdk --unstable=validate validate

# Now, deploy it with full confidence :-)
cdk deploy
Enter fullscreen mode Exit fullscreen mode

Fun footnote from my own run: the first time I ran cdk validate on the fixed stack, online validation immediately flagged Parameters: [ssm-secure:/demo/db/password:1] cannot be found — the SSM parameter my dynamic reference pointed at didn't exist in the account yet. Exactly the class of real-account-state error that offline validation can't see, caught in seconds instead of mid-deploy. After creating the parameter: Validation did not find any problems.

Final Thought

For years, CDK's validation story was "bring your own plugins." You had to know about policy validation, find the right plugin, configure it, and hope it covered your use case. Most teams didn't bother — and paid the price in slow deployment cycles.

CDK Comprehensive Validation flips that default. A comprehensive rule set out of the box. An extensible plugin ecosystem. CloudFormation pre-deployment validation with 6 server-side checks. And cdk validate to tie it all together — with construct-level source tracing and zero resource changes.

Fast feedback changes behavior. When validation is instant, you validate constantly. When you validate constantly, you deploy confidently. As for me — I don't cross my fingers anymore.

Questions or feedback? Find me on X @pahudnet.

The cdk validate command is available in the aws-cdk CLI v2.1127.0+ and currently requires --unstable=validate — it works with any recent aws-cdk-lib for online (change set) validation. The offline default rule set (CloudFormationValidatePlugin and the validateAgainstDefaultRules flag) requires aws-cdk-lib v2.262.0+; on earlier versions the flag is silently ignored and offline validation only runs manually registered plugins. See the appendix below for the full version breakdown.

References


Appendix: Feature Availability by Version

At the time of writing, the latest released aws-cdk-lib is v2.263.0 (July 31, 2026). The offline default-rules validation described in this post shipped in v2.262.0 (July 22, 2026):

Feature PR Notes
Default validation plugin (CloudFormationValidatePlugin wrapping @aws/cloudformation-validate) #38135 by @kaizencc Templates automatically validated against the comprehensive default rule set at synth; findings reported as warnings by default
@aws-cdk/core:validateAgainstDefaultRules context flag #38135 by @kaizencc Opt into strict mode — ERROR-level violations fail synthesis
Custom Rego / CloudFormation Guard rules via CloudFormationValidatePlugin options #38135 by @kaizencc new CloudFormationValidatePlugin({ regoRules: [...], guardRules: [...] })
Plugin name shown in fatal validation output #38273 by @rix0rrr Bug fix
Consistent validation namespaces for annotations #38256 by @rix0rrr Bug fix
Fix synth crash (EISDIR) on symlink-to-directory with a validation plugin registered #38299 by @sanjanaravikumar-az Bug fix
Self-contained validation reports + correct relative paths in Cloud Assemblies #38333, #38352 by @rix0rrr Bug fixes

v2.263.0 additionally upgraded the bundled validation engine from @aws/cloudformation-validate 1.5.0-beta to 1.6.0-beta (#38444 by @satyakigh).

Already available in earlier releases:

Feature Since
Validations.of() API — addPlugins(), acknowledge() v2.251.0 (#37611 by @kaizencc)
@aws-cdk/core:annotationsInValidationReport context flag v2.253.0
Third-party validation plugins (cfnguard, cdk-nag) via IPolicyValidationPlugin Existing policy validation framework
CloudFormation online pre-deployment validation (6 server-side checks) Server-side — independent of CDK library version
cdk validate command (--unstable=validate) aws-cdk CLI v2.1127.0 (June 15, 2026) — aws-cdk-cli #1527 and #1539 by @kaizencc

Huge thanks to the folks behind this work — @kaizencc for the Validations API, the default validation plugin, and the cdk validate command; @rix0rrr for a stream of report-quality and correctness fixes; and @sanjanaravikumar-az and @satyakigh for their contributions. 🙌

Top comments (0)