@go-to-k/cdkd 0.283.23 → 0.283.24

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.
@@ -9842,6 +9842,102 @@ function buildNeedleRegex(values) {
9842
9842
  if (needles.length === 0) return void 0;
9843
9843
  return new RegExp(needles.map(escapeRegExp).join("|"), "g");
9844
9844
  }
9845
+ /** The bag was produced by resolving the source: same shape, template source. */
9846
+ const TEMPLATE_DERIVED_RULES = {
9847
+ descendArrays: true,
9848
+ trustAnyExpression: false
9849
+ };
9850
+ /** An AWS readback projected from a persisted STATE bag. */
9851
+ const STATE_SOURCED_READBACK_RULES = {
9852
+ descendArrays: false,
9853
+ trustAnyExpression: true
9854
+ };
9855
+ /** An AWS readback projected from the TEMPLATE: neither relaxation applies. */
9856
+ const TEMPLATE_SOURCED_READBACK_RULES = {
9857
+ descendArrays: false,
9858
+ trustAnyExpression: false
9859
+ };
9860
+ function isDynamicReferenceString(value) {
9861
+ return typeof value === "string" && value.includes("{{resolve:");
9862
+ }
9863
+ /**
9864
+ * Is this template expression one whose resolved value is a SECRET?
9865
+ *
9866
+ * Two independent answers, and both are needed:
9867
+ *
9868
+ * - A `secretsmanager` reference is secret BY DEFINITION, so spelling settles
9869
+ * it with no lookup. This arm is what makes the #1904 fix work at all: when
9870
+ * two expressions resolve to the same value the value-keyed map keeps only the
9871
+ * last, so asking the map whether the LOSING expression was a secret answers
9872
+ * "no" — precisely for the pair the fix exists to separate.
9873
+ * - An `ssm` reference is secret only when its parameter is a `SecureString`
9874
+ * (issue #1901), which is not derivable from the string, so that arm consults
9875
+ * what the resolver actually recorded.
9876
+ *
9877
+ * The residue is narrow and safe: two SecureString ssm references sharing one
9878
+ * resolved value still collapse, because the map cannot distinguish them and
9879
+ * spelling cannot either. That is the pre-existing behavior, it leaks nothing
9880
+ * (both are redacted, just to one expression), and closing it needs the resolver
9881
+ * to record expressions in their own set rather than as map values.
9882
+ */
9883
+ function isKnownSecretExpression(expression, secretExpressions) {
9884
+ return expression.startsWith("{{resolve:secretsmanager:") || secretExpressions.has(expression);
9885
+ }
9886
+ /**
9887
+ * Is this source leaf a SINGLE complete `{{resolve:...}}` token and nothing
9888
+ * else?
9889
+ *
9890
+ * Whole-leaf substitution is only correct for that shape. A MIXED leaf --
9891
+ * `pre{{resolve:ssm:/public}}-{{resolve:secretsmanager:x}}post`, i.e. anything
9892
+ * the resolver substituted INTO rather than replaced -- must fall to the value
9893
+ * scan, which rewrites just the secret substring. Substituting the whole leaf
9894
+ * there would re-introduce every other token in it, including a public ssm
9895
+ * reference the resolver deliberately left resolved (issue #1901).
9896
+ */
9897
+ function isSingleDynamicReferenceToken(value) {
9898
+ return /^\{\{resolve:[^{}]*\}\}$/.test(value);
9899
+ }
9900
+ function isPlainObject$2(value) {
9901
+ return value !== null && typeof value === "object" && !Array.isArray(value);
9902
+ }
9903
+ /**
9904
+ * PATH-based redaction: walk `bag` alongside a SOURCE bag that still carries the
9905
+ * unresolved `{{resolve:...}}` expressions, and wherever the source leaf is such
9906
+ * a string, persist THAT string verbatim.
9907
+ *
9908
+ * This exists because value-keyed redaction cannot answer the question at all
9909
+ * when two expressions share one resolved value (issue #1904): the map is keyed
9910
+ * by the plaintext, so the two collapse and every site is rewritten to whichever
9911
+ * expression was recorded last — state then holds an expression the template
9912
+ * does not have at that leaf, and the stack takes a permanent spurious UPDATE.
9913
+ * Position is the only disambiguator, and the source bag supplies it.
9914
+ *
9915
+ * It also covers the case where there is no secrets map to consult at all
9916
+ * (issue #1900): an UNCHANGED resource is never resolved during a deploy, so its
9917
+ * `perResourceSecrets` entry is empty, and a live readback that echoes a secret
9918
+ * would be persisted in plaintext. Projecting from the resource's OWN state
9919
+ * record — which already holds the expressions — redacts it with no secret
9920
+ * fetch and no value matching.
9921
+ *
9922
+ * The value scan is still applied wherever the source cannot answer: a source
9923
+ * leaf that is an intrinsic OBJECT (`Fn::Join` / `Fn::Sub`) resolves to a string
9924
+ * embedding the secret, and only a value match can find it inside. So the two
9925
+ * passes are complementary rather than alternatives — path where position is
9926
+ * knowable, value where it is not.
9927
+ */
9928
+ function redactByPath(bag, source, secrets, rules, secretExpressions) {
9929
+ if (isDynamicReferenceString(source) && typeof bag === "string") {
9930
+ if (isSingleDynamicReferenceToken(source) && (rules.trustAnyExpression || isKnownSecretExpression(source, secretExpressions))) return source;
9931
+ return redactSecretsForState(bag, secrets);
9932
+ }
9933
+ if (rules.descendArrays && Array.isArray(bag) && Array.isArray(source) && bag.length === source.length) return bag.map((item, i) => redactByPath(item, source[i], secrets, rules, secretExpressions));
9934
+ if (isPlainObject$2(bag) && isPlainObject$2(source)) {
9935
+ const out = {};
9936
+ for (const [k, v] of Object.entries(bag)) out[k] = Object.hasOwn(source, k) ? redactByPath(v, source[k], secrets, rules, secretExpressions) : redactSecretsForState(v, secrets);
9937
+ return out;
9938
+ }
9939
+ return redactSecretsForState(bag, secrets);
9940
+ }
9845
9941
  /**
9846
9942
  * Deep-clone `bag`, replacing every occurrence of a recorded secret value with
9847
9943
  * the unresolved `{{resolve:...}}` expression it came from. A string whose WHOLE
@@ -9851,8 +9947,9 @@ function buildNeedleRegex(values) {
9851
9947
  * is nothing to redact, so callers can persist the original object unchanged in
9852
9948
  * the common no-secret case.
9853
9949
  */
9854
- function redactSecretsForState(bag, secrets) {
9855
- if (secrets.size === 0) return bag;
9950
+ function redactSecretsForState(bag, secrets, source, rules = TEMPLATE_DERIVED_RULES) {
9951
+ if (secrets.size === 0 && source === void 0) return bag;
9952
+ if (source !== void 0) return redactByPath(bag, source, secrets, rules, new Set(secrets.values()));
9856
9953
  const regex = buildNeedleRegex(secrets.keys());
9857
9954
  const wholeValueExpr = (s) => s === "" ? void 0 : secrets.get(s);
9858
9955
  const walk = (value) => {
@@ -9883,12 +9980,12 @@ function redactSecretsForState(bag, secrets) {
9883
9980
  * detect a no-op cheaply. Shared by the deploy engine's save choke point and
9884
9981
  * the `cdkd scrub` command so both scrub the same three fields identically.
9885
9982
  */
9886
- function scrubResourceRecord(record, secrets) {
9887
- if (secrets.size === 0) return record;
9983
+ function scrubResourceRecord(record, secrets, sourceProperties) {
9984
+ if (secrets.size === 0 && sourceProperties === void 0 && !record.observedProperties) return record;
9888
9985
  const next = { ...record };
9889
- next.properties = redactSecretsForState(record.properties, secrets);
9986
+ next.properties = redactSecretsForState(record.properties, secrets, sourceProperties);
9890
9987
  if (record.attributes) next.attributes = redactSecretsForState(record.attributes, secrets);
9891
- if (record.observedProperties) next.observedProperties = redactSecretsForState(record.observedProperties, secrets);
9988
+ if (record.observedProperties) next.observedProperties = redactSecretsForState(record.observedProperties, secrets, sourceProperties ?? next.properties, sourceProperties === void 0 ? STATE_SOURCED_READBACK_RULES : TEMPLATE_SOURCED_READBACK_RULES);
9892
9989
  return next;
9893
9990
  }
9894
9991
  /**
@@ -14409,7 +14506,7 @@ var CloudControlProvider = class {
14409
14506
  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);
14410
14507
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
14411
14508
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
14412
- const { ASGProvider } = await import("./asg-provider-BKIOy3_q.js").then((n) => n.n);
14509
+ const { ASGProvider } = await import("./asg-provider-C9Mq493c.js").then((n) => n.n);
14413
14510
  return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
14414
14511
  }
14415
14512
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
@@ -21596,7 +21693,7 @@ const FLUSH_INTERVAL_MS = 2e3;
21596
21693
  const FLUSH_EVENT_THRESHOLD = 50;
21597
21694
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
21598
21695
  function getCdkdVersion() {
21599
- return "0.283.23";
21696
+ return "0.283.24";
21600
21697
  }
21601
21698
  /**
21602
21699
  * Generate a time-sortable unique run id, e.g.
@@ -22210,6 +22307,17 @@ var DeployEngine = class {
22210
22307
  */
22211
22308
  perResourceSecrets = /* @__PURE__ */ new Map();
22212
22309
  /**
22310
+ * PER-RESOURCE unresolved TEMPLATE properties, keyed by logicalId (issues
22311
+ * #1904 / #1900). The redaction choke point uses this as the POSITION source:
22312
+ * wherever the template leaf is a `{{resolve:...}}` string, state persists
22313
+ * that string verbatim instead of asking the value-keyed map which expression
22314
+ * a plaintext came from — a question that map cannot answer when two
22315
+ * expressions resolve to the same value. Captured at the same two sites that
22316
+ * populate `perResourceSecrets`, where the unresolved bag is already in hand.
22317
+ * Reset per `deploy()`.
22318
+ */
22319
+ perResourceTemplateProps = /* @__PURE__ */ new Map();
22320
+ /**
22213
22321
  * Resolved secrets recorded while resolving the stack OUTPUTS (a `CfnOutput`
22214
22322
  * whose Value resolves a `{{resolve:...}}` reference). Separate from the
22215
22323
  * per-resource maps for the same anti-cross-contamination reason. Reset per
@@ -22258,6 +22366,7 @@ var DeployEngine = class {
22258
22366
  this.recordedImports = [];
22259
22367
  this.recordedOutputReads = [];
22260
22368
  this.perResourceSecrets = /* @__PURE__ */ new Map();
22369
+ this.perResourceTemplateProps = /* @__PURE__ */ new Map();
22261
22370
  this.outputSecrets = /* @__PURE__ */ new Map();
22262
22371
  this.resolver.resetPhysicalIdFallbackCount();
22263
22372
  return withStackName(stackName, () => this.doDeploy(stackName, template));
@@ -22329,7 +22438,8 @@ var DeployEngine = class {
22329
22438
  const resources = {};
22330
22439
  for (const [logicalId, record] of Object.entries(state.resources)) {
22331
22440
  const secrets = this.perResourceSecrets.get(logicalId);
22332
- resources[logicalId] = secrets ? scrubResourceRecord(record, secrets) : record;
22441
+ const templateProps = this.perResourceTemplateProps.get(logicalId);
22442
+ resources[logicalId] = scrubResourceRecord(record, secrets ?? /* @__PURE__ */ new Map(), templateProps);
22333
22443
  }
22334
22444
  return {
22335
22445
  ...state,
@@ -23343,6 +23453,7 @@ var DeployEngine = class {
23343
23453
  ...conditions && { conditions }
23344
23454
  }, stackName);
23345
23455
  const resolvedProps = await this.resolver.resolve(desiredProps, context);
23456
+ this.perResourceTemplateProps.set(logicalId, desiredProps);
23346
23457
  if (context.recordedSecretValues) this.perResourceSecrets.set(logicalId, context.recordedSecretValues);
23347
23458
  this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
23348
23459
  this.attemptedResolvedProps.set(logicalId, resolvedProps);
@@ -23387,6 +23498,7 @@ var DeployEngine = class {
23387
23498
  const resolvedProps = await this.resolver.resolve(desiredProps, context);
23388
23499
  const updateSecrets = context.recordedSecretValues ?? /* @__PURE__ */ new Map();
23389
23500
  this.perResourceSecrets.set(logicalId, updateSecrets);
23501
+ this.perResourceTemplateProps.set(logicalId, desiredProps);
23390
23502
  this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
23391
23503
  this.attemptedResolvedProps.set(logicalId, resolvedProps);
23392
23504
  if (JSON.stringify(this.redactPropsWith(resolvedProps, updateSecrets)) === JSON.stringify(currentProps)) {
@@ -23931,4 +24043,4 @@ var DeployEngine = class {
23931
24043
 
23932
24044
  //#endregion
23933
24045
  export { coerceCfnBoolean as $, resolveCaptureObservedState as $t, cyan as A, LocalStartServiceError as An, rewriteTemplateAssetReferences as At, findSilentDropProperties as B, StateError as Bn, buildDockerImage as Bt, makeCanonicalizePropertiesFn as C, AssetError as Cn, shouldRetainResource as Ct, renderStatefulReason as D, DeployCancelledError as Dn, buildAssetRedirectMap as Dt, isStatefulRecreateTargetSync as E, DependencyError as En, WorkGraph as Et, IAMRoleProvider as F, ProvisioningError as Fn, getBootstrapMarkerKey as Ft, IntrinsicFunctionResolver as G, withErrorHandling as Gn, AssetManifestLoader as Gt, slowCcOperationTimeoutMs as H, formatError as Hn, getDockerCmd as Ht, collectInlinePolicyNamesManagedBySiblings as I, ResourceTimeoutError as In, parseBootstrapMarker as It, refStateLookupFromResource as J, isThrottlingError as Jn, synthesisStatusMessage as Jt, cfnRefValueFromPhysicalId as K, isMarkedNonRetryable as Kn, getDockerImageBySourceHash as Kt, clearOnUpdateRemoval as L, ResourceUpdateNotSupportedError as Ln, validateAssetBucketName as Lt, green as M, MissingCdkCliError as Mn, AssetModeResolver as Mt, red as N, NestedStackChildDirectDestroyError as Nn, BOOTSTRAP_MARKER_PREFIX as Nt, formatResourceLine as O, LocalInvokeBuildError as On, createAssetRedirectResolver as Ot, yellow as P, PartialFailureError as Pn, ensureAssetStorage as Pt, assertRegionMatch as Q, resolveAutoAssetStorage as Qt, ProviderRegistry as R, StackHasActiveImportsError as Rn, validateContainerRepoName as Rt, unsupportedFinalSnapshotError as S, setAwsClients as Sn, rebuildClientForBucketRegion as St, MULTI_REGION_RECREATE_BLOCKED_TYPES as T, ConfigError as Tn, stringifyValue as Tt, disableInstanceApiTermination as U, isCdkdError as Un, runDockerForeground as Ut, CloudControlProvider as V, SynthesisError as Vn, formatDockerLoginError as Vt, isTerminationProtectionPropagationError as W, normalizeAwsError as Wn, runDockerStreaming as Wt, normalizeAwsTagsToCfn as X, __exportAll as Xn, getLegacyStateBucketName as Xt, WAFv2WebACLProvider as Y, markNonRetryable as Yn, getDefaultStateBucketName as Yt, resolveExplicitPhysicalId as Z, resolveApp as Zt, buildFinalSnapshotIdentifier as _, clearBucketRegionCache as _n, withRetry as _t, DeploymentEventsStore as a, warnDeprecatedNoPrefixCliFlag as an, requireConfigObject as at, isFinalSnapshotError as b, getAwsClients as bn, LockManager as bt, replayFailedOperations as c, MIGRATE_TMP_PREFIX as cn, scrubResourceRecord as ct, deleteSkipReason as d, expectedOwnerParam as dn, s3BucketDualStackDomainName as dt, resolveSkipPrefix as en, configBooleanRefusal as et, withResourceDeadline as f, PARTITION_TABLE as fn, s3BucketRegionalDomainName as ft, PRE_DELETE_SNAPSHOT_TYPES as g, processStackMessages as gn, describeTypeWithThrottleRetry as gt, ATOMIC_FINAL_SNAPSHOT_TYPES as h, AssemblyReader as hn, DiffCalculator as ht, DeploymentEventsReader as i, stateBucketExistenceConfirmed as in, requireConfigArray as it, gray as j, LockError as jn, escapeRegExp$1 as jt, bold as k, LocalMigrateError as kn, loadPublishableAssetManifest as kt, replayRollback as l, findLargeInlineResources as ln, s3BucketArn as lt, computeImplicitDeleteEdges as m, derivePartitionAndUrlSuffix as mn, applyRoleArnIfSet as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, resolveStateBucketWithDefaultAndSource as nn, readConfigString as nt, planFailedOps as o, CFN_TEMPLATE_BODY_LIMIT as on, requireConfigString as ot, IMPLICIT_DELETE_DEPENDENCIES as p, canonicalizeRegion as pn, s3BucketWebsiteUrl as pt, getAccountInfo as q, isRetryableTransientError as qn, Synthesizer as qt, DeployEngine as r, resolveUseCdkBootstrapAssets as rn, replayWarn as rt, planRollback as s, CFN_TEMPLATE_URL_LIMIT as sn, redactSecretsForState as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, resolveStateBucketWithDefault as tn, configStringRefusal as tt, UNSPECIFIED_SKIP_REASON as u, uploadCfnTemplate as un, s3BucketDomainName as ut, ccRoutedFinalSnapshotError as v, resolveBucketRegion as vn, DagBuilder as vt, extractDeploymentEventError as w, CdkdError as wn, AssetPublisher as wt, refusesFinalSnapshot as x, resetAwsClients as xn, S3StateBackend as xt, createPreDeleteFinalSnapshot as y, AwsClients as yn, TemplateParser as yt, findActionableSilentDrops as z, StackTerminationProtectionError as zn, buildDenyExternalAccessPolicy as zt };
23934
- //# sourceMappingURL=deploy-engine-CQXpKq7o.js.map
24046
+ //# sourceMappingURL=deploy-engine-Cu6OO-8t.js.map