@go-to-k/cdkd 0.284.13 → 0.284.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12850,6 +12850,25 @@ function buildUnknownIntrinsicError(key) {
12850
12850
  const issueUrl = `https://github.com/go-to-k/cdkd/issues/new?title=${encodeURIComponent(title)}&labels=intrinsic-support`;
12851
12851
  return /* @__PURE__ */ new Error(`Unsupported CloudFormation intrinsic function "${key}": cdkd does not support resolving it yet. Deploying this template would produce a broken value. Please request support by opening an issue: ${issueUrl}`);
12852
12852
  }
12853
+ /**
12854
+ * Does `value` carry a CloudFormation dynamic reference anywhere inside it?
12855
+ *
12856
+ * Used as the identity fast path of {@link
12857
+ * IntrinsicFunctionResolver.reresolveCrossStackValue}: a cross-stack value that
12858
+ * carries none is returned untouched, so every ordinary import keeps its
12859
+ * pre-#1934 behaviour with no walk, no AWS call and no allocation.
12860
+ *
12861
+ * The walk descends arrays and objects because `state.outputs` is typed
12862
+ * `Record<string, unknown>` and deliberately NOT coerced to string — a
12863
+ * list-valued `Fn::GetAtt` persists a JSON array — so a secret-bearing output
12864
+ * is not always a bare string.
12865
+ */
12866
+ function carriesDynamicReference(value) {
12867
+ if (typeof value === "string") return value.includes("{{resolve:");
12868
+ if (Array.isArray(value)) return value.some(carriesDynamicReference);
12869
+ if (value !== null && typeof value === "object") return Object.values(value).some(carriesDynamicReference);
12870
+ return false;
12871
+ }
12853
12872
  let cachedAccountIdentity = null;
12854
12873
  /**
12855
12874
  * Cache for availability zones per region
@@ -13148,7 +13167,7 @@ function stringifyParameterForLog(paramDef, value) {
13148
13167
  if (paramDef?.NoEcho === true) return "<redacted>";
13149
13168
  return stringifyValue(value);
13150
13169
  }
13151
- var IntrinsicFunctionResolver = class {
13170
+ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
13152
13171
  logger = getLogger().child("IntrinsicFunctionResolver");
13153
13172
  resolverRegion;
13154
13173
  /**
@@ -13208,6 +13227,55 @@ var IntrinsicFunctionResolver = class {
13208
13227
  * another's identity, which is a worse bug than the one this cache serves.
13209
13228
  */
13210
13229
  regionScopedClients = /* @__PURE__ */ new Map();
13230
+ /**
13231
+ * ServiceDiscovery clients keyed by the region {@link clientsForRegion}
13232
+ * selected (`''` when it selected none). See {@link serviceDiscoveryClient}
13233
+ * for why this one service is not read off an `AwsClients` bag, and why the
13234
+ * PROMISE rather than the client is what is stored.
13235
+ */
13236
+ serviceDiscoveryClients = /* @__PURE__ */ new Map();
13237
+ /**
13238
+ * Resolvers pinned to a PRODUCER stack's region, for re-resolving a
13239
+ * cross-stack imported value that was persisted REDACTED (issue
13240
+ * [#1934](https://github.com/go-to-k/cdkd/issues/1934)); see
13241
+ * {@link reresolveCrossStackValue}.
13242
+ *
13243
+ * A whole resolver rather than a client bag, because what has to be
13244
+ * region-scoped is not only the lookup but the VALUE CACHE behind it:
13245
+ * {@link cachedDynamicReferences} is keyed by the expression alone and is
13246
+ * sound only because one resolver stands for one stack in one region (issue
13247
+ * #1933). Resolving a producer's expression inside THIS resolver would put a
13248
+ * foreign region's answer under a key the consumer's own lookups read — the
13249
+ * exact cross-region leak that field's instance scope closed. A separate
13250
+ * resolver per producer region keeps that invariant by construction.
13251
+ *
13252
+ * WHAT THAT DOES *NOT* BUY, stated because an earlier revision of this note
13253
+ * implied it did: a separate instance isolates only the state this class
13254
+ * OWNS. The `{{resolve:...}}` SECRET VERDICT store lives in
13255
+ * `secret-redaction.ts`, is process-global and is keyed by the expression
13256
+ * string alone, so a guest's resolution would still pin a foreign region's
13257
+ * verdict for every later reader. That half is closed at the WRITE instead —
13258
+ * see {@link pinSecretVerdict} and {@link producerRegionGuest}, which also
13259
+ * record why re-keying that store is not available from this lane.
13260
+ *
13261
+ * Bounded like {@link regionScopedClients}: an entry exists only for a
13262
+ * producer region that DIFFERS from this resolver's own, and at most one per
13263
+ * such region. Same lifetime too — the resolver's own, with no teardown.
13264
+ */
13265
+ producerRegionResolvers = /* @__PURE__ */ new Map();
13266
+ /**
13267
+ * True on a resolver built by {@link resolverForProducerRegion} to answer for
13268
+ * ANOTHER stack's region — a read-only guest of this deploy.
13269
+ *
13270
+ * Its one consequence is {@link pinSecretVerdict}: a guest never writes the
13271
+ * PROCESS-GLOBAL secret-verdict store, because a verdict keyed by the
13272
+ * expression string alone would carry a foreign region's answer into the
13273
+ * consumer's own next pass. Deliberately NOT on
13274
+ * {@link IntrinsicFunctionResolverOptions} — no caller outside this class may
13275
+ * declare itself a guest, and the flag is set by the one line that builds
13276
+ * one.
13277
+ */
13278
+ producerRegionGuest = false;
13211
13279
  strictGetAtt;
13212
13280
  cfnFallback;
13213
13281
  /**
@@ -13474,6 +13542,56 @@ var IntrinsicFunctionResolver = class {
13474
13542
  return scoped;
13475
13543
  }
13476
13544
  /**
13545
+ * The ServiceDiscovery client for the `HostedZoneId` namespace lookup, in the
13546
+ * region {@link clientsForRegion} selects (issue
13547
+ * [#1994](https://github.com/go-to-k/cdkd/issues/1994)).
13548
+ *
13549
+ * It is BUILT here rather than read off the bag for one reason: `AwsClients`
13550
+ * carries no `serviceDiscovery` member, and adding one would put a static
13551
+ * `@aws-sdk/client-servicediscovery` import into a module every command
13552
+ * loads. So the REGION DECISION is still `clientsForRegion`'s — including its
13553
+ * ambient-reuse rule and its refusal of a region that is not client-safe —
13554
+ * and only the construction is local: the chosen bag's
13555
+ * {@link AwsClients.credentialConfig} carries `--profile` / explicit
13556
+ * credentials across, and its {@link AwsClients.configuredRegion} is the
13557
+ * region to pin. An UNCONFIGURED bag (no region was ever named, i.e. the
13558
+ * no-argument constructor) pins nothing and lets the SDK's own chain
13559
+ * resolve — the same arm-1 answer `clientsForRegion` gives, and strictly
13560
+ * better than the `resolverRegion` this site used to read, which substitutes
13561
+ * `AWS_REGION` and then a hard-coded `us-east-1`.
13562
+ *
13563
+ * The PROMISE is memoized, not the client: the dynamic import makes this
13564
+ * async, so two callers arriving from different await depths can both be
13565
+ * inside the seam and would each construct (and leak) their own client. That
13566
+ * is defensive rather than measured — the unit case dispatching ten lookups
13567
+ * together produces ONE client either way, because they serialize on
13568
+ * `getAccountInfo`'s in-flight promise and reach here one at a time, which
13569
+ * the case says out loud. A REJECTED import is evicted
13570
+ * so a transient failure does not poison the rest of the deploy, mirroring
13571
+ * `cfnExportsPromise`. Lifetime is the resolver's own, like
13572
+ * {@link regionScopedClients} and {@link cfnClients}: at most one per region
13573
+ * per resolver, versus the one-per-CALL this replaces.
13574
+ */
13575
+ async serviceDiscoveryClient() {
13576
+ const scoped = this.clientsForRegion(this.explicitRegion);
13577
+ const region = scoped.configuredRegion;
13578
+ const key = region ?? "";
13579
+ const cached = this.serviceDiscoveryClients.get(key);
13580
+ if (cached) return cached;
13581
+ const building = (async () => {
13582
+ const { ServiceDiscoveryClient } = await import("@aws-sdk/client-servicediscovery");
13583
+ return new ServiceDiscoveryClient({
13584
+ ...scoped.credentialConfig ?? {},
13585
+ ...region ? { region } : {}
13586
+ });
13587
+ })();
13588
+ this.serviceDiscoveryClients.set(key, building);
13589
+ building.catch(() => {
13590
+ if (this.serviceDiscoveryClients.get(key) === building) this.serviceDiscoveryClients.delete(key);
13591
+ });
13592
+ return building;
13593
+ }
13594
+ /**
13477
13595
  * Resolve parameter values from template Parameters section
13478
13596
  *
13479
13597
  * Merges default values from template with user-provided parameter values.
@@ -13884,8 +14002,7 @@ var IntrinsicFunctionResolver = class {
13884
14002
  case "VpcId": return physicalId;
13885
14003
  case "CidrBlock": return resource.attributes?.["CidrBlock"] || resource.properties?.["CidrBlock"];
13886
14004
  case "Ipv6CidrBlocks": try {
13887
- const { EC2Client, DescribeVpcsCommand } = await import("@aws-sdk/client-ec2");
13888
- const ec2 = new EC2Client({ region: this.resolverRegion });
14005
+ const ec2 = this.clientsForRegion(this.explicitRegion).ec2;
13889
14006
  const maxAttempts = 15;
13890
14007
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
13891
14008
  const associations = (await ec2.send(new DescribeVpcsCommand({ VpcIds: [physicalId] }))).Vpcs?.[0]?.Ipv6CidrBlockAssociationSet || [];
@@ -13984,8 +14101,8 @@ var IntrinsicFunctionResolver = class {
13984
14101
  case "Arn": return `arn:${partition}:servicediscovery:${region}:${accountId}:namespace/${physicalId}`;
13985
14102
  case "Id": return physicalId;
13986
14103
  case "HostedZoneId": try {
13987
- const { ServiceDiscoveryClient, GetNamespaceCommand } = await import("@aws-sdk/client-servicediscovery");
13988
- return (await new ServiceDiscoveryClient({ region: this.resolverRegion }).send(new GetNamespaceCommand({ Id: physicalId }))).Namespace?.Properties?.DnsProperties?.HostedZoneId;
14104
+ const { GetNamespaceCommand } = await import("@aws-sdk/client-servicediscovery");
14105
+ return (await (await this.serviceDiscoveryClient()).send(new GetNamespaceCommand({ Id: physicalId }))).Namespace?.Properties?.DnsProperties?.HostedZoneId;
13989
14106
  } catch (error) {
13990
14107
  this.logger.warn(`Failed to fetch HostedZoneId for namespace ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
13991
14108
  return;
@@ -14542,6 +14659,174 @@ var IntrinsicFunctionResolver = class {
14542
14659
  return result;
14543
14660
  }
14544
14661
  /**
14662
+ * Re-resolve the dynamic references in a value read out of a PRODUCER
14663
+ * stack's persisted outputs, before it is handed to the consumer (issue
14664
+ * [#1934](https://github.com/go-to-k/cdkd/issues/1934)).
14665
+ *
14666
+ * Since PR #1899 a secret-bearing output is stored REDACTED: `state.outputs`
14667
+ * and the exports index hold `{{resolve:secretsmanager:X:SecretString:pw}}`
14668
+ * rather than the resolved value. A consumer resolving `Fn::ImportValue` /
14669
+ * `Fn::GetStackOutput` got that string back VERBATIM and shipped the literal
14670
+ * token to AWS as the property value — the GHSA-p5qg-v9gv-hc7w class one
14671
+ * more time, and the same shape `resolveReplayProps` fixed for the rollback
14672
+ * replay and #1914 fixed for `cdkd drift --revert`. Every place that reads a
14673
+ * REDACTED bag and hands it onward has to re-resolve first; the cross-stack
14674
+ * import edge was the one that was missed, because the redaction and the
14675
+ * consumption live in different stacks and often different runs.
14676
+ *
14677
+ * `{{resolve:` re-resolution in {@link resolveValue} could not cover it: that
14678
+ * arm fires for a leaf INPUT string, and an intrinsic's RETURN value is never
14679
+ * fed back through it.
14680
+ *
14681
+ * WHICH REGION RESOLVES IT — the producer's, via {@link
14682
+ * resolverForProducerRegion}. The expression was resolved BY the producer IN
14683
+ * the producer's region, so that is the only region whose answer reproduces
14684
+ * the value the producer exported; a Secrets Manager secret or an SSM
14685
+ * parameter of the same NAME in two regions is two independent values (issue
14686
+ * #1933). The index hit carries `entry.producerRegion` (a required field) and
14687
+ * `Fn::GetStackOutput` carries the reference's own resolved `Region`, so both
14688
+ * of those are recorded rather than inferred.
14689
+ *
14690
+ * THE SCAN ARM IS THE EXCEPTION, and it is one this method inherits rather
14691
+ * than introduces: a pre-v2 state record has no `region` field, so the scan
14692
+ * reads it as `refRegion ?? this.resolverRegion` — the consumer's own region,
14693
+ * itself defaulted through `AWS_REGION` and then `us-east-1`. Where that
14694
+ * guess is wrong the re-resolution asks the wrong region and either misses
14695
+ * the secret (a hard failure naming the lookup, since issue #1934's
14696
+ * restructure surfaces it rather than degrading to `export not found`) or
14697
+ * resolves a same-named secret in the consumer's region. What bounds it is
14698
+ * that the guess is the SAME one the state READ just used, so this method
14699
+ * cannot disagree with the record it was handed — a region-less record was
14700
+ * already being read from that region or not found at all. The honest fix is
14701
+ * a region on the record, which is what schema v2 did for every record
14702
+ * written since.
14703
+ *
14704
+ * WHICH CREDENTIALS — the consumer's, which are the producer's too for every
14705
+ * path that reaches here. `Fn::ImportValue` reads the account-scoped state
14706
+ * bucket / exports index, so a producer it can see is in this account by
14707
+ * construction, and the cross-ACCOUNT `Fn::GetStackOutput` path deliberately
14708
+ * never calls this (see the refusal at its call site) rather than resolving a
14709
+ * producer's expression under the consumer's identity — which would answer
14710
+ * from a same-named secret in the WRONG account, the #1957 disclosure shape.
14711
+ *
14712
+ * The CONSUMER's `context` is passed through, which is what keeps the
14713
+ * consumer's own state redacted: each resolved plaintext is recorded into its
14714
+ * `recordedSecretValues`, so the deploy engine's save choke point rewrites it
14715
+ * back to the expression on the way into `state.json`. It also means
14716
+ * `skipDynamicReferences` is honoured, so the diff / no-op path keeps
14717
+ * comparing expression-vs-expression instead of fetching a secret to print.
14718
+ *
14719
+ * Identity-returns a value carrying no `{{resolve:` at all, so an ordinary
14720
+ * import is untouched.
14721
+ *
14722
+ * THE WALK BELOW IS A THIRD COPY, and that is recorded rather than fixed.
14723
+ * `rollback-executor.ts`'s `resolveReplayProps` carries the same descent, and
14724
+ * `drift.ts`'s `resolveStateSecretExpressions` the same idea. Extracting one
14725
+ * helper is the right end state and is NOT this change's to make: the natural
14726
+ * home is beside those callers, in files a parallel lane owns, and a
14727
+ * cross-module extraction done from here would edit them. Two things a future
14728
+ * extractor needs that a mechanical merge would drop: this copy rebuilds
14729
+ * objects with `Object.create(null)` (the `__proto__` hazard below), and it
14730
+ * takes the RESOLVER as a parameter because the region it must answer for is
14731
+ * the producer's rather than the caller's — which is exactly what issue #2057
14732
+ * says the other two copies get wrong.
14733
+ */
14734
+ async reresolveCrossStackValue(value, producerRegion, context, origin) {
14735
+ if (!carriesDynamicReference(value)) return value;
14736
+ const resolver = this.resolverForProducerRegion(producerRegion);
14737
+ const walk = async (v) => {
14738
+ if (typeof v === "string") return v.includes("{{resolve:") ? await resolver.resolveDynamicReferences(v, context) : v;
14739
+ if (Array.isArray(v)) {
14740
+ const out = new Array(v.length);
14741
+ for (let i = 0; i < v.length; i++) out[i] = await walk(v[i]);
14742
+ return out;
14743
+ }
14744
+ if (v !== null && typeof v === "object") {
14745
+ const out = Object.create(null);
14746
+ for (const [k, val] of Object.entries(v)) out[k] = await walk(val);
14747
+ return out;
14748
+ }
14749
+ return v;
14750
+ };
14751
+ this.logger.debug(`Re-resolving dynamic reference(s) in ${origin}`);
14752
+ return await walk(value);
14753
+ }
14754
+ /**
14755
+ * The resolver that must answer for a PRODUCER region — `this` when the
14756
+ * producer shares this resolver's own region, otherwise the pinned sibling
14757
+ * from {@link producerRegionResolvers}.
14758
+ *
14759
+ * The comparison is against {@link explicitRegion}, NOT {@link
14760
+ * resolverRegion}: the latter substitutes `AWS_REGION` and then a hard-coded
14761
+ * `us-east-1`, so comparing against it would answer "same region" on the
14762
+ * strength of a guess and resolve the producer's expression against whatever
14763
+ * the ambient clients happen to point at. When no region was named, a producer
14764
+ * region that IS named still binds — "unknown means SCOPE, not skip", the same
14765
+ * rule {@link clientsForRegion} applies, and the same one `Fn::GetAZs` already
14766
+ * follows for its template-named region.
14767
+ */
14768
+ /**
14769
+ * Pin (or retract) a `{{resolve:...}}` secret verdict in the PROCESS-GLOBAL
14770
+ * store — unless this resolver is a producer-region GUEST, which writes
14771
+ * nothing there.
14772
+ *
14773
+ * The guest suppression is the correction the review of issue #1934 forced,
14774
+ * and the isolation note on {@link producerRegionResolvers} used to overstate
14775
+ * what a per-region resolver bought. The value cache is per-instance, but the
14776
+ * VERDICT store is not this class's — it lives in `secret-redaction.ts` and is
14777
+ * keyed by the expression STRING alone, so a producer-region resolution would
14778
+ * pin a FOREIGN region's answer for the whole process. The consequence is
14779
+ * concrete, and it lands on the consumer's very next pass: `isKnownSecret`
14780
+ * consults that store, and on the `skipDynamicReferences` (diff / no-op) path
14781
+ * a `true` verdict SKIPS the lookup and leaves the expression unresolved — so
14782
+ * a consumer-region parameter that is a plain `String`, and which state
14783
+ * therefore holds RESOLVED, would be compared as an expression and report a
14784
+ * spurious change on every run. That is issue #1901's perpetual-UPDATE class,
14785
+ * arriving through a region boundary the store cannot see.
14786
+ *
14787
+ * KEYING THE STORE BY REGION IS THE BETTER FIX AND IS NOT AVAILABLE FROM
14788
+ * HERE. `secret-redaction.ts` reads its own store internally with the BARE
14789
+ * expression (`isKnownSecretExpression`, and the mixed-leaf public-reference
14790
+ * test), so a region-qualified key would silently stop matching for the
14791
+ * redaction path — losing the #1910 losing-member arm and changing the #1926
14792
+ * empty-map verdict — and that file is owned by another lane in this run.
14793
+ * Suppressing the WRITE is the half that is correct on its own: it removes
14794
+ * the new cross-region reachability without changing the key, and the
14795
+ * consumer's own resolver keeps pinning its own region's verdicts exactly as
14796
+ * before.
14797
+ *
14798
+ * READS are deliberately NOT suppressed. A guest reading the consumer's
14799
+ * verdict can only seed `isKnownSecret`, which for `ssm` is OVERWRITTEN by
14800
+ * the fresh `GetParameter` response, and on the skip path it produces the
14801
+ * unresolved expression the diff wants anyway. Only the write direction
14802
+ * carried the defect.
14803
+ *
14804
+ * The cost of suppressing is one `GetParameter` per foreign expression per
14805
+ * later pass, since the guest's OWN instance cache still carries the verdict
14806
+ * alongside the value (issue #1933's design) and answers every repeat within
14807
+ * the deploy.
14808
+ */
14809
+ pinSecretVerdict(expression, secret) {
14810
+ if (this.producerRegionGuest) return;
14811
+ if (secret) recordedSecretExpressions.add(expression);
14812
+ else recordedSecretExpressions.delete(expression);
14813
+ }
14814
+ resolverForProducerRegion(producerRegion) {
14815
+ if (!producerRegion) return this;
14816
+ const target = canonicalizeRegion(producerRegion);
14817
+ if (target === canonicalizeRegion(this.explicitRegion)) return this;
14818
+ const cached = this.producerRegionResolvers.get(target);
14819
+ if (cached) return cached;
14820
+ const scoped = new IntrinsicFunctionResolver(target, {
14821
+ strictGetAtt: this.strictGetAtt,
14822
+ cfnFallback: this.cfnFallback
14823
+ });
14824
+ scoped.producerRegionGuest = true;
14825
+ this.producerRegionResolvers.set(target, scoped);
14826
+ this.logger.debug(`Using a producer-region resolver for ${target}`);
14827
+ return scoped;
14828
+ }
14829
+ /**
14545
14830
  * Resolve Fn::ImportValue (cross-stack references)
14546
14831
  *
14547
14832
  * Searches all other stacks for an exported output with the given name.
@@ -14551,18 +14836,23 @@ var IntrinsicFunctionResolver = class {
14551
14836
  if (typeof exportName !== "string") throw new Error(`Fn::ImportValue: export name must resolve to a string, got ${typeof exportName}`);
14552
14837
  if (!context.stateBackend) throw new Error("Fn::ImportValue: state backend is required for cross-stack references");
14553
14838
  this.logger.debug(`Resolving Fn::ImportValue: ${exportName}`);
14554
- if (context.exportIndex) try {
14555
- const entry = await context.exportIndex.lookup(exportName);
14839
+ if (context.exportIndex) {
14840
+ let entry;
14841
+ try {
14842
+ entry = await context.exportIndex.lookup(exportName);
14843
+ } catch (err) {
14844
+ this.logger.warn(`Exports index lookup failed for '${exportName}': ${err instanceof Error ? err.message : String(err)}; falling back to state.json scan`);
14845
+ entry = void 0;
14846
+ }
14556
14847
  if (entry && (!context.stackName || entry.producerStack !== context.stackName)) {
14557
14848
  this.recordImport(context, exportName, entry.producerStack, entry.producerRegion);
14558
14849
  this.logger.info(`Resolved Fn::ImportValue: ${exportName} = ${JSON.stringify(entry.value)} (from index: ${entry.producerStack} / ${entry.producerRegion})`);
14559
- return entry.value;
14850
+ return await this.reresolveCrossStackValue(entry.value, entry.producerRegion, context, `Fn::ImportValue '${exportName}' (producer ${entry.producerStack} / ${entry.producerRegion})`);
14560
14851
  }
14561
- } catch (err) {
14562
- this.logger.warn(`Exports index lookup failed for '${exportName}': ${err instanceof Error ? err.message : String(err)}; falling back to state.json scan`);
14563
14852
  }
14564
14853
  const allStacks = await context.stateBackend.listStacks();
14565
14854
  this.logger.debug(`Found ${allStacks.length} state record(s) to search for export: ${exportName}`);
14855
+ let found;
14566
14856
  for (const ref of allStacks) {
14567
14857
  const { stackName: refStack, region: refRegion } = ref;
14568
14858
  if (context.stackName && refStack === context.stackName) {
@@ -14592,13 +14882,19 @@ var IntrinsicFunctionResolver = class {
14592
14882
  this.logger.debug(`Failed to patch exports index for '${exportName}': ${err instanceof Error ? err.message : String(err)}`);
14593
14883
  });
14594
14884
  this.recordImport(context, exportName, refStack, lookupRegion);
14595
- return value;
14885
+ found = {
14886
+ value,
14887
+ refStack,
14888
+ lookupRegion
14889
+ };
14890
+ break;
14596
14891
  }
14597
14892
  } catch (error) {
14598
14893
  this.logger.warn(`Failed to read state for stack ${refStack}: ${error instanceof Error ? error.message : String(error)}`);
14599
14894
  continue;
14600
14895
  }
14601
14896
  }
14897
+ if (found) return await this.reresolveCrossStackValue(found.value, found.lookupRegion, context, `Fn::ImportValue '${exportName}' (producer ${found.refStack} / ${found.lookupRegion})`);
14602
14898
  if (this.cfnFallback) {
14603
14899
  const cfnExport = await this.lookupCfnExport(exportName);
14604
14900
  if (cfnExport) {
@@ -14817,7 +15113,8 @@ var IntrinsicFunctionResolver = class {
14817
15113
  const value = outputs[outputName];
14818
15114
  this.logger.info(`Resolved Fn::GetStackOutput: StackName=${stackName}, Region=${region}, OutputName=${outputName}${roleArn ? `, RoleArn=${roleArn}` : ""} -> ${JSON.stringify(value)}`);
14819
15115
  if (!roleArn) this.recordOutputRead(context, stackName, region, outputName);
14820
- return value;
15116
+ if (roleArn && !context.skipDynamicReferences && carriesDynamicReference(value)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::GetStackOutput: output '${outputName}' of stack '${stackName}' (${region}) is a redacted dynamic reference, and this is a CROSS-ACCOUNT reference (RoleArn ${roleArn}). cdkd will not resolve a producer account's secret with the consumer's credentials — a same-named secret in the consumer account would answer instead. Export a non-secret value (e.g. the secret's ARN) and resolve it in the consumer stack, or reference the producer stack from within its own account.`));
15117
+ return await this.reresolveCrossStackValue(value, region, context, `Fn::GetStackOutput '${outputName}' (producer ${stackName} / ${region})`);
14821
15118
  }
14822
15119
  /**
14823
15120
  * Push a resolved `Fn::GetStackOutput` into the consumer's
@@ -15091,8 +15388,8 @@ var IntrinsicFunctionResolver = class {
15091
15388
  else if (service === "ssm") {
15092
15389
  const decrypt = context?.skipDynamicReferences !== true;
15093
15390
  const param = await this.resolveSSMReference(parts, decrypt);
15094
- if (param.type === "SecureString") recordedSecretExpressions.add(fullMatch);
15095
- else if (!param.secure) recordedSecretExpressions.delete(fullMatch);
15391
+ if (param.type === "SecureString") this.pinSecretVerdict(fullMatch, true);
15392
+ else if (!param.secure) this.pinSecretVerdict(fullMatch, false);
15096
15393
  else cacheable = false;
15097
15394
  isSecret = param.secure;
15098
15395
  if (param.secure) {
@@ -15109,7 +15406,7 @@ var IntrinsicFunctionResolver = class {
15109
15406
  });
15110
15407
  if (isSecret && resolved) {
15111
15408
  context?.recordedSecretValues?.set(resolved, fullMatch);
15112
- if (service === "secretsmanager") recordedSecretExpressions.add(fullMatch);
15409
+ if (service === "secretsmanager") this.pinSecretVerdict(fullMatch, true);
15113
15410
  }
15114
15411
  result = result.replace(fullMatch, () => resolved);
15115
15412
  }
@@ -16206,7 +16503,7 @@ var CloudControlProvider = class {
16206
16503
  if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
16207
16504
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
16208
16505
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
16209
- const { ASGProvider } = await import("./asg-provider-H-Dl8qng.js").then((n) => n.n);
16506
+ const { ASGProvider } = await import("./asg-provider-BZwQzb8e.js").then((n) => n.n);
16210
16507
  return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
16211
16508
  }
16212
16509
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
@@ -18884,7 +19181,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
18884
19181
  silentDrop: /* @__PURE__ */ new Map([
18885
19182
  ["IssuerConfiguration", "not yet implemented by cdkd"],
18886
19183
  ["KeyConfiguration", "not yet implemented by cdkd"],
18887
- ["WebAuthnFactorConfiguration", "No SDK wire path: @aws-sdk/client-cognito-identity-provider has no field accepting SINGLE_FACTOR | MULTI_FACTOR_WITH_USER_VERIFICATION (not on CreateUserPool/UpdateUserPool, nor SetUserPoolMfaConfig.WebAuthnConfiguration which only carries RelyingPartyId/UserVerification); CC-API-registry-only property"]
19184
+ ["WebAuthnFactorConfiguration", "No wire path in the PINNED SDK (@aws-sdk/client-cognito-identity-provider 3.1018.0): WebAuthnConfigurationType is {RelyingPartyId?, UserVerification?} and no CreateUserPool/UpdateUserPool field accepts SINGLE_FACTOR | MULTI_FACTOR_WITH_USER_VERIFICATION. This is an SDK-VERSION limit, NOT an API one -- the live API does honour FactorConfiguration (measured us-east-1 2026-08-20: SetUserPoolMfaConfig(ON) on a pool allowing WEB_AUTHN as a first auth factor is rejected with \"Cannot set WebAuthn factor configuration to SINGLE_FACTOR if MFA is required and WebAuthn is an allowed first auth factor\", i.e. the service reads a field the SDK cannot send). RE-EVALUATE THIS ENTRY ON AN SDK BUMP: if WebAuthnConfigurationType gains FactorConfiguration, the property becomes handleable and this entry must go"]
18888
19185
  ])
18889
19186
  }],
18890
19187
  ["AWS::DLM::LifecyclePolicy", {
@@ -24304,7 +24601,7 @@ const FLUSH_INTERVAL_MS = 2e3;
24304
24601
  const FLUSH_EVENT_THRESHOLD = 50;
24305
24602
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
24306
24603
  function getCdkdVersion() {
24307
- return "0.284.13";
24604
+ return "0.284.15";
24308
24605
  }
24309
24606
  /**
24310
24607
  * Generate a time-sortable unique run id, e.g.
@@ -26844,4 +27141,4 @@ var DeployEngine = class {
26844
27141
 
26845
27142
  //#endregion
26846
27143
  export { isTerminationProtectionPropagationError as $, ResourceUpdateNotSupportedError as $n, validateAssetBucketName as $t, renderStatefulReason as A, derivePartitionAndUrlSuffix as An, INTRINSIC_KEYS as At, exportAliasCollisionScrubWarning as B, CdkdError as Bn, stringifyValue as Bt, isFinalSnapshotError as C, CFN_TEMPLATE_URL_LIMIT as Cn, s3BucketArn as Ct, extractDeploymentEventError as D, expectedOwnerParam as Dn, s3BucketWebsiteUrl as Dt, makeCanonicalizePropertiesFn as E, uploadCfnTemplate as En, s3BucketRegionalDomainName as Et, green as F, AwsClients as Fn, LockManager as Ft, collectInlinePolicyNamesManagedBySiblings as G, LocalMigrateError as Gn, rewriteTemplateAssetReferences as Gt, secretBearingStateKeyWarning as H, DependencyError as Hn, buildAssetRedirectMap as Ht, red as I, getAwsClients as In, S3StateBackend as It, findActionableSilentDrops as J, MissingCdkCliError as Jn, AssetModeResolver as Jt, clearOnUpdateRemoval as K, LocalStartServiceError as Kn, escapeRegExp$1 as Kt, yellow as L, resetAwsClients as Ln, rebuildClientForBucketRegion as Lt, bold as M, processStackMessages as Mn, withRetry as Mt, cyan as N, clearBucketRegionCache as Nn, DagBuilder as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, PARTITION_TABLE as On, applyRoleArnIfSet as Ot, gray as P, resolveBucketRegion as Pn, TemplateParser as Pt, disableInstanceApiTermination as Q, ResourceTimeoutError as Qn, parseBootstrapMarker as Qt, collectDeclaredOutputNames as R, setAwsClients as Rn, shouldRetainResource as Rt, createPreDeleteFinalSnapshot as S, CFN_TEMPLATE_BODY_LIMIT as Sn, scrubResourceRecord as St, unsupportedFinalSnapshotError as T, findLargeInlineResources as Tn, s3BucketDualStackDomainName as Tt, stateKeySecretExposure as U, DeployCancelledError as Un, createAssetRedirectResolver as Ut, isExportAliasCollision as V, ConfigError as Vn, WorkGraph as Vt, IAMRoleProvider as W, LocalInvokeBuildError as Wn, loadPublishableAssetManifest as Wt, CloudControlProvider as X, PartialFailureError as Xn, ensureAssetStorage as Xt, findSilentDropProperties as Y, NestedStackChildDirectDestroyError as Yn, BOOTSTRAP_MARKER_PREFIX as Yt, slowCcOperationTimeoutMs as Z, ProvisioningError as Zn, getBootstrapMarkerKey as Zt, computeImplicitDeleteEdges as _, resolveStateBucketWithDefault as _n, STATE_SOURCED_READBACK_RULES as _t, DeploymentEventsStore as a, runDockerForeground as an, isCdkdError as ar, normalizeAwsTagsToCfn as at, buildFinalSnapshotIdentifier as b, stateBucketExistenceConfirmed as bn, maskSecretsInText as bt, replayFailedOperations as c, getDockerImageBySourceHash as cn, isMarkedNonRetryable as cr, coerceCfnBoolean as ct, updatePartialReason as d, getDefaultStateBucketName as dn, markNonRetryable as dr, readConfigString as dt, validateContainerRepoName as en, StackHasActiveImportsError as er, IntrinsicFunctionResolver as et, UNSPECIFIED_SKIP_REASON as f, getLegacyStateBucketName as fn, __exportAll as fr, replayWarn as ft, IMPLICIT_DELETE_DEPENDENCIES as g, resolveSkipPrefix as gn, STATE_SOURCED_CROSS_GENERATION_RULES as gt, maskingRetryLogger as h, resolveCaptureObservedState as hn, requireConfigString as ht, DeploymentEventsReader as i, getDockerCmd as in, formatError as ir, WAFv2WebACLProvider as it, formatResourceLine as j, AssemblyReader as jn, describeTypeWithThrottleRetry as jt, isStatefulRecreateTargetSync as k, canonicalizeRegion as kn, DiffCalculator as kt, replayRollback as l, Synthesizer as ln, isRetryableTransientError as lr, configBooleanRefusal as lt, withResourceDeadline as m, resolveAutoAssetStorage as mn, requireConfigObject as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, buildDockerImage as nn, StateError as nr, getAccountInfo as nt, planFailedOps as o, runDockerStreaming as on, normalizeAwsError as or, resolveExplicitPhysicalId as ot, deleteSkipReason as p, resolveApp as pn, requireConfigArray as pt, ProviderRegistry as q, LockError as qn, stripControlChars as qt, DeployEngine as r, formatDockerLoginError as rn, SynthesisError as rr, refStateLookupFromResource as rt, planRollback as s, AssetManifestLoader as sn, withErrorHandling as sr, assertRegionMatch as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, buildDenyExternalAccessPolicy as tn, StackTerminationProtectionError as tr, cfnRefValueFromPhysicalId as tt, updatePartialMessage as u, synthesisStatusMessage as un, isThrottlingError as ur, configStringRefusal as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, resolveStateBucketWithDefaultAndSource as vn, TEMPLATE_SOURCED_RULES as vt, refusesFinalSnapshot as w, MIGRATE_TMP_PREFIX as wn, s3BucketDomainName as wt, ccRoutedFinalSnapshotError as x, warnDeprecatedNoPrefixCliFlag as xn, redactSecretsForState as xt, PRE_DELETE_SNAPSHOT_TYPES as y, resolveUseCdkBootstrapAssets as yn, createSecretMasker as yt, collectPublishedOutputNames as z, AssetError as zn, AssetPublisher as zt };
26847
- //# sourceMappingURL=deploy-engine-4Eh5Qqlm.js.map
27144
+ //# sourceMappingURL=deploy-engine-BNujuzW5.js.map