@go-to-k/cdkd 0.284.7 → 0.284.8

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.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as IntrinsicFunctionResolver, $n as StackHasActiveImportsError, $t as validateContainerRepoName, A as formatResourceLine, An as AssemblyReader, At as describeTypeWithThrottleRetry, B as isExportAliasCollision, Bt as WorkGraph, C as refusesFinalSnapshot, Cn as MIGRATE_TMP_PREFIX, Ct as s3BucketDomainName, D as MULTI_REGION_RECREATE_BLOCKED_TYPES, Dn as PARTITION_TABLE, Dt as applyRoleArnIfSet, E as extractDeploymentEventError, En as expectedOwnerParam, Et as s3BucketWebsiteUrl, F as red, Fn as getAwsClients, Ft as S3StateBackend, G as clearOnUpdateRemoval, Gn as LocalStartServiceError, Gt as escapeRegExp, H as stateKeySecretExposure, Hn as DeployCancelledError, Ht as createAssetRedirectResolver, I as yellow, In as resetAwsClients, It as rebuildClientForBucketRegion, J as findSilentDropProperties, Jn as NestedStackChildDirectDestroyError, Jt as BOOTSTRAP_MARKER_PREFIX, K as ProviderRegistry, Kt as stripControlChars, L as collectDeclaredOutputNames, Ln as setAwsClients, Lt as shouldRetainResource, M as cyan, Mt as DagBuilder, N as gray, Nn as resolveBucketRegion, Nt as TemplateParser, O as isStatefulRecreateTargetSync, On as canonicalizeRegion, Ot as DiffCalculator, P as green, Pn as AwsClients, Pt as LockManager, Q as isTerminationProtectionPropagationError, Qn as ResourceUpdateNotSupportedError, Qt as validateAssetBucketName, R as collectPublishedOutputNames, Rt as AssetPublisher, S as isFinalSnapshotError, Sn as CFN_TEMPLATE_URL_LIMIT, St as s3BucketArn, T as makeCanonicalizePropertiesFn, Tn as uploadCfnTemplate, Tt as s3BucketRegionalDomainName, U as IAMRoleProvider, Un as LocalInvokeBuildError$1, Ut as loadPublishableAssetManifest, V as secretBearingStateKeyWarning, Vt as buildAssetRedirectMap, W as collectInlinePolicyNamesManagedBySiblings, Wn as LocalMigrateError, Wt as rewriteTemplateAssetReferences, X as slowCcOperationTimeoutMs, Xn as ProvisioningError, Xt as getBootstrapMarkerKey, Y as CloudControlProvider, Yn as PartialFailureError, Yt as ensureAssetStorage, Z as disableInstanceApiTermination, Zn as ResourceTimeoutError, Zt as parseBootstrapMarker, _ as ATOMIC_FINAL_SNAPSHOT_TYPES, _n as resolveStateBucketWithDefaultAndSource, _t as TEMPLATE_SOURCED_RULES, a as DeploymentEventsStore, an as runDockerStreaming, ar as normalizeAwsError, at as resolveExplicitPhysicalId, b as ccRoutedFinalSnapshotError, bn as warnDeprecatedNoPrefixCliFlag, bt as redactSecretsForState, c as replayFailedOperations, cn as Synthesizer, cr as isRetryableTransientError, ct as configBooleanRefusal, d as updatePartialReason, dn as getLegacyStateBucketName, dt as replayWarn, en as buildDenyExternalAccessPolicy, er as StackTerminationProtectionError, et as cfnRefValueFromPhysicalId, f as UNSPECIFIED_SKIP_REASON, fn as resolveApp, ft as requireConfigArray, g as computeImplicitDeleteEdges, gn as resolveStateBucketWithDefault, gt as STATE_SOURCED_READBACK_RULES, h as IMPLICIT_DELETE_DEPENDENCIES, hn as resolveSkipPrefix, ht as STATE_SOURCED_CROSS_GENERATION_RULES, i as DeploymentEventsReader, in as runDockerForeground, it as normalizeAwsTagsToCfn, j as bold, jn as processStackMessages, jt as withRetry, k as renderStatefulReason, kn as derivePartitionAndUrlSuffix, kt as INTRINSIC_KEYS, l as replayRollback, ln as synthesisStatusMessage, lr as isThrottlingError, lt as configStringRefusal, m as withResourceDeadline, mn as resolveCaptureObservedState, mt as requireConfigString, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as formatDockerLoginError, nt as refStateLookupFromResource, o as planFailedOps, on as AssetManifestLoader, or as withErrorHandling, ot as assertRegionMatch, p as deleteSkipReason, pn as resolveAutoAssetStorage, pt as requireConfigObject, q as findActionableSilentDrops, qn as MissingCdkCliError, qt as AssetModeResolver, r as DeployEngine, rn as getDockerCmd, rt as WAFv2WebACLProvider, s as planRollback, sn as getDockerImageBySourceHash, sr as isMarkedNonRetryable, st as coerceCfnBoolean, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as buildDockerImage, tt as getAccountInfo, u as updatePartialMessage, un as getDefaultStateBucketName, ur as markNonRetryable, ut as readConfigString, v as PRE_DELETE_SNAPSHOT_TYPES, vn as resolveUseCdkBootstrapAssets, vt as createSecretMasker, w as unsupportedFinalSnapshotError, wn as findLargeInlineResources, wt as s3BucketDualStackDomainName, x as createPreDeleteFinalSnapshot, xn as CFN_TEMPLATE_BODY_LIMIT, xt as scrubResourceRecord, y as buildFinalSnapshotIdentifier, yn as stateBucketExistenceConfirmed, yt as maskSecretsInText, z as exportAliasCollisionScrubWarning, zn as CdkdError, zt as stringifyValue } from "./deploy-engine-CJncZQEi.js";
2
+ import { $ as IntrinsicFunctionResolver, $n as StackHasActiveImportsError, $t as validateContainerRepoName, A as formatResourceLine, An as AssemblyReader, At as describeTypeWithThrottleRetry, B as isExportAliasCollision, Bt as WorkGraph, C as refusesFinalSnapshot, Cn as MIGRATE_TMP_PREFIX, Ct as s3BucketDomainName, D as MULTI_REGION_RECREATE_BLOCKED_TYPES, Dn as PARTITION_TABLE, Dt as applyRoleArnIfSet, E as extractDeploymentEventError, En as expectedOwnerParam, Et as s3BucketWebsiteUrl, F as red, Fn as getAwsClients, Ft as S3StateBackend, G as clearOnUpdateRemoval, Gn as LocalStartServiceError, Gt as escapeRegExp, H as stateKeySecretExposure, Hn as DeployCancelledError, Ht as createAssetRedirectResolver, I as yellow, In as resetAwsClients, It as rebuildClientForBucketRegion, J as findSilentDropProperties, Jn as NestedStackChildDirectDestroyError, Jt as BOOTSTRAP_MARKER_PREFIX, K as ProviderRegistry, Kt as stripControlChars, L as collectDeclaredOutputNames, Ln as setAwsClients, Lt as shouldRetainResource, M as cyan, Mt as DagBuilder, N as gray, Nn as resolveBucketRegion, Nt as TemplateParser, O as isStatefulRecreateTargetSync, On as canonicalizeRegion, Ot as DiffCalculator, P as green, Pn as AwsClients, Pt as LockManager, Q as isTerminationProtectionPropagationError, Qn as ResourceUpdateNotSupportedError, Qt as validateAssetBucketName, R as collectPublishedOutputNames, Rt as AssetPublisher, S as isFinalSnapshotError, Sn as CFN_TEMPLATE_URL_LIMIT, St as s3BucketArn, T as makeCanonicalizePropertiesFn, Tn as uploadCfnTemplate, Tt as s3BucketRegionalDomainName, U as IAMRoleProvider, Un as LocalInvokeBuildError$1, Ut as loadPublishableAssetManifest, V as secretBearingStateKeyWarning, Vt as buildAssetRedirectMap, W as collectInlinePolicyNamesManagedBySiblings, Wn as LocalMigrateError, Wt as rewriteTemplateAssetReferences, X as slowCcOperationTimeoutMs, Xn as ProvisioningError, Xt as getBootstrapMarkerKey, Y as CloudControlProvider, Yn as PartialFailureError, Yt as ensureAssetStorage, Z as disableInstanceApiTermination, Zn as ResourceTimeoutError, Zt as parseBootstrapMarker, _ as ATOMIC_FINAL_SNAPSHOT_TYPES, _n as resolveStateBucketWithDefaultAndSource, _t as TEMPLATE_SOURCED_RULES, a as DeploymentEventsStore, an as runDockerStreaming, ar as normalizeAwsError, at as resolveExplicitPhysicalId, b as ccRoutedFinalSnapshotError, bn as warnDeprecatedNoPrefixCliFlag, bt as redactSecretsForState, c as replayFailedOperations, cn as Synthesizer, cr as isRetryableTransientError, ct as configBooleanRefusal, d as updatePartialReason, dn as getLegacyStateBucketName, dt as replayWarn, en as buildDenyExternalAccessPolicy, er as StackTerminationProtectionError, et as cfnRefValueFromPhysicalId, f as UNSPECIFIED_SKIP_REASON, fn as resolveApp, ft as requireConfigArray, g as computeImplicitDeleteEdges, gn as resolveStateBucketWithDefault, gt as STATE_SOURCED_READBACK_RULES, h as IMPLICIT_DELETE_DEPENDENCIES, hn as resolveSkipPrefix, ht as STATE_SOURCED_CROSS_GENERATION_RULES, i as DeploymentEventsReader, in as runDockerForeground, it as normalizeAwsTagsToCfn, j as bold, jn as processStackMessages, jt as withRetry, k as renderStatefulReason, kn as derivePartitionAndUrlSuffix, kt as INTRINSIC_KEYS, l as replayRollback, ln as synthesisStatusMessage, lr as isThrottlingError, lt as configStringRefusal, m as withResourceDeadline, mn as resolveCaptureObservedState, mt as requireConfigString, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as formatDockerLoginError, nt as refStateLookupFromResource, o as planFailedOps, on as AssetManifestLoader, or as withErrorHandling, ot as assertRegionMatch, p as deleteSkipReason, pn as resolveAutoAssetStorage, pt as requireConfigObject, q as findActionableSilentDrops, qn as MissingCdkCliError, qt as AssetModeResolver, r as DeployEngine, rn as getDockerCmd, rt as WAFv2WebACLProvider, s as planRollback, sn as getDockerImageBySourceHash, sr as isMarkedNonRetryable, st as coerceCfnBoolean, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as buildDockerImage, tt as getAccountInfo, u as updatePartialMessage, un as getDefaultStateBucketName, ur as markNonRetryable, ut as readConfigString, v as PRE_DELETE_SNAPSHOT_TYPES, vn as resolveUseCdkBootstrapAssets, vt as createSecretMasker, w as unsupportedFinalSnapshotError, wn as findLargeInlineResources, wt as s3BucketDualStackDomainName, x as createPreDeleteFinalSnapshot, xn as CFN_TEMPLATE_BODY_LIMIT, xt as scrubResourceRecord, y as buildFinalSnapshotIdentifier, yn as stateBucketExistenceConfirmed, yt as maskSecretsInText, z as exportAliasCollisionScrubWarning, zn as CdkdError, zt as stringifyValue } from "./deploy-engine-36yWG1OT.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
4
- import { t as ASGProvider } from "./asg-provider-DOdJgPLS.js";
4
+ import { t as ASGProvider } from "./asg-provider-jYyUCsx_.js";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -58723,6 +58723,18 @@ function containsSecretDynamicReference(value) {
58723
58723
  if (value !== null && typeof value === "object") return Object.values(value).some(containsSecretDynamicReference);
58724
58724
  return false;
58725
58725
  }
58726
+ /**
58727
+ * Does this template carry a secret-bearing dynamic reference ANYWHERE?
58728
+ *
58729
+ * Exported for the DELETED nested-child path (issue #1948 review): that node
58730
+ * diffs its state against an EMPTY template, so it can compute nothing about
58731
+ * secrets from its own input and would render a pre-GHSA child's whole stored
58732
+ * bag in full. The caller passes the PARENT's answer instead — see
58733
+ * `diff-recursive.ts`'s `buildDeletedSubtree`.
58734
+ */
58735
+ function templateHasSecretDynamicReference(template) {
58736
+ return containsSecretDynamicReference(template);
58737
+ }
58726
58738
  /** Structural equality, mirroring the deploy engine's `outputMapsEqual` leaf rule. */
58727
58739
  function valuesEqual(a, b) {
58728
58740
  if (a === b) return true;
@@ -58772,13 +58784,21 @@ function valuesEqual(a, b) {
58772
58784
  * resolver runs with `skipDynamicReferences`, so a `{{resolve:...}}` reference
58773
58785
  * is never resolved to plaintext in the first place and the bag already holds
58774
58786
  * the expression that state stores post-redaction.
58787
+ *
58788
+ * `storedOutputs` is the bag currently in state. It is consulted for exactly
58789
+ * one decision — the LITERAL `Export.Name` of a secret-bearing stack, see issue
58790
+ * #1942 at that branch — and never merged into the result: everything else here
58791
+ * previews what the template says, and reading state anywhere else would make
58792
+ * the preview agree with the past instead of with the next deploy.
58775
58793
  */
58776
- async function resolveTemplateOutputs(template, resolveFn, conditions) {
58794
+ async function resolveTemplateOutputs(template, resolveFn, conditions, storedOutputs) {
58777
58795
  const logger = getLogger().child("OutputsDiff");
58778
- const outputs = {};
58796
+ const outputs = Object.create(null);
58779
58797
  const exportNames = /* @__PURE__ */ new Set();
58780
58798
  const failedKeys = /* @__PURE__ */ new Set();
58781
58799
  const secretSourceKeys = /* @__PURE__ */ new Set();
58800
+ const declaredKeys = /* @__PURE__ */ new Set();
58801
+ const templateHasSecretReference = containsSecretDynamicReference(template);
58782
58802
  let resolutionFailed = false;
58783
58803
  const publishedOutputNames = collectPublishedOutputNames(template.Outputs ?? {}, conditions);
58784
58804
  if (!template.Outputs) return {
@@ -58786,6 +58806,8 @@ async function resolveTemplateOutputs(template, resolveFn, conditions) {
58786
58806
  exportNames,
58787
58807
  failedKeys,
58788
58808
  secretSourceKeys,
58809
+ declaredKeys,
58810
+ templateHasSecretReference,
58789
58811
  resolutionFailed
58790
58812
  };
58791
58813
  /**
@@ -58799,6 +58821,8 @@ async function resolveTemplateOutputs(template, resolveFn, conditions) {
58799
58821
  if (typeof output.Export?.Name === "string") failedKeys.add(output.Export.Name);
58800
58822
  };
58801
58823
  for (const [outputKey, output] of Object.entries(template.Outputs)) {
58824
+ declaredKeys.add(outputKey);
58825
+ if (typeof output.Export?.Name === "string") declaredKeys.add(output.Export.Name);
58802
58826
  if (!containsSecretDynamicReference(output.Value)) continue;
58803
58827
  secretSourceKeys.add(outputKey);
58804
58828
  if (typeof output.Export?.Name === "string") secretSourceKeys.add(output.Export.Name);
@@ -58838,11 +58862,15 @@ async function resolveTemplateOutputs(template, resolveFn, conditions) {
58838
58862
  }
58839
58863
  if (typeof exportName === "string" && !isUnresolvedValue(exportName, exportSourceUsedSub)) if (declaredExportIsIntrinsic && isSecretDynamicReference(exportName)) logger.debug(`Diff skipping export alias of ${stripControlChars(outputKey)} — the name carries a secret reference`);
58840
58864
  else if (isExportAliasCollision(exportName, outputKey, publishedOutputNames)) logger.debug(`Diff skipping export alias ${stripControlChars(exportName)} of ${stripControlChars(outputKey)} — collides with an output name`);
58841
- else if (!declaredExportIsIntrinsic && secretSourceKeys.size > 0) {
58842
- logger.debug(`Diff cannot decide the export alias of ${stripControlChars(outputKey)} — a literal name may contain a resolved secret`);
58865
+ else if (!declaredExportIsIntrinsic && secretSourceKeys.size > 0) if (storedOutputs !== void 0 && Object.prototype.hasOwnProperty.call(storedOutputs, exportName)) {
58866
+ outputs[exportName] = value;
58867
+ exportNames.add(exportName);
58868
+ } else {
58869
+ logger.debug(`Diff cannot decide the export alias of ${stripControlChars(outputKey)} — a literal name may contain a resolved secret and state does not hold that key`);
58843
58870
  failedKeys.add(exportName);
58844
58871
  resolutionFailed = true;
58845
- } else {
58872
+ }
58873
+ else {
58846
58874
  outputs[exportName] = value;
58847
58875
  exportNames.add(exportName);
58848
58876
  }
@@ -58854,6 +58882,8 @@ async function resolveTemplateOutputs(template, resolveFn, conditions) {
58854
58882
  exportNames,
58855
58883
  failedKeys,
58856
58884
  secretSourceKeys,
58885
+ declaredKeys,
58886
+ templateHasSecretReference,
58857
58887
  resolutionFailed
58858
58888
  };
58859
58889
  }
@@ -58883,24 +58913,73 @@ async function resolveTemplateOutputs(template, resolveFn, conditions) {
58883
58913
  * {@link isSecretDynamicReference} (this resolver runs with
58884
58914
  * `skipDynamicReferences`) while the stored side is not; and
58885
58915
  * - `secretSourceKeys` — the template itself declares the key's value as such a
58886
- * reference. This one reaches cases the first cannot: a
58887
- * condition-skipped secret output, or one deleted from the template, has NO
58888
- * desired side at all and would otherwise print in full as a REMOVE row.
58916
+ * reference. This one reaches a case the first cannot: a condition-skipped
58917
+ * secret output has NO desired side at all and would otherwise print in full
58918
+ * as a REMOVE row.
58889
58919
  *
58890
58920
  * A hit on either makes the WHOLE record suspect, not just that key: the
58891
58921
  * record was written by a pre-GHSA binary, so every value in it is unredacted.
58892
58922
  * Withholding is therefore record-level. The change is still REPORTED — only
58893
58923
  * the value is withheld — so `--fail` and the exports story are unaffected.
58894
- */
58895
- function computeOutputsDiff(current, desired, exportNames, secretSourceKeys = /* @__PURE__ */ new Set()) {
58924
+ *
58925
+ * ## The key today's template cannot account for (issue #1948)
58926
+ *
58927
+ * Neither signal above covers an output DELETED from the template, and an
58928
+ * earlier revision of this note claimed `secretSourceKeys` did — a false
58929
+ * coverage claim, which is exactly what lets a reader conclude the case is
58930
+ * handled. It structurally cannot: that set is built from DECLARATIONS, and a
58931
+ * deleted output declares nothing, so a record whose ONLY secret-bearing output
58932
+ * has since been removed is judged not-legacy and its stored pre-GHSA plaintext
58933
+ * renders as the `old:` side of a REMOVE row.
58934
+ *
58935
+ * The signal has to come from the STORED bag, and the honest statement about
58936
+ * the stored bag is that the case is UNDECIDABLE there: post-GHSA a secret
58937
+ * output stores its `{{resolve:...}}` EXPRESSION, but pre-GHSA it stores a
58938
+ * plaintext, and a plaintext is indistinguishable from an ordinary string. So
58939
+ * this is a REFUSAL rather than a detection — a stored key the template cannot
58940
+ * account for has its value withheld — bounded by two gates that keep it off
58941
+ * the common benign case (deleting an output is a normal refactor):
58942
+ *
58943
+ * - `templateHasSecretReference` — the template must still prove this stack
58944
+ * handles secrets AT ALL (anywhere, `Resources` included). An ordinary stack
58945
+ * with no secret reference keeps printing its REMOVE values.
58946
+ * - no stored value is itself a secret-bearing expression. One that is proves
58947
+ * the LAST write was post-GHSA, and the bag is rewritten wholesale by
58948
+ * `resolveOutputs` on every deploy, so every other value in it is redacted
58949
+ * too. This exonerates the record.
58950
+ *
58951
+ * That reasoning holds for a DEPLOY write and is weaker for a `cdkd scrub`
58952
+ * one, which rewrites `state.outputs` IN PLACE: scrub redacts what it has a
58953
+ * needle or a template position for, and its own docs admit it can leave a
58954
+ * plaintext it has neither for — so a scrubbed bag holding one redacted key
58955
+ * can exonerate a deleted key's surviving plaintext. Recorded rather than
58956
+ * closed: removing the exoneration would withhold values on every clean
58957
+ * post-GHSA record in a secret-handling stack, which is the larger harm.
58958
+ *
58959
+ * Withholding here is PER-KEY, unlike the record-level arms above, and the
58960
+ * asymmetry follows from what each concludes: those conclude the record was
58961
+ * written by a pre-GHSA binary (a claim about the whole bag), while this one
58962
+ * concludes only that THIS key is undecidable. So a secret-handling stack that
58963
+ * deletes an ordinary output withholds that one row's value and prints the
58964
+ * rest.
58965
+ *
58966
+ * Residual, stated because the gate is what buys the low false-positive rate:
58967
+ * a stack whose ONLY secret reference WAS the deleted output has no secret left
58968
+ * in its template, so nothing here fires. Closing it would mean withholding
58969
+ * every REMOVE value on every stack, which is a worse trade.
58970
+ */
58971
+ function computeOutputsDiff(current, desired, exportNames, secretSourceKeys = /* @__PURE__ */ new Set(), unaccountableScan = {}) {
58896
58972
  const changes = [];
58897
58973
  const currentBag = current ?? {};
58898
58974
  const legacyRecord = Object.entries(currentBag).some(([name, oldValue]) => {
58899
58975
  if (typeof oldValue === "string" && isSecretDynamicReference(oldValue)) return false;
58900
58976
  return containsSecretDynamicReference(desired[name]) || secretSourceKeys.has(name);
58901
58977
  });
58978
+ const recordProvesPostGhsa = Object.values(currentBag).some((v) => typeof v === "string" && isSecretDynamicReference(v));
58979
+ const declaredKeys = unaccountableScan.declaredKeys ?? /* @__PURE__ */ new Set();
58980
+ const unaccountable = (name) => unaccountableScan.templateHasSecretReference === true && !recordProvesPostGhsa && !declaredKeys.has(name) && !Object.prototype.hasOwnProperty.call(desired, name);
58902
58981
  const push = (change) => {
58903
- if (legacyRecord && change.changeType !== "ADD") {
58982
+ if (change.changeType !== "ADD" && (legacyRecord || unaccountable(change.name))) {
58904
58983
  const { oldValue: _dropped, ...rest } = change;
58905
58984
  changes.push({
58906
58985
  ...rest,
@@ -59911,7 +59990,8 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
59911
59990
  * Pure with respect to AWS state mutation — only reads state (the resolver
59912
59991
  * may read producer state for `Fn::ImportValue` / `Fn::GetStackOutput`).
59913
59992
  */
59914
- async function computeStackDiff(currentState, template, region, stackName, stateBackend, diffCalculator, parameters, canonicalizeProperties, cfnFallback) {
59993
+ async function computeStackDiff(currentState, template, region, stackName, stateBackend, diffCalculator, options = {}) {
59994
+ const { parameters, canonicalizeProperties, cfnFallback, inheritSecretBearingTemplate } = options;
59915
59995
  const intrinsicResolver = new IntrinsicFunctionResolver(region, { cfnFallback: cfnFallback ?? true });
59916
59996
  let mergedParameters = parameters;
59917
59997
  let parametersBound = false;
@@ -59953,11 +60033,17 @@ async function computeStackDiff(currentState, template, region, stackName, state
59953
60033
  skipDynamicReferences: true
59954
60034
  });
59955
60035
  const changes = await diffCalculator.calculateDiff(currentState, effectiveTemplate, resolveFn, canonicalizeProperties);
59956
- const resolved = await resolveTemplateOutputs(effectiveTemplate, resolveFn, conditions);
60036
+ const resolved = await resolveTemplateOutputs(effectiveTemplate, resolveFn, conditions, currentState.outputs);
59957
60037
  let outputChanges = [];
59958
60038
  if (resolved.resolutionFailed) {
59959
- if (computeOutputsDiff(currentState.outputs, resolved.outputs, resolved.exportNames, resolved.secretSourceKeys).filter((change) => !resolved.failedKeys.has(change.name)).length > 0) logger.warn(`Outputs of stack ${stackName} may have changed, but one or more could not be resolved against current state — omitting the Outputs section from this diff. It is usually an output referencing a resource this deploy has yet to create.`);
59960
- } else outputChanges = computeOutputsDiff(currentState.outputs, resolved.outputs, resolved.exportNames, resolved.secretSourceKeys);
60039
+ if (computeOutputsDiff(currentState.outputs, resolved.outputs, resolved.exportNames, resolved.secretSourceKeys, {
60040
+ declaredKeys: resolved.declaredKeys,
60041
+ templateHasSecretReference: resolved.templateHasSecretReference || inheritSecretBearingTemplate === true
60042
+ }).filter((change) => !resolved.failedKeys.has(change.name)).length > 0) logger.warn(`Outputs of stack ${stackName} may have changed, but one or more could not be resolved against current state — omitting the Outputs section from this diff. It is usually an output referencing a resource this deploy has yet to create.`);
60043
+ } else outputChanges = computeOutputsDiff(currentState.outputs, resolved.outputs, resolved.exportNames, resolved.secretSourceKeys, {
60044
+ declaredKeys: resolved.declaredKeys,
60045
+ templateHasSecretReference: resolved.templateHasSecretReference || inheritSecretBearingTemplate === true
60046
+ });
59961
60047
  return {
59962
60048
  changes,
59963
60049
  outputChanges
@@ -60022,9 +60108,15 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
60022
60108
  * fail at deploy time.
60023
60109
  */
60024
60110
  async function buildDiffTree(args) {
60025
- const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parameters, canonicalizeProperties, assetRedirect, cfnFallback } = args;
60111
+ const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parentHasSecretReference, parameters, canonicalizeProperties, assetRedirect, cfnFallback } = args;
60026
60112
  const state = await loadStateOrEmpty(stackName, region, stateBackend);
60027
- const { changes, outputChanges } = await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, parameters, canonicalizeProperties, cfnFallback);
60113
+ const secretBearingAbove = parentHasSecretReference === true || templateHasSecretDynamicReference(template);
60114
+ const { changes, outputChanges } = await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, {
60115
+ ...parameters && { parameters },
60116
+ ...canonicalizeProperties && { canonicalizeProperties },
60117
+ ...cfnFallback !== void 0 && { cfnFallback },
60118
+ inheritSecretBearingTemplate: false
60119
+ });
60028
60120
  const node = {
60029
60121
  stackName,
60030
60122
  displayName,
@@ -60058,13 +60150,14 @@ async function buildDiffTree(args) {
60058
60150
  parameters: childParameters,
60059
60151
  ...canonicalizeProperties && { canonicalizeProperties },
60060
60152
  ...assetRedirect && { assetRedirect },
60061
- ...cfnFallback !== void 0 && { cfnFallback }
60153
+ ...cfnFallback !== void 0 && { cfnFallback },
60154
+ parentHasSecretReference: secretBearingAbove
60062
60155
  }));
60063
60156
  }
60064
60157
  for (const [logicalId, resource] of Object.entries(state.resources)) {
60065
60158
  if (resource.resourceType !== "AWS::CloudFormation::Stack") continue;
60066
60159
  if (templateChildIds.has(logicalId)) continue;
60067
- node.children.push(await buildDeletedSubtree(`${stackName}~${logicalId}`, region, stateBackend, diffCalculator));
60160
+ node.children.push(await buildDeletedSubtree(`${stackName}~${logicalId}`, region, stateBackend, diffCalculator, secretBearingAbove));
60068
60161
  }
60069
60162
  return node;
60070
60163
  }
@@ -60074,9 +60167,9 @@ async function buildDiffTree(args) {
60074
60167
  * (state vs empty template), recursing into state-listed grandchildren
60075
60168
  * (also all DELETE).
60076
60169
  */
60077
- async function buildDeletedSubtree(stackName, region, stateBackend, diffCalculator) {
60170
+ async function buildDeletedSubtree(stackName, region, stateBackend, diffCalculator, parentHasSecretReference) {
60078
60171
  const state = await loadStateOrEmpty(stackName, region, stateBackend);
60079
- const { changes, outputChanges } = await computeStackDiff(state, EMPTY_TEMPLATE, region, stackName, stateBackend, diffCalculator);
60172
+ const { changes, outputChanges } = await computeStackDiff(state, EMPTY_TEMPLATE, region, stackName, stateBackend, diffCalculator, { inheritSecretBearingTemplate: parentHasSecretReference });
60080
60173
  const node = {
60081
60174
  stackName,
60082
60175
  displayName: stackName,
@@ -60088,7 +60181,7 @@ async function buildDeletedSubtree(stackName, region, stateBackend, diffCalculat
60088
60181
  };
60089
60182
  for (const [logicalId, resource] of Object.entries(state.resources)) {
60090
60183
  if (resource.resourceType !== "AWS::CloudFormation::Stack") continue;
60091
- node.children.push(await buildDeletedSubtree(`${stackName}~${logicalId}`, region, stateBackend, diffCalculator));
60184
+ node.children.push(await buildDeletedSubtree(`${stackName}~${logicalId}`, region, stateBackend, diffCalculator, parentHasSecretReference));
60092
60185
  }
60093
60186
  return node;
60094
60187
  }
@@ -60318,25 +60411,19 @@ function renderChangeLines(changes, logFn, ccApiRoutes) {
60318
60411
  delete: deleteCount
60319
60412
  };
60320
60413
  }
60321
- /** Stand-in printed instead of a withheld legacy-plaintext output value. */
60322
- const REDACTED_LEGACY_PLAINTEXT = "<redacted: legacy plaintext in state — run `cdkd scrub`>";
60323
60414
  /**
60324
- * Strip characters that let a template-controlled string manipulate the
60325
- * TERMINAL rather than merely appear in it: C0 (incl. ESC, so no ANSI sequence
60326
- * survives), DEL, C1 (the 8-bit CSI forms some terminals still honor), and the
60327
- * bidi / format overrides that visually reorder a rendered line.
60328
- *
60329
- * Applied only on the human-render path. The `--json` payload is deliberately
60330
- * left byte-faithful: it is a machine interface, an export NAME is data a
60331
- * consumer may match on, and silently mutating it there would trade a real
60332
- * correctness regression for a display concern belonging to whatever renders
60333
- * the JSON. `JSON.stringify` already escapes everything below 0x20.
60415
+ * Stand-in printed instead of a withheld stored output value.
60334
60416
  *
60335
- * The helper itself now lives in `src/utils/regexp.ts` `outputs-export-alias`
60336
- * prints the same class of string and had grown a second copy.
60417
+ * Worded as a POSSIBILITY because both reasons the value is withheld are
60418
+ * suspicions rather than detections a record that LOOKS pre-GHSA, and a
60419
+ * stored key today's template cannot account for (issue #1948), which is
60420
+ * undecidable by construction. Stating it as a fact would be wrong on the
60421
+ * benign half of each, and `cdkd scrub` finding nothing is the expected
60422
+ * outcome there.
60337
60423
  */
60424
+ const REDACTED_LEGACY_PLAINTEXT = "<redacted: may be legacy plaintext in state — run `cdkd scrub`>";
60338
60425
  /**
60339
- * The same guard for an already-JSON-SERIALIZED value: C1 and the bidi marks
60426
+ * The guard for an already-JSON-SERIALIZED value: C1 and the bidi marks
60340
60427
  * only, deliberately NOT C0.
60341
60428
  *
60342
60429
  * `JSON.stringify` escapes every character below 0x20 that occurs INSIDE a
@@ -67888,13 +67975,13 @@ async function stateResourcesCommand(stackName, options) {
67888
67975
  for (const detail of details) {
67889
67976
  lines.push(detail.logicalId);
67890
67977
  lines.push(` Type: ${detail.resourceType}`);
67891
- lines.push(` PhysicalID: ${detail.physicalId}`);
67978
+ lines.push(` PhysicalID: ${stripControlChars(detail.physicalId)}`);
67892
67979
  lines.push(` Dependencies: ${detail.dependencies.length > 0 ? detail.dependencies.join(", ") : "(none)"}`);
67893
67980
  const attrEntries = Object.entries(detail.attributes);
67894
67981
  if (attrEntries.length === 0) lines.push(" Attributes: (none)");
67895
67982
  else {
67896
67983
  lines.push(" Attributes:");
67897
- for (const [k, v] of attrEntries) lines.push(` ${k}: ${formatAttributeValue(v)}`);
67984
+ for (const [k, v] of attrEntries) lines.push(` ${stripControlChars(k)}: ${formatAttributeValue(v)}`);
67898
67985
  }
67899
67986
  lines.push("");
67900
67987
  }
@@ -67914,11 +68001,31 @@ async function stateResourcesCommand(stackName, options) {
67914
68001
  *
67915
68002
  * Scalar values render as-is; objects/arrays are JSON-encoded inline so a
67916
68003
  * resource block stays compact even when an attribute is structured.
68004
+ *
68005
+ * Control characters are stripped HERE rather than at each call site (issue
68006
+ * #1948 review), because every caller has the same provenance problem and the
68007
+ * same answer: an Outputs value, a resource PROPERTY and a provider-returned
68008
+ * ATTRIBUTE are all values cdkd resolved or read back, none of them passed a
68009
+ * CloudFormation validator, and all three land in the same terminal. Doing it
68010
+ * once means a future caller cannot forget — the half-applied version of this
68011
+ * guard is what the review caught.
68012
+ *
68013
+ * The FULL class (C0 included), unlike `diff-recursive`'s
68014
+ * `stripDisplayOnlyChars`: that narrower guard exists to protect a
68015
+ * PRETTY-PRINTED payload's structural newlines, and this function never
68016
+ * pretty-prints — `JSON.stringify` is called without an indent, so a newline
68017
+ * here is content injected by the value, not layout.
68018
+ *
68019
+ * Only the human path routes through this; `--json` serializes the state
68020
+ * record directly and stays byte-faithful, which is the same split
68021
+ * `renderOutputChangeLines` makes.
67917
68022
  */
67918
68023
  function formatAttributeValue(value) {
67919
68024
  if (value === null) return "null";
67920
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
67921
- return JSON.stringify(value);
68025
+ if (value === void 0) return "undefined";
68026
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return stripControlChars(String(value));
68027
+ const json = JSON.stringify(value);
68028
+ return json === void 0 ? "(unserializable)" : stripControlChars(json);
67922
68029
  }
67923
68030
  /**
67924
68031
  * Render a duration in milliseconds as `1m23s` / `45s`.
@@ -68020,7 +68127,7 @@ function renderStateBlock(state, lockInfo) {
68020
68127
  if (outputEntries.length > 0) {
68021
68128
  lines.push("");
68022
68129
  lines.push("Outputs:");
68023
- for (const [k, v] of outputEntries) lines.push(` ${k}: ${formatAttributeValue(v)}`);
68130
+ for (const [k, v] of outputEntries) lines.push(` ${stripControlChars(k)}: ${formatAttributeValue(v)}`);
68024
68131
  }
68025
68132
  const resourceEntries = Object.entries(state.resources ?? {}).sort(([a], [b]) => a.localeCompare(b));
68026
68133
  lines.push("");
@@ -68029,7 +68136,7 @@ function renderStateBlock(state, lockInfo) {
68029
68136
  lines.push("");
68030
68137
  lines.push(logicalId);
68031
68138
  lines.push(` Type: ${resource.resourceType}`);
68032
- lines.push(` PhysicalID: ${resource.physicalId}`);
68139
+ lines.push(` PhysicalID: ${stripControlChars(resource.physicalId)}`);
68033
68140
  const provisionedBy = resource.provisionedBy ?? "(sdk, legacy default)";
68034
68141
  lines.push(` ProvisionedBy: ${provisionedBy}`);
68035
68142
  const deps = resource.dependencies ?? [];
@@ -68038,13 +68145,13 @@ function renderStateBlock(state, lockInfo) {
68038
68145
  if (attrEntries.length === 0) lines.push(" Attributes: (none)");
68039
68146
  else {
68040
68147
  lines.push(" Attributes:");
68041
- for (const [k, v] of attrEntries) lines.push(` ${k}: ${formatAttributeValue(v)}`);
68148
+ for (const [k, v] of attrEntries) lines.push(` ${stripControlChars(k)}: ${formatAttributeValue(v)}`);
68042
68149
  }
68043
68150
  const propEntries = Object.entries(resource.properties ?? {});
68044
68151
  if (propEntries.length === 0) lines.push(" Properties: (none)");
68045
68152
  else {
68046
68153
  lines.push(" Properties:");
68047
- for (const [k, v] of propEntries) lines.push(` ${k}: ${formatAttributeValue(v)}`);
68154
+ for (const [k, v] of propEntries) lines.push(` ${stripControlChars(k)}: ${formatAttributeValue(v)}`);
68048
68155
  }
68049
68156
  }
68050
68157
  return lines;
@@ -68715,6 +68822,19 @@ async function stateRefreshObservedCommand(stackArgs, options) {
68715
68822
  }
68716
68823
  }
68717
68824
  /**
68825
+ * The secrets map every redaction on this command's paths gets, and it is
68826
+ * EMPTY by construction (issue #1926).
68827
+ *
68828
+ * `cdkd state refresh-observed` neither synthesizes a template nor resolves a
68829
+ * dynamic reference, so no plaintext is ever recorded here and a VALUE scan
68830
+ * would have no needles at all. That is the issue #1900 shape, and it is why
68831
+ * the PATH pass has to carry the redaction on its own — see the call site.
68832
+ *
68833
+ * Shared rather than constructed per resource because nothing writes to it:
68834
+ * `redactSecretsForState` only reads its argument.
68835
+ */
68836
+ const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
68837
+ /**
68718
68838
  * Refresh the `observedProperties` of every resource in one stack
68719
68839
  * record. Returns counts so the caller can aggregate across `--all`.
68720
68840
  *
@@ -68793,7 +68913,7 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
68793
68913
  unsupported++;
68794
68914
  return;
68795
68915
  }
68796
- resource.observedProperties = observed;
68916
+ resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS, resource.properties ?? {}, STATE_SOURCED_READBACK_RULES);
68797
68917
  refreshed++;
68798
68918
  } catch (err) {
68799
68919
  failed++;
@@ -68834,7 +68954,7 @@ async function confirmRefresh(prompt) {
68834
68954
  * Create the `state refresh-observed` subcommand.
68835
68955
  */
68836
68956
  function createStateRefreshObservedCommand() {
68837
- const cmd = new Command("refresh-observed").description("Refresh observedProperties for every resource in a stack by calling provider.readCurrentState — populates the drift baseline for stacks deployed before state schema v3, without redeploying.").argument("[stacks...]", "Stack name(s) to refresh (physical CloudFormation names)").option("--all", "Refresh every stack in the state bucket", false).option("--dry-run", "Print the per-stack refresh count without writing state", false).addOption(stackRegionOption$1()).action(withErrorHandling(stateRefreshObservedCommand));
68957
+ const cmd = new Command("refresh-observed").description("Refresh observedProperties for every resource in a stack by calling provider.readCurrentState — populates the drift baseline for stacks deployed before state schema v3, without redeploying. Run cdkd scrub first on state written by a pre-GHSA binary: the readback is redacted by POSITION against the record, so a record whose own properties still hold plaintext has nothing to redact from.").argument("[stacks...]", "Stack name(s) to refresh (physical CloudFormation names)").option("--all", "Refresh every stack in the state bucket", false).option("--dry-run", "Print the per-stack refresh count without writing state", false).addOption(stackRegionOption$1()).action(withErrorHandling(stateRefreshObservedCommand));
68838
68958
  [...commonOptions, ...stateOptions].forEach((opt) => cmd.addOption(opt));
68839
68959
  cmd.addOption(deprecatedRegionOption);
68840
68960
  return cmd;
@@ -82361,7 +82481,7 @@ function createMigrateCommand() {
82361
82481
  */
82362
82482
  function buildProgram() {
82363
82483
  const program = new Command();
82364
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.7");
82484
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.8");
82365
82485
  program.hook("preAction", (_thisCommand, actionCommand) => {
82366
82486
  const { profile } = actionCommand.optsWithGlobals();
82367
82487
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;