@go-to-k/cdkd 0.285.13 → 0.285.14

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.
@@ -1,6 +1,6 @@
1
1
  import { _ as withStackName, d as applyDefaultNameForFallback, f as generateResourceName, h as looksLikeCdkdGeneratedName, m as getCurrentStackName, n as getLogger, p as generateResourceNameWithFallback, r as isStdoutReservedForPayload, s as getLiveRenderer } from "./logger-Dw-3y48G.js";
2
2
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
3
- import { t as getCdkdVersion } from "./version-FLW4rE5A.js";
3
+ import { t as getCdkdVersion } from "./version-DP5kGlzj.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -9608,6 +9608,99 @@ var LockManager = class {
9608
9608
  /** Fixed marker substituted for a secret value in log / error output. */
9609
9609
  const SECRET_MASK = "***";
9610
9610
  /**
9611
+ * The UNCOLLAPSED companion of a {@link RecordedSecretValues} map: for each map
9612
+ * instance, every `expression -> plaintext` pair the resolver recorded INTO IT,
9613
+ * keyed by EXPRESSION (issue [#2485](https://github.com/go-to-k/cdkd/issues/2485)).
9614
+ *
9615
+ * WHY IT EXISTS. The map is keyed by PLAINTEXT, so two expressions resolving to
9616
+ * one value keep ONE entry — whichever the resolver recorded last. A WHOLE-token
9617
+ * leaf is immune (the position pass copies its own source), but a leaf that
9618
+ * EMBEDS a token in a literal string is redacted by the value scan, which can
9619
+ * only write the map's surviving expression: the versioned sibling's, for a
9620
+ * template that spells the un-versioned one, and the next deploy diffs that
9621
+ * leaf forever. Recovering the losing expression needs evidence the map has
9622
+ * discarded, and it has to be PASS-LOCAL: `recordedSecretExpressions` is
9623
+ * process-wide and says only that an expression IS secret, never what it
9624
+ * resolved to in THIS resource — so it cannot tell "the source token lost the
9625
+ * map slot to its sibling" from "the source token was never resolved here"
9626
+ * (a previous generation's bag, where writing today's expression over the
9627
+ * framed value would record something that was never deployed).
9628
+ *
9629
+ * Keyed by the map INSTANCE, so the evidence is exactly as pass-local as the
9630
+ * map itself: a map the resolver populated (the deploy's `perResourceSecrets`
9631
+ * entry, and equally the map drift / scrub / import hand their own resolution)
9632
+ * carries the pairs of THAT resolution, while a map the resolver did not
9633
+ * populate — a derived needle map, a nested-stack inheritance copy, a
9634
+ * `new Map(secrets)` copy — starts with no entries here and takes the
9635
+ * pre-#2485 fall-through, the safe direction. A copy loses the evidence
9636
+ * deliberately: a copy is not the pass that resolved anything.
9637
+ *
9638
+ * `CONFLICTING_PLAINTEXT` marks an expression this map saw resolve to TWO
9639
+ * values (a region-pinned re-resolution of one spelling, say); it then vouches
9640
+ * for nothing, which is the same "answer nothing you cannot prove" rule
9641
+ * {@link plaintextIndexOf} applies to the collapsed map's reverse index.
9642
+ */
9643
+ const resolvedPairsOf = /* @__PURE__ */ new WeakMap();
9644
+ /**
9645
+ * Record that `expression` resolved to `plaintext` in the pass that owns
9646
+ * `secrets` — the resolver's recording seam calls this beside its
9647
+ * `secrets.set(plaintext, expression)`, so the two never disagree about which
9648
+ * pass the evidence belongs to. Mask-only map entries (value `SECRET_MASK`)
9649
+ * never pass through that seam — they came from no `{{resolve:...}}` token —
9650
+ * so nothing here special-cases the mask string: a secret whose plaintext
9651
+ * happens to BE `***` is a secret like any other.
9652
+ */
9653
+ function recordResolvedPair(secrets, expression, plaintext) {
9654
+ let pairs = resolvedPairsOf.get(secrets);
9655
+ if (pairs === void 0) {
9656
+ pairs = /* @__PURE__ */ new Map();
9657
+ resolvedPairsOf.set(secrets, pairs);
9658
+ }
9659
+ const previous = pairs.get(expression);
9660
+ if (previous === void 0) pairs.set(expression, plaintext);
9661
+ else if (previous !== plaintext) pairs.set(expression, CONFLICTING_PLAINTEXT);
9662
+ }
9663
+ /**
9664
+ * Carry the resolved pairs of `from` into `to`, for the one copy of a
9665
+ * resolver-populated map that POSITIONS anything: the deploy engine accumulates
9666
+ * each stack's output resolution into its `outputSecrets` bag entry by entry,
9667
+ * and without this the copy would keep the collapsed entries while dropping the
9668
+ * evidence — so a literal `Output` embedding one of two same-plaintext
9669
+ * references would fall back to the value scan and persist the sibling's
9670
+ * expression. The engine's other entry-by-entry copy — an `Export.Name`'s
9671
+ * secrets into the pass map — deliberately does NOT call this: a name never
9672
+ * positions a leaf, a value re-using the same token records its own pair at
9673
+ * the seam, and the only thing the merge could add is a CONFLICT (a
9674
+ * non-cacheable `{{resolve:ssm:X}}` whose value moved between the value pass
9675
+ * and the name's resolution), which would destroy positioning the value pass
9676
+ * had earned. A pair that conflicts across the two maps is marked conflicting
9677
+ * in `to`, the same rule {@link recordResolvedPair} applies within one map.
9678
+ *
9679
+ * Deliberately NOT a general "copy the map" helper: every other new map is a
9680
+ * different PASS, and starting it without evidence is the safe direction.
9681
+ */
9682
+ function mergeResolvedPairs(from, to) {
9683
+ const pairs = resolvedPairsOf.get(from);
9684
+ if (pairs === void 0) return;
9685
+ for (const [expression, plaintext] of pairs) if (typeof plaintext === "string") recordResolvedPair(to, expression, plaintext);
9686
+ else {
9687
+ let target = resolvedPairsOf.get(to);
9688
+ if (target === void 0) {
9689
+ target = /* @__PURE__ */ new Map();
9690
+ resolvedPairsOf.set(to, target);
9691
+ }
9692
+ target.set(expression, CONFLICTING_PLAINTEXT);
9693
+ }
9694
+ }
9695
+ /**
9696
+ * The plaintext `expression` resolved to in the pass that owns `secrets`, or
9697
+ * `undefined` when that pass recorded nothing for it (or two different values).
9698
+ */
9699
+ function resolvedPlaintextOf(secrets, expression) {
9700
+ const recorded = resolvedPairsOf.get(secrets)?.get(expression);
9701
+ return typeof recorded === "string" ? recorded : void 0;
9702
+ }
9703
+ /**
9611
9704
  * Every `{{resolve:...}}` expression this process has PROVEN resolves to a
9612
9705
  * secret, as a SET — uncollapsed by resolved value (issue #1910).
9613
9706
  *
@@ -10618,8 +10711,9 @@ function isKnownSecretExpression(expression, secretExpressions) {
10618
10711
  return isSecretExpressionByVerdictOrSpelling(expression) || secretExpressions.has(expression);
10619
10712
  }
10620
10713
  /**
10621
- * The two arms of {@link isKnownSecretExpression} that need NO pass-local set:
10622
- * `secretsmanager` by SPELLING, and anything this process PROVED secret.
10714
+ * The arms of {@link isKnownSecretExpression} that need NO pass-local set:
10715
+ * `secretsmanager` / `ssm-secure` by SPELLING, and anything this process
10716
+ * PROVED secret.
10623
10717
  *
10624
10718
  * Split out so the resolver can ask the same question at the issue #2059
10625
10719
  * recording seam, where no `secretExpressions` set is in hand. It must not
@@ -10644,7 +10738,7 @@ function isKnownSecretExpression(expression, secretExpressions) {
10644
10738
  * change to a store this function only reads.
10645
10739
  */
10646
10740
  function isSecretExpressionByVerdictOrSpelling(expression) {
10647
- return expression.startsWith("{{resolve:secretsmanager:") || isRecordedSecretExpression(expression);
10741
+ return expression.startsWith("{{resolve:secretsmanager:") || expression.startsWith("{{resolve:ssm-secure:") || isRecordedSecretExpression(expression);
10648
10742
  }
10649
10743
  /**
10650
10744
  * The character class a `{{resolve:...}}` reference's INNER text is built from,
@@ -11188,6 +11282,124 @@ function positionByIntrinsicSkeleton(bag, source, secrets, secretExpressions) {
11188
11282
  return matched;
11189
11283
  }
11190
11284
  /**
11285
+ * The ONE-span frame shared by {@link positionByEmbeddedSpan} and
11286
+ * {@link learnMixedLeafNeedle}: a source holding exactly one `{{resolve:...}}`
11287
+ * token, and a bag that starts with the source's prefix and ends with its
11288
+ * suffix with something non-empty between them that is NOT itself a complete
11289
+ * token (an already-redacted record is a persisted answer, not a plaintext).
11290
+ * `undefined` for any other shape. One helper rather than two copies so the
11291
+ * two refusals cannot drift apart.
11292
+ */
11293
+ function singleSpanFrame(bag, source) {
11294
+ const spans = dynamicReferenceSpans(source);
11295
+ if (spans.length !== 1) return void 0;
11296
+ const [span] = spans;
11297
+ const token = source.slice(span.start, span.end);
11298
+ const prefix = source.slice(0, span.start);
11299
+ const suffix = source.slice(span.end);
11300
+ if (bag.length <= prefix.length + suffix.length) return void 0;
11301
+ if (!bag.startsWith(prefix) || !bag.endsWith(suffix)) return void 0;
11302
+ const middle = bag.slice(prefix.length, bag.length - suffix.length);
11303
+ if (isSingleDynamicReferenceToken(middle)) return void 0;
11304
+ return {
11305
+ token,
11306
+ prefix,
11307
+ suffix,
11308
+ middle
11309
+ };
11310
+ }
11311
+ /**
11312
+ * Position a literal source leaf that EMBEDS exactly one `{{resolve:...}}`
11313
+ * token — `postgres://app-svc:{{resolve:ssm-secure:NAME}}@db/app` — by the
11314
+ * span the source states, writing `prefix + token + suffix` (issue
11315
+ * [#2485](https://github.com/go-to-k/cdkd/issues/2485)).
11316
+ *
11317
+ * WHY THE VALUE SCAN IS NOT ENOUGH HERE. The scan writes the map's surviving
11318
+ * expression for a plaintext, and the map keeps one expression per plaintext:
11319
+ * a whole-value `NAME:1` sibling that resolved LAST leaves `NAME:1` as the only
11320
+ * expression for the value, so the embedded leaf persists the versioned
11321
+ * spelling for a template that spells `NAME`, and the deploy diff — expression
11322
+ * against expression — reports that leaf on every run. The whole-token arm of
11323
+ * {@link redactByPath} is immune because it copies its own source; this arm
11324
+ * gives the one-span literal leaf the same immunity.
11325
+ *
11326
+ * THE EVIDENCE, and why the shape of the frame is not enough on its own: the
11327
+ * frame check (`bag` starts with the source's prefix and ends with its suffix,
11328
+ * with something between) is what {@link learnMixedLeafNeedle} already uses to
11329
+ * LEARN a needle, and it proves only that the bag has the source's shape. The
11330
+ * bag can also be a PREVIOUS generation's (`cdkd scrub`, a state-sourced walk)
11331
+ * with an earlier plaintext framed exactly like this, and writing today's
11332
+ * token over it would record an expression that was never deployed at that
11333
+ * position — the hazard `sourceIsSameGeneration` exists for on the whole-token
11334
+ * arm. So the middle must EQUAL what THIS pass recorded the source token
11335
+ * resolving to ({@link recordResolvedPair}, per map instance): that is evidence
11336
+ * of this resolution, not of shape, and it is absent by construction for every
11337
+ * bag this pass did not produce. It is also what keeps a PUBLIC `ssm` token
11338
+ * resolved (issue #1901) — the resolver records only secret verdicts — and what
11339
+ * keeps a mask-only `NoEcho` value out (never recorded).
11340
+ *
11341
+ * WHAT THIS EVIDENCE DOES NOT CLAIM, stated because a reviewer asked: it does
11342
+ * not prove the bag was produced FROM this source. A previous generation's bag
11343
+ * whose framed middle happens to EQUAL a plaintext this pass resolved the
11344
+ * source token to (`cdkd scrub` walking an old record against today's template,
11345
+ * or a failed deploy persisting an old bag) takes this arm and persists TODAY's
11346
+ * expression at that position. That is not a new claim: the value scan the
11347
+ * arm replaces rewrites that same plaintext onto one of THIS pass's expressions
11348
+ * regardless of generation — the map holds no other — so the class of answer
11349
+ * is unchanged and only the choice within it improves (the source's own
11350
+ * token rather than the map's survivor). The generation hazard this arm must
11351
+ * not create is the whole-token arm's: a middle that is ALREADY an expression
11352
+ * (a persisted answer from another generation), which the token refusal below
11353
+ * keeps out — and, by the same argument, any leaf the value scan would NOT
11354
+ * rewrite to exactly `prefix + survivor + suffix`: a middle shorter than the
11355
+ * scan's needle floor (an embedded 1-3 character secret stays the scan's
11356
+ * documented residual — issue #2516 tracks closing it with a bound that
11357
+ * proves the bag's generation, which this evidence does not), a whole leaf
11358
+ * that is itself another recorded plaintext, a needle starting in the prefix
11359
+ * and overlapping the middle. The
11360
+ * arm checks that equivalence against the scan's own answer rather than
11361
+ * re-deriving the scan's rules. Pinned by the cross-generation cases in
11362
+ * `secret-redaction-embedded-span.test.ts`.
11363
+ *
11364
+ * One shape reaches this arm that a reader may not expect: a WHOLE-token
11365
+ * source that FAILED the whole-token arm's `isKnownSecretExpression` gate (an
11366
+ * `ssm` token whose type came back unclassifiable and which lost the map slot
11367
+ * to a sibling). Its "frame" is empty, and if this pass recorded it resolving
11368
+ * to the bag it is written back as itself — an expression, and the leaf's own,
11369
+ * where the scan wrote the survivor. Stated so it is not mistaken for a leak.
11370
+ *
11371
+ * Everything else keeps the pre-#2485 fall-through: two or more spans (which
11372
+ * span produced which value is genuinely ambiguous when they share one), an
11373
+ * `Fn::Sub` / `Fn::Join` source (an object, not this arm at all — issue #2320's
11374
+ * placeholder primitive), a frame mismatch, a middle that is itself a complete
11375
+ * token (an already-redacted record, per the same refusal
11376
+ * {@link learnMixedLeafNeedle} makes), and a middle this pass cannot vouch for.
11377
+ *
11378
+ * The frame is copied from the SOURCE, not scanned. A needle occurring in the
11379
+ * literal frame would be a reference the template never had at that offset —
11380
+ * the fabricated-baseline direction {@link preferPositionDecisions} refuses —
11381
+ * and the whole-token arm returns its source unscanned for the same reason.
11382
+ *
11383
+ * RETURNS THE VALUE SCAN'S ANSWER ON EVERY REFUSAL, not `undefined`: the scan
11384
+ * is computed once, here, for `(bag, secrets)` — the arm's bound below compares
11385
+ * against it, and every fall-through IS it — so the compared value provably
11386
+ * comes from the same bag and map the arm positions. An earlier revision took
11387
+ * the scan as a parameter, which left the bound one wrong caller away from
11388
+ * comparing against a scan of some other bag with no type error.
11389
+ */
11390
+ function positionByEmbeddedSpan(bag, source, secrets) {
11391
+ const scanned = redactSecretsForState(bag, secrets);
11392
+ const frame = singleSpanFrame(bag, source);
11393
+ if (frame === void 0) return scanned;
11394
+ const { token, prefix, suffix, middle } = frame;
11395
+ const recorded = resolvedPlaintextOf(secrets, token);
11396
+ if (recorded === void 0 || recorded !== middle) return scanned;
11397
+ const survivor = secrets.get(middle);
11398
+ if (survivor === void 0) return scanned;
11399
+ if (scanned !== prefix + survivor + suffix) return scanned;
11400
+ return prefix + token + suffix;
11401
+ }
11402
+ /**
11191
11403
  * Keys tried, in order, when pairing two arrays whose ORDER cannot be trusted
11192
11404
  * (issue #1915).
11193
11405
  *
@@ -11387,7 +11599,7 @@ function redactByPath(bag, source, secrets, rules, secretExpressions) {
11387
11599
  if (!rules.sourceIsSameGeneration && isSingleDynamicReferenceToken(bag)) return secrets.get(bag) ?? bag;
11388
11600
  return source;
11389
11601
  }
11390
- return redactSecretsForState(bag, secrets);
11602
+ return positionByEmbeddedSpan(bag, source, secrets);
11391
11603
  }
11392
11604
  if (typeof bag === "string" && isPlainObject$2(source)) {
11393
11605
  const certified = positionByCrossStackSource(bag, source, secrets);
@@ -11505,12 +11717,13 @@ function subtreeHasDynamicReference(value) {
11505
11717
  * Widening it changes one answer, in the SAFE direction for BOTH readers.
11506
11718
  *
11507
11719
  * `drift.ts`'s `survivingDynamicReferences` is the reader that is easy to
11508
- * forget, because it lives in another file — it feeds `isSecretBySpelling`,
11509
- * so seeing MORE tokens can only mask more, never less. Do not shorten this
11510
- * to "the only reader": that sentence is what a later editor uses to bound
11511
- * the blast radius of touching the class, and getting it wrong points them
11512
- * away from the report / `--json` / `--accept` path where an unmasked
11513
- * `ssm-secure` survivor would surface.
11720
+ * forget, because it lives in another file — it feeds the survivor REPORT
11721
+ * (`onUnresolved`, and through it the `unresolvedToken` cause), so seeing MORE
11722
+ * tokens can only report more, never less. Do not shorten this to "the only
11723
+ * reader": that sentence is what a later editor uses to bound the blast
11724
+ * radius of touching the class, and getting it wrong points them away from
11725
+ * the report / `--json` / `--accept` path where an unreported survivor would
11726
+ * surface.
11514
11727
  *
11515
11728
  * The other reader is the DECLARED direction for issue #1901:
11516
11729
  * {@link mixedLeafMayCarryPublicReference}, which asks whether a MIXED leaf
@@ -11916,9 +12129,9 @@ function unkeyedArrayPairsByAnchors(bag, source) {
11916
12129
  * from one the pass decided IN FAVOUR of the value already there. Two shapes
11917
12130
  * hit it, both fabricating a baseline `cdkd drift --revert` then pushes:
11918
12131
  *
11919
- * - the resolver's unsupported-service arm leaves an `{{resolve:ssm-secure:`
11920
- * token LITERAL, so AWS echoes it back and the source leaf EQUALS the bag
11921
- * leaf. The string arm returns `source` — a decision — and the equality made
12132
+ * - the resolver's unsupported-service arm leaves a `{{resolve:...}}` token it
12133
+ * has no arm for LITERAL (`ssm-secure:` was one until issue #2482), so AWS
12134
+ * echoes it back and the source leaf EQUALS the bag leaf. The string arm returns `source` — a decision — and the equality made
11922
12135
  * it look like no decision at all. (A BARE such token takes the whole-token
11923
12136
  * arm and one embedded in text takes the mixed-leaf arm; both decide, and
11924
12137
  * both were misread.)
@@ -12102,7 +12315,8 @@ function learnWholeTokenNeedle(collector, bag, source) {
12102
12315
  * would then contain a whole `{{resolve:...}}` token and a resolved readback
12103
12316
  * cannot end with one. The shape it genuinely decides is a second reference
12104
12317
  * that survives LITERALLY in the readback — the resolver's
12105
- * unsupported-service arm (`ssm-secure:`) produces exactly that — where the
12318
+ * unsupported-service arm produces exactly that (`ssm-secure:` did until
12319
+ * issue #2482; a spelling with no arm still does) — where the
12106
12320
  * extraction would in fact be right and is declined anyway. Measured: a
12107
12321
  * both-resolved fixture leaves this line unfenced.
12108
12322
  * - the source's literal PREFIX and SUFFIX must both be present at the ends of
@@ -12116,17 +12330,10 @@ function learnWholeTokenNeedle(collector, bag, source) {
12116
12330
  * correctly, while an `indexOf` scan would cut it short.
12117
12331
  */
12118
12332
  function learnMixedLeafNeedle(collector, bag, source) {
12119
- const spans = dynamicReferenceSpans(source);
12120
- if (spans.length !== 1) return;
12121
- const [span] = spans;
12122
- const token = source.slice(span.start, span.end);
12333
+ const frame = singleSpanFrame(bag, source);
12334
+ if (frame === void 0) return;
12335
+ const { token, middle: plaintext } = frame;
12123
12336
  if (!expressionMaySeedANeedle(token)) return;
12124
- const prefix = source.slice(0, span.start);
12125
- const suffix = source.slice(span.end);
12126
- if (bag.length <= prefix.length + suffix.length) return;
12127
- if (!bag.startsWith(prefix) || !bag.endsWith(suffix)) return;
12128
- const plaintext = bag.slice(prefix.length, bag.length - suffix.length);
12129
- if (isSingleDynamicReferenceToken(plaintext)) return;
12130
12337
  learnNeedle(collector, plaintext, token);
12131
12338
  }
12132
12339
  /**
@@ -15382,17 +15589,22 @@ function s3BucketWebsiteUrl(bucketName, region) {
15382
15589
  /**
15383
15590
  * The `{{resolve:<service>:...}}` families whose value can be a SECRET, and
15384
15591
  * therefore the only ones the region question below is asked about: every
15385
- * `secretsmanager` reference by spelling, and every `ssm` one, which is secret
15386
- * exactly when its parameter is a `SecureString` (issue #1901).
15387
- *
15388
- * Every OTHER service is `local` because cdkd cannot resolve it at all, NOT
15389
- * because it is public. `ssm-secure` is the live example and is emphatically
15390
- * not public: `resolveDynamicReferences` has no arm for it, so the literal
15391
- * token is passed through to AWS and CloudFormation resolves it SERVER-side.
15392
- * cdkd never holds its value, so there is no region for cdkd to get wrong
15393
- * which is the only reason it can be waved through here.
15394
- */
15395
- const REPLAY_SECRET_SERVICES = /* @__PURE__ */ new Set(["secretsmanager", "ssm"]);
15592
+ * `secretsmanager` reference by spelling, every `ssm-secure` one by spelling
15593
+ * (issue #2482 it is resolved through the same `GetParameter` as `ssm`, so
15594
+ * the wrong region answers it in exactly the same way), and every `ssm` one,
15595
+ * which is secret exactly when its parameter is a `SecureString` (issue #1901).
15596
+ *
15597
+ * Every OTHER service is `local` because cdkd cannot resolve it at all — the
15598
+ * resolver's unsupported-service arm leaves such a token in place, so there
15599
+ * is no lookup for a region to get wrong. None of CloudFormation's three
15600
+ * services is in that position any more; the arm exists for a spelling that
15601
+ * is not a dynamic reference at all, or one AWS adds later.
15602
+ */
15603
+ const REPLAY_SECRET_SERVICES = /* @__PURE__ */ new Set([
15604
+ "secretsmanager",
15605
+ "ssm",
15606
+ "ssm-secure"
15607
+ ]);
15396
15608
  /**
15397
15609
  * Split a `{{resolve:secretsmanager:...}}` inner body into its SECRET_ID.
15398
15610
  *
@@ -15428,7 +15640,7 @@ function secretsManagerSecretId(inner) {
15428
15640
  * a different thing in the refusal message than the one that would be read.
15429
15641
  */
15430
15642
  function ssmParameterName(inner) {
15431
- return inner.substring(4);
15643
+ return inner.substring(inner.indexOf(":") + 1);
15432
15644
  }
15433
15645
  /**
15434
15646
  * The region an ARN names, or `undefined` for anything that is not an ARN with
@@ -20488,7 +20700,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
20488
20700
  });
20489
20701
  for (const { fullMatch, inner } of matches) {
20490
20702
  const service = inner.split(":")[0];
20491
- const isKnownSecret = service === "secretsmanager" || recordedSecretExpressions.has(fullMatch);
20703
+ const isKnownSecret = service === "secretsmanager" || service === "ssm-secure" || recordedSecretExpressions.has(fullMatch);
20492
20704
  if (isKnownSecret && context?.skipDynamicReferences) continue;
20493
20705
  const regionVerdict = classifyReplaySecretRegion(fullMatch, this.explicitRegion ?? this.resolverRegion, context?.producerRegions);
20494
20706
  if (regionVerdict.kind === "ambiguous") throw markNonRetryable(new DynamicReferenceRegionAmbiguousError(`Refusing to resolve the secret reference ${fullMatch}: it names '${regionVerdict.secretName}' without a region, and this stack reads from ${regionVerdict.foreignProducerRegions.join(", ")} as well as its own region. cdkd cannot tell which one must answer, and resolving against the wrong one yields a different secret. Spell the reference as a full ARN to say which region owns it.`));
@@ -20499,7 +20711,10 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
20499
20711
  }
20500
20712
  const cached = this.cachedDynamicReferences.get(fullMatch);
20501
20713
  if (cached) {
20502
- if (cached.secret && cached.value) context?.recordedSecretValues?.set(cached.value, fullMatch);
20714
+ if (cached.secret && cached.value) {
20715
+ context?.recordedSecretValues?.set(cached.value, fullMatch);
20716
+ if (context?.recordedSecretValues) recordResolvedPair(context.recordedSecretValues, fullMatch, cached.value);
20717
+ }
20503
20718
  result = result.replace(fullMatch, () => cached.value);
20504
20719
  continue;
20505
20720
  }
@@ -20531,6 +20746,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
20531
20746
  if (!decrypt) continue;
20532
20747
  }
20533
20748
  resolved = param.value;
20749
+ } else if (service === "ssm-secure") {
20750
+ const param = await this.resolveSSMReference(parts, true, "ssm-secure");
20751
+ if (!param.secure) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Refusing to resolve ${fullMatch}: the parameter is a ${param.type} parameter, and the ssm-secure spelling is defined for SecureString parameters only. Reference it as {{resolve:ssm:...}} if it is public configuration.`));
20752
+ isSecret = true;
20753
+ resolved = param.value;
20534
20754
  } else {
20535
20755
  this.logger.warn(`Unsupported dynamic reference service: ${service}`);
20536
20756
  continue;
@@ -20541,7 +20761,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
20541
20761
  });
20542
20762
  if (isSecret && resolved) {
20543
20763
  context?.recordedSecretValues?.set(resolved, fullMatch);
20544
- if (service === "secretsmanager") this.pinSecretVerdict(fullMatch, true);
20764
+ if (context?.recordedSecretValues) recordResolvedPair(context.recordedSecretValues, fullMatch, resolved);
20765
+ if (service === "secretsmanager" || service === "ssm-secure") this.pinSecretVerdict(fullMatch, true);
20545
20766
  }
20546
20767
  result = result.replace(fullMatch, () => resolved);
20547
20768
  }
@@ -20751,16 +20972,16 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
20751
20972
  * discard the value when `secure` is set — it is ciphertext, not the resolved
20752
20973
  * reference.
20753
20974
  */
20754
- async resolveSSMReference(parts, decrypt = true) {
20975
+ async resolveSSMReference(parts, decrypt = true, service = "ssm") {
20755
20976
  const parameterName = parts.slice(1).join(":");
20756
- if (!parameterName) throw new Error("Dynamic reference: ssm PARAMETER_NAME is required");
20757
- this.logger.debug(`Resolving dynamic reference: ssm:${parameterName}`);
20977
+ if (!parameterName) throw new Error(`Dynamic reference: ${service} PARAMETER_NAME is required`);
20978
+ this.logger.debug(`Resolving dynamic reference: ${service}:${parameterName}`);
20758
20979
  const client = this.clientsForRegion(this.explicitRegion).ssm;
20759
20980
  const command = new GetParameterCommand({
20760
20981
  Name: parameterName,
20761
20982
  WithDecryption: decrypt
20762
20983
  });
20763
- const response = await this.sendWithThrottleRetry(() => client.send(command), `ssm:${parameterName}`);
20984
+ const response = await this.sendWithThrottleRetry(() => client.send(command), `${service}:${parameterName}`);
20764
20985
  const paramValue = response.Parameter?.Value;
20765
20986
  if (paramValue === void 0 || paramValue === null) throw new Error(`Dynamic reference: SSM parameter '${parameterName}' not found or has no value`);
20766
20987
  const paramType = response.Parameter?.Type;
@@ -20768,7 +20989,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
20768
20989
  if (secure && paramType !== "SecureString" && !this.warnedUnrecognizedSsmTypes.has(`${parameterName}\u0000${String(paramType)}`)) {
20769
20990
  this.warnedUnrecognizedSsmTypes.add(`${parameterName}\u0000${String(paramType)}`);
20770
20991
  const reported = paramType === void 0 ? "(absent)" : `'${String(paramType)}'`;
20771
- this.logger.warn(`SSM parameter '${parameterName}' reported an unrecognized Type ${reported} — treating its value as a secret, so cdkd will persist the {{resolve:ssm:...}} expression rather than the resolved value. Declare the parameter as String / StringList if it is public config.`);
20992
+ this.logger.warn(`SSM parameter '${parameterName}' reported an unrecognized Type ${reported} — treating its value as a secret, so cdkd will persist the {{resolve:${service}:...}} expression rather than the resolved value. Declare the parameter as String / StringList if it is public config.`);
20772
20993
  }
20773
20994
  return {
20774
20995
  value: paramValue,
@@ -21903,7 +22124,7 @@ var CloudControlProvider = class {
21903
22124
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
21904
22125
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
21905
22126
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
21906
- const { ASGProvider } = await import("./asg-provider-DcFgnQQO.js").then((n) => n.n);
22127
+ const { ASGProvider } = await import("./asg-provider-BvIH9Ivw.js").then((n) => n.n);
21907
22128
  return withIndeterminateGuard(await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
21908
22129
  }
21909
22130
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
@@ -34007,7 +34228,10 @@ var DeployEngine = class {
34007
34228
  }
34008
34229
  outputsPassCompleted = true;
34009
34230
  } finally {
34010
- if (context.recordedSecretValues) for (const [value, expr] of context.recordedSecretValues) this.outputSecrets.set(value, expr);
34231
+ if (context.recordedSecretValues) {
34232
+ for (const [value, expr] of context.recordedSecretValues) this.outputSecrets.set(value, expr);
34233
+ mergeResolvedPairs(context.recordedSecretValues, this.outputSecrets);
34234
+ }
34011
34235
  if (!outputsPassCompleted) this.outputsSourceUsable = false;
34012
34236
  }
34013
34237
  for (const [outputKey, output] of Object.entries(template.Outputs)) {
@@ -34028,5 +34252,5 @@ var DeployEngine = class {
34028
34252
  };
34029
34253
 
34030
34254
  //#endregion
34031
- export { DEFAULT_STATE_PREFIX as $, resolveUseCdkBootstrapAssets as $n, __exportAll as $r, isSingleDynamicReferenceToken as $t, bold as A, validateAssetBucketName as An, LocalStartServiceError as Ar, requireConfigObject as At, secretBearingStateKeyWarning as B, runDockerStreaming as Bn, StateError as Br, DiffCalculator as Bt, unsupportedFinalSnapshotError as C, BOOTSTRAP_MARKER_PREFIX as Cn, ConfigError as Cr, assertRegionMatch as Ct, isStatefulRecreateTargetSync as D, isCrossRegionRedirect as Dn, DynamicReferenceRegionAmbiguousError as Dr, readConfigString as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, getBootstrapMarkerKey as En, DeployCancelledError as Er, configStringRefusal as Et, yellow as F, dockerSpawnEnvWithSensitive as Fn, ProvisioningError as Fr, s3BucketDomainName as Ft, clearOnUpdateRemoval as G, getDefaultStateBucketName as Gn, withErrorHandling as Gr, TemplateParser as Gt, getCurrentResourceSecrets as H, getDockerImageBySourceHash as Hn, formatError as Hr, describeTypeWithThrottleRetry as Ht, collectDeclaredOutputNames as I, formatDockerLoginError as In, ResourceTimeoutError as Ir, s3BucketDualStackDomainName as It, findSilentDropProperties as J, resolveAutoAssetStorage as Jn, isThrottlingError as Jr, TEMPLATE_SOURCED_RULES as Jt, ProviderRegistry as K, getLegacyStateBucketName as Kn, isMarkedNonRetryable as Kr, STATE_SOURCED_CROSS_GENERATION_RULES as Kt, collectPublishedOutputNames as L, getDockerCmd as Ln, ResourceUpdateNotSupportedError as Lr, s3BucketRegionalDomainName as Lt, gray as M, buildDenyExternalAccessPolicy as Mn, MissingCdkCliError as Mr, classifyReplaySecretRegion as Mt, green as N, describeAwsFailure as Nn, NestedStackChildDirectDestroyError as Nr, producerRegionsFromState as Nt, renderStatefulReason as O, parseBootstrapMarker as On, LocalInvokeBuildError as Or, replayWarn as Ot, red as P, buildDockerImage as Pn, PartialFailureError as Pr, s3BucketArn as Pt, CUSTOM_RESOURCE_RESPONSE_PREFIX as Q, resolveStateBucketWithDefaultAndSource as Qn, retryClassificationText as Qr, errorCauseChain as Qt, exportAliasCollisionScrubWarning as R, partitionSensitiveEnv as Rn, StackHasActiveImportsError as Rr, s3BucketWebsiteUrl as Rt, refusesFinalSnapshot as S, AssetModeResolver as Sn, CdkdError as Sr, resolveExplicitPhysicalId as St, extractDeploymentEventError as T, ensureAssetStorage as Tn, DependencyError as Tr, configBooleanRefusal as Tt, IAMRoleProvider as U, Synthesizer as Un, isCdkdError as Ur, withRetry as Ut, stateKeySecretExposure as V, AssetManifestLoader as Vn, SynthesisError as Vr, INTRINSIC_KEYS as Vt, collectInlinePolicyNamesManagedBySiblings as W, synthesisStatusMessage as Wn, normalizeAwsError as Wr, DagBuilder as Wt, maskDeep as X, resolveSkipPrefix as Xn, markNonRetryable as Xr, createSecretMasker as Xt, createMaskedRetryLogger as Y, resolveCaptureObservedState as Yn, isTransientServerError as Yr, carriesSecretMask as Yt, maskerOrIdentity as Z, resolveStateBucketWithDefault as Zn, markRedactedCause as Zr, dynamicReferenceTokens as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, createAssetRedirectResolver as _n, AwsClients as _r, isUnboundTemplateParameter as _t, DeploymentEventsStore as a, scrubResourceRecord as an, findLargeInlineResources as ar, CloudControlProvider as at, createPreDeleteFinalSnapshot as b, escapeRegExp$1 as bn, setAwsClients as br, WAFv2WebACLProvider as bt, replayFailedOperations as c, rebuildClientForBucketRegion as cn, displaySafe as cr, deleteIndeterminateGuards as ct, updatePartialReason as d, importableOutputs as dn, canonicalizeRegion as dr, isTerminationProtectionPropagationError as dt, maskSecretsInError as en, stateBucketExistenceConfirmed as er, beginCommandInterruptScope as et, withResourceDeadline as f, shouldRetainResource as fn, derivePartitionAndUrlSuffix as fr, IntrinsicFunctionResolver as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, buildAssetRedirectMap as gn, resolveBucketRegion as gr, getAccountInfo as gt, computeImplicitDeleteEdges as h, WorkGraph as hn, clearBucketRegionCache as hr, coerceParameterTypedValue as ht, DeploymentEventsReader as i, redactSecretsForState as in, MIGRATE_TMP_PREFIX as ir, startInterruptWatch as it, cyan as j, validateContainerRepoName as jn, LockError as jr, requireConfigString as jt, formatResourceLine as k, readBootstrapMarkerBody as kn, LocalMigrateError as kr, requireConfigArray as kt, replayRollback as l, exportNamesCarriedFrom as ln, expectedOwnerParam as lr, deleteSkipReason as lt, IMPLICIT_DELETE_DEPENDENCIES as m, stringifyValue as mn, processStackMessages as mr, cfnRefValueFromPhysicalId as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, recordMaskOnlyValue as nn, CFN_TEMPLATE_BODY_LIMIT as nr, interruptWatchListenerCount as nt, planFailedOps as o, LockManager as on, uploadCfnTemplate as or, slowCcOperationTimeoutMs as ot, maskingRetryLogger as p, AssetPublisher as pn, AssemblyReader as pr, carriesDynamicReference as pt, findActionableSilentDrops as q, resolveApp as qn, isRetryableTransientError as qr, STATE_SOURCED_READBACK_RULES as qt, DeployEngine as r, recoverMaskedOutput as rn, CFN_TEMPLATE_URL_LIMIT as rr, isInterruptedWaitError as rt, planRollback as s, S3StateBackend as sn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as sr, UNSPECIFIED_SKIP_REASON as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, maskSecretsInText as tn, warnDeprecatedNoPrefixCliFlag as tr, endCommandInterruptScope as tt, updatePartialMessage as u, importableOutputKeys as un, PARTITION_TABLE as ur, disableInstanceApiTermination as ut, buildFinalSnapshotIdentifier as v, loadPublishableAssetManifest as vn, getAwsClients as vr, parameterTypeMayLoseSecretIdentity as vt, makeCanonicalizePropertiesFn as w, assertAssetBucketRegion as wn, CrossAccountSecretRefusalError as wr, coerceCfnBoolean as wt, isFinalSnapshotError as x, stripControlChars as xn, AssetError as xr, normalizeAwsTagsToCfn as xt, ccRoutedFinalSnapshotError as y, rewriteTemplateAssetReferences as yn, resetAwsClients as yr, refStateLookupFromResource as yt, isExportAliasCollision as z, runDockerForeground as zn, StackTerminationProtectionError as zr, applyRoleArnIfSet as zt };
34032
- //# sourceMappingURL=deploy-engine-DhMm2M33.js.map
34255
+ export { DEFAULT_STATE_PREFIX as $, resolveUseCdkBootstrapAssets as $n, retryClassificationText as $r, isSingleDynamicReferenceToken as $t, bold as A, validateAssetBucketName as An, LocalMigrateError as Ar, requireConfigObject as At, secretBearingStateKeyWarning as B, runDockerStreaming as Bn, StackTerminationProtectionError as Br, DiffCalculator as Bt, unsupportedFinalSnapshotError as C, BOOTSTRAP_MARKER_PREFIX as Cn, ConfigError as Cr, assertRegionMatch as Ct, isStatefulRecreateTargetSync as D, isCrossRegionRedirect as Dn, DynamicReferenceRegionAmbiguousError as Dr, readConfigString as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, getBootstrapMarkerKey as En, DeployCancelledError as Er, configStringRefusal as Et, yellow as F, dockerSpawnEnvWithSensitive as Fn, PartialFailureError as Fr, s3BucketDomainName as Ft, clearOnUpdateRemoval as G, getDefaultStateBucketName as Gn, normalizeAwsError as Gr, TemplateParser as Gt, getCurrentResourceSecrets as H, getDockerImageBySourceHash as Hn, SynthesisError as Hr, describeTypeWithThrottleRetry as Ht, collectDeclaredOutputNames as I, formatDockerLoginError as In, ProvisioningError as Ir, s3BucketDualStackDomainName as It, findSilentDropProperties as J, resolveAutoAssetStorage as Jn, isRetryableTransientError as Jr, TEMPLATE_SOURCED_RULES as Jt, ProviderRegistry as K, getLegacyStateBucketName as Kn, withErrorHandling as Kr, STATE_SOURCED_CROSS_GENERATION_RULES as Kt, collectPublishedOutputNames as L, getDockerCmd as Ln, ResourceTimeoutError as Lr, s3BucketRegionalDomainName as Lt, gray as M, buildDenyExternalAccessPolicy as Mn, LockError as Mr, classifyReplaySecretRegion as Mt, green as N, describeAwsFailure as Nn, MissingCdkCliError as Nr, producerRegionsFromState as Nt, renderStatefulReason as O, parseBootstrapMarker as On, IntrinsicResolutionRefusalError as Or, replayWarn as Ot, red as P, buildDockerImage as Pn, NestedStackChildDirectDestroyError as Pr, s3BucketArn as Pt, CUSTOM_RESOURCE_RESPONSE_PREFIX as Q, resolveStateBucketWithDefaultAndSource as Qn, markRedactedCause as Qr, errorCauseChain as Qt, exportAliasCollisionScrubWarning as R, partitionSensitiveEnv as Rn, ResourceUpdateNotSupportedError as Rr, s3BucketWebsiteUrl as Rt, refusesFinalSnapshot as S, AssetModeResolver as Sn, CdkdError as Sr, resolveExplicitPhysicalId as St, extractDeploymentEventError as T, ensureAssetStorage as Tn, DependencyError as Tr, configBooleanRefusal as Tt, IAMRoleProvider as U, Synthesizer as Un, formatError as Ur, withRetry as Ut, stateKeySecretExposure as V, AssetManifestLoader as Vn, StateError as Vr, INTRINSIC_KEYS as Vt, collectInlinePolicyNamesManagedBySiblings as W, synthesisStatusMessage as Wn, isCdkdError as Wr, DagBuilder as Wt, maskDeep as X, resolveSkipPrefix as Xn, isTransientServerError as Xr, createSecretMasker as Xt, createMaskedRetryLogger as Y, resolveCaptureObservedState as Yn, isThrottlingError as Yr, carriesSecretMask as Yt, maskerOrIdentity as Z, resolveStateBucketWithDefault as Zn, markNonRetryable as Zr, dynamicReferenceTokens as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, createAssetRedirectResolver as _n, AwsClients as _r, isUnboundTemplateParameter as _t, DeploymentEventsStore as a, scrubResourceRecord as an, findLargeInlineResources as ar, CloudControlProvider as at, createPreDeleteFinalSnapshot as b, escapeRegExp$1 as bn, setAwsClients as br, WAFv2WebACLProvider as bt, replayFailedOperations as c, rebuildClientForBucketRegion as cn, displaySafe as cr, deleteIndeterminateGuards as ct, updatePartialReason as d, importableOutputs as dn, canonicalizeRegion as dr, isTerminationProtectionPropagationError as dt, __exportAll as ei, maskSecretsInError as en, stateBucketExistenceConfirmed as er, beginCommandInterruptScope as et, withResourceDeadline as f, shouldRetainResource as fn, derivePartitionAndUrlSuffix as fr, IntrinsicFunctionResolver as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, buildAssetRedirectMap as gn, resolveBucketRegion as gr, getAccountInfo as gt, computeImplicitDeleteEdges as h, WorkGraph as hn, clearBucketRegionCache as hr, coerceParameterTypedValue as ht, DeploymentEventsReader as i, redactSecretsForState as in, MIGRATE_TMP_PREFIX as ir, startInterruptWatch as it, cyan as j, validateContainerRepoName as jn, LocalStartServiceError as jr, requireConfigString as jt, formatResourceLine as k, readBootstrapMarkerBody as kn, LocalInvokeBuildError as kr, requireConfigArray as kt, replayRollback as l, exportNamesCarriedFrom as ln, expectedOwnerParam as lr, deleteSkipReason as lt, IMPLICIT_DELETE_DEPENDENCIES as m, stringifyValue as mn, processStackMessages as mr, cfnRefValueFromPhysicalId as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, recordMaskOnlyValue as nn, CFN_TEMPLATE_BODY_LIMIT as nr, interruptWatchListenerCount as nt, planFailedOps as o, LockManager as on, uploadCfnTemplate as or, slowCcOperationTimeoutMs as ot, maskingRetryLogger as p, AssetPublisher as pn, AssemblyReader as pr, carriesDynamicReference as pt, findActionableSilentDrops as q, resolveApp as qn, isMarkedNonRetryable as qr, STATE_SOURCED_READBACK_RULES as qt, DeployEngine as r, recoverMaskedOutput as rn, CFN_TEMPLATE_URL_LIMIT as rr, isInterruptedWaitError as rt, planRollback as s, S3StateBackend as sn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as sr, UNSPECIFIED_SKIP_REASON as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, maskSecretsInText as tn, warnDeprecatedNoPrefixCliFlag as tr, endCommandInterruptScope as tt, updatePartialMessage as u, importableOutputKeys as un, PARTITION_TABLE as ur, disableInstanceApiTermination as ut, buildFinalSnapshotIdentifier as v, loadPublishableAssetManifest as vn, getAwsClients as vr, parameterTypeMayLoseSecretIdentity as vt, makeCanonicalizePropertiesFn as w, assertAssetBucketRegion as wn, CrossAccountSecretRefusalError as wr, coerceCfnBoolean as wt, isFinalSnapshotError as x, stripControlChars as xn, AssetError as xr, normalizeAwsTagsToCfn as xt, ccRoutedFinalSnapshotError as y, rewriteTemplateAssetReferences as yn, resetAwsClients as yr, refStateLookupFromResource as yt, isExportAliasCollision as z, runDockerForeground as zn, StackHasActiveImportsError as zr, applyRoleArnIfSet as zt };
34256
+ //# sourceMappingURL=deploy-engine-CA50haPO.js.map