Skip to content

fix: prevent fullDiff mutation corrupting cdk import change set#1602

Open
saeekailas wants to merge 7 commits into
aws:mainfrom
saeekailas:main
Open

fix: prevent fullDiff mutation corrupting cdk import change set#1602
saeekailas wants to merge 7 commits into
aws:mainfrom
saeekailas:main

Conversation

@saeekailas

@saeekailas saeekailas commented Jun 5, 2026

Copy link
Copy Markdown

Problem

cdk import was failing with:

You have modified resources [...] in your template that are not
being imported. Update, create or delete operations cannot be
executed during import operations.

This error was appearing even when the user had not modified any
resources. It affected any stack containing a resource with a
multi-element DependsOn array whose natural order is not alphabetical.

A CDK L2 lambda.Function reliably triggers this because CDK generates:

DependsOn: [ FnServiceRoleDefaultPolicy, FnServiceRole ]

which is not in alphabetical order. After the bug, the submitted
change set had them reversed, which CloudFormation treated as a
modification and rejected the import.


Root Cause

Two issues working together:

  1. fullDiff() in diff-template.ts called normalize() directly on the
    caller's template objects, sorting DependsOn arrays in-place.
    This means fullDiff() was secretly mutating whatever object you
    passed into it.

  2. ResourceImporter in importer.ts cached the deployed template and
    reused the exact same object for both:

    • passing to fullDiff() during import discovery (which mutated it)
    • building the IMPORT change set template afterward

So by the time the change set was submitted, DependsOn arrays were
reordered and CloudFormation saw that as a modification.

Fix

packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts

Clone both templates before calling normalize() so fullDiff() is
completely side-effect free and never mutates the caller's objects.

packages/@aws-cdk/toolkit-lib/lib/api/resource-import/importer.ts

Deep clone the deployed template when storing it in _currentTemplate
so no downstream mutation can ever corrupt the cached value.

Also clone inside currentTemplateWithAdditions() before appending
new resources so the cache stays a clean mirror of the deployed state.

Testing

  • Added unit test that calls fullDiff() with a template containing
    a multi-element DependsOn in non-alphabetical order and asserts
    the original object is unchanged after the call.

  • Manually verified cdk import succeeds on a stack containing a
    CDK L2 lambda.Function (the original reproduction case).

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

Related

Fixes #1575

@aws-cdk-automation aws-cdk-automation requested a review from a team June 5, 2026 06:37
@saeekailas saeekailas changed the title fix(import): resolve cdk import failure caused by fullDiff mutating DependsOn arrays fix: prevent fullDiff mutation corrupting cdk import change set Jun 5, 2026
Comment on lines +55 to +56
const currentCopy = JSON.parse(JSON.stringify(currentTemplate));
const newCopy = JSON.parse(JSON.stringify(newTemplate));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use structuredClone instead

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use structuredClone
good call much cleaner

private async currentTemplate(): Promise<any> {
if (!this._currentTemplate) {
this._currentTemplate = await this.cfn.readCurrentTemplate(this.stack);
this._currentTemplate = JSON.parse(JSON.stringify(await this.cfn.readCurrentTemplate(this.stack)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required here? Should we freeze the object if we don't expect changes to it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is required. Without it, fullDiff() mutates the cached object downstream, which corrupts the change set built afterward that's the exact root cause of the bug.
I've kept the clone and also added Object.freeze() to make the intent explicit: this cached value should never be modified. Let me know if you'd prefer a different approach.

*/
private async currentTemplateWithAdditions(additions: ImportableResource[]): Promise<any> {
const template = await this.currentTemplate();
const template = JSON.parse(JSON.stringify(await this.currentTemplate()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

structuredClone

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to structuredClone here as well.

auto-merge was automatically disabled June 10, 2026 17:44

Head branch was pushed to by a user without write access

@saeekailas

Copy link
Copy Markdown
Author

Thanks for the review @mrgrain! Updated all three switched to structuredClone in both places, and added Object.freeze() on the cached template to make the no-mutation contract explicit. Also rebased on top of main. Ready for another look when you get a chance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants