Skip to content

fix(toolkit-lib): allowCrossAccountAssetPublishing cache ignores which stack's environment it was computed for - #1884

Open
Adityaj0 wants to merge 1 commit into
aws:mainfrom
Adityaj0:fix/cross-account-asset-publishing-cache-env-key
Open

fix(toolkit-lib): allowCrossAccountAssetPublishing cache ignores which stack's environment it was computed for#1884
Adityaj0 wants to merge 1 commit into
aws:mainfrom
Adityaj0:fix/cross-account-asset-publishing-cache-env-key

Conversation

@Adityaj0

Copy link
Copy Markdown
Contributor

fixes #1883

Reason for this change

Deployments.allowCrossAccountAssetPublishingForEnv() (packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts) caches its result in a single un-keyed instance field:

private _allowCrossAccountAssetPublishing: boolean | undefined;

private async allowCrossAccountAssetPublishingForEnv(stack: cxapi.CloudFormationStackArtifact): Promise<boolean> {
  if (this._allowCrossAccountAssetPublishing === undefined) {
    const env = await this.envs.accessStackForReadOnlyStackOperations(stack);
    this._allowCrossAccountAssetPublishing = await determineAllowCrossAccountAssetPublishing(env.sdk, this.ioHelper, this.props.toolkitStackName);
  }
  return this._allowCrossAccountAssetPublishing; // stack's own environment ignored on a cache hit
}

determineAllowCrossAccountAssetPublishing reads the target environment's bootstrap stack (CDKToolkit's BootstrapVersion/BucketName) and its answer is intrinsically per-account/per-region — it returns false only when that specific environment has a staging bucket and an old (< 21) bootstrap version.

Reachability, stated honestly: this is directly reachable via ordinary CLI usage, no programmatic toolkit-lib reuse needed. deploy() (toolkit.ts) creates one Deployments instance per deploymentsForAction('deploy') call and reuses it for every stack in stackCollection.stackArtifacts — so a plain cdk deploy StackA StackB (or cdk deploy '*') where StackA and StackB target different accounts/regions with different bootstrap states hits this on the very first multi-stack, multi-environment deploy. publishSingleAsset calls allowCrossAccountAssetPublishingForEnv(options.stack) once per asset (line 743), so whichever stack's asset publishes first determines the answer applied to every other stack for the rest of the invocation.

Two concrete outcomes depending on stack order:

  • StackA (old bootstrap, correctly false) publishes first → StackB (properly bootstrapped, should be true) gets a spurious cross-account-publishing failure on an otherwise valid deploy.
  • StackA (properly bootstrapped, true) publishes first → StackB (old bootstrap, should be false) silently skips the safety check determineAllowCrossAccountAssetPublishing exists to enforce, and an asset can be published to the wrong account without the protection ever running against StackB's actual environment.

This is the same class of bug as #1838, fixed for the neighboring publisherCache field in #1839 — that fix's own comment explains exactly why environment-keying is required here too; _allowCrossAccountAssetPublishing sits a few lines below it and was simply missed.

Description of changes

Replaces the single boolean | undefined field with a Map<string, boolean> keyed by ${account}:${region} (same key shape cachedPublisher already uses), resolved once per call via this.envs.resolveStackEnvironment(stack) before checking the cache.

Description of how you validated changes

Added two tests against allowCrossAccountAssetPublishingForEnv in cloudformation-deployments.test.ts, mirroring the existing cachedPublisher regression tests:

  • 'does not reuse the answer across different environments' — two stacks in different accounts/regions, determineAllowCrossAccountAssetPublishing mocked to return a different value per call; asserts each stack gets its own environment's answer and the underlying check runs twice. Fails on the pre-fix code (confirmed locally): the second stack's call returns the first stack's cached answer instead of its own.
  • 'reuses the cached answer for repeat calls with the same environment' — confirms the legitimate caching behavior (same environment) is preserved and the underlying check only runs once.

Ran the full test/api/deployments/ suite (210 tests) — all pass, no regressions. eslint --fix clean.

Checklist

  • Unit tests added/updated
  • Integration tests added/updated (not applicable — internal caching bug in a helper class; a live multi-account integration test isn't part of the existing CLI integ suite's environment model, and the unit tests directly exercise the caching behavior that was broken)
  • No manual edits to generated files

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

…h stack's environment it was computed for

Deployments.allowCrossAccountAssetPublishingForEnv() cached its answer
in a single un-keyed instance field. determineAllowCrossAccountAssetPublishing()
reads the target environment's bootstrap stack, so its answer is
intrinsically per-account/per-region — but a single Deployments
instance is reused for every stack in one `cdk deploy` invocation,
which can span multiple accounts/regions.

The first stack's asset publish would cache its environment's answer
and every other stack's asset publish would silently reuse it,
regardless of that stack's own environment. Depending on stack
ordering this either blocks a perfectly valid deploy with a spurious
cross-account error, or — worse — skips the safety check entirely for
a stack whose own environment should have blocked cross-account
publishing.

This is the same class of bug as aws#1838, fixed for the neighboring
publisherCache in aws#1839: key the cache by resolved environment
(account:region), same as cachedPublisher already does.

Fixes aws#1883

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.13%. Comparing base (7ff50e7) to head (f5ad05b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1884      +/-   ##
==========================================
+ Coverage   91.10%   91.13%   +0.03%     
==========================================
  Files          80       80              
  Lines       12205    12205              
  Branches     1742     1744       +2     
==========================================
+ Hits        11119    11123       +4     
+ Misses       1050     1046       -4     
  Partials       36       36              
Flag Coverage Δ
suite.unit 91.13% <ø> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

toolkit-lib: allowCrossAccountAssetPublishing cache ignores which stack's environment it was computed for

2 participants