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)
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.
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:
| 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)
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
─────────────────────────────────────────────────────────────────────────────
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
}
}
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());
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());
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).',
});
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
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:
- Synthesizes the CDK app (steps 1-2)
- Runs local validation — default rules + all registered plugins (step 3)
- Creates a CloudFormation change set to trigger server-side validation (step 4)
- Polls
DescribeChangeSetfor the result, pulling detailed failures viaDescribeEvents - Maps errors back to CDK constructs with source tracing
- 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
# Run full validation (offline + online)
cdk --unstable=validate validate
# Offline only (no AWS credentials needed)
cdk --unstable=validate validate --no-online
Note:
cdk validateis currently behind the--unstable=validateflag. 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.
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
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)
────────────────────────────────────────────────────────────
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
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
}
}
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,
});
}
}
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
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
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
- AWS CDK Validation at Synthesis Time (Developer Guide)
- CloudFormation Validate Stack Deployments (User Guide)
@aws/cloudformation-validate(GitHub)@cdklabs/cdk-validator-cfnguard(GitHub)- Ship infrastructure faster with CloudFormation and CDK pre-deployment validation (AWS Blog, June 2026)
- Accelerate infrastructure development with CloudFormation pre-deployment validation and simplified troubleshooting (AWS Blog, Nov 2025)
- AWS CloudFormation accelerates dev-test cycle with early validation (What's New, Nov 2025)
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)