@go-to-k/cdkd 0.284.21 → 0.284.22

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.
@@ -11106,6 +11106,29 @@ function dynamicReferenceTokens(value) {
11106
11106
  return value.match(DYNAMIC_REFERENCE_TOKEN_SCAN) ?? [];
11107
11107
  }
11108
11108
  /**
11109
+ * Where each complete `{{resolve:...}}` token sits in the string, as
11110
+ * `[start, end)` offsets. The OFFSETS are what {@link dynamicReferenceTokens}
11111
+ * cannot give, and the value scan needs them to decide whether a needle match
11112
+ * lies inside a reference or merely beside one.
11113
+ *
11114
+ * `lastIndex` is reset before `matchAll`, and that is load-bearing rather than
11115
+ * defensive. `String.prototype.matchAll` does not MUTATE the pattern's
11116
+ * `lastIndex` — it clones — but it SEEDS the clone from it, so a caller that
11117
+ * left the shared constant dirty (the constant's own doc forbids `.exec` /
11118
+ * `.test` on it for exactly this reason) would make this function skip every
11119
+ * span before that offset, silently restoring the splice this offsets are used
11120
+ * to prevent. Measured, not assumed.
11121
+ */
11122
+ function dynamicReferenceSpans(value) {
11123
+ DYNAMIC_REFERENCE_TOKEN_SCAN.lastIndex = 0;
11124
+ const spans = [];
11125
+ for (const match of value.matchAll(DYNAMIC_REFERENCE_TOKEN_SCAN)) spans.push({
11126
+ start: match.index,
11127
+ end: match.index + match[0].length
11128
+ });
11129
+ return spans;
11130
+ }
11131
+ /**
11109
11132
  * Does this MIXED leaf embed a reference that may be PUBLIC config?
11110
11133
  *
11111
11134
  * A plain `{{resolve:ssm:...}}` is classified by the parameter's TYPE, not by
@@ -11283,6 +11306,92 @@ function redactSecretsForState(bag, secrets, source, rules = TEMPLATE_DERIVED_RU
11283
11306
  }
11284
11307
  const regex = buildNeedleRegex(secrets.keys());
11285
11308
  const wholeValueExpr = (s) => s === "" ? void 0 : secrets.get(s);
11309
+ /**
11310
+ * The SUBSTRING arm for a leaf the resolver substituted INTO rather than
11311
+ * replaced. ONE rule over the WHOLE leaf (issue
11312
+ * [#1935](https://github.com/go-to-k/cdkd/issues/1935)):
11313
+ *
11314
+ * > replace every recorded-plaintext match EXCEPT one that lies STRICTLY
11315
+ * > INSIDE a complete `{{resolve:...}}` span.
11316
+ *
11317
+ * "Strictly inside" means contained by a span and SHORTER than it. The four
11318
+ * positions a match can take, and why each lands where it does:
11319
+ *
11320
+ * - **strictly inside a span** -> KEPT. This is the defect: a plaintext that
11321
+ * happens to occur inside a token's own TEXT was spliced into the
11322
+ * reference. Deploy 1 persists
11323
+ * `jdbc://appdb:{{resolve:secretsmanager:appdb/creds:SecretString:password}}@host`;
11324
+ * deploy 2 records an ssm SecureString whose plaintext is `appdb`; the walk
11325
+ * wrote `{{resolve:secretsmanager:{{resolve:ssm:/app/dbname}}/creds:...}}`.
11326
+ * `resolveReplayProps` scans with `([^}]+)`, which stops at the FIRST `}`,
11327
+ * so the replay asks Secrets Manager for the secret id
11328
+ * `{{resolve:ssm:/app/dbname` — rollback blocked, or garbage applied to a
11329
+ * live resource. `cdkd scrub` writes the same wreckage into `properties`
11330
+ * and `observedProperties`.
11331
+ * - **coextensive with a span** -> REPLACED. A secret whose resolved
11332
+ * PLAINTEXT is itself a `{{resolve:...}}` string (issue #1917), embedded in
11333
+ * a larger leaf. This is why "mask only OUTSIDE the spans" is wrong on its
11334
+ * own: that plaintext IS a span, so span-skipping would stop redacting it
11335
+ * and trade a mangling bug for a disclosure.
11336
+ * - **containing or straddling a span** -> REPLACED. A recorded plaintext
11337
+ * that embeds a whole reference plus surrounding text. Nothing is spliced,
11338
+ * because the whole reference is consumed by the replacement. An earlier
11339
+ * revision of this fix expressed the rule as TWO rules — replace a span
11340
+ * that is a recorded plaintext, value-scan the text between spans — and
11341
+ * that form DROPPED this case at both ends (it is neither a whole span nor
11342
+ * contained in the text between spans), persisting the plaintext in the
11343
+ * clear where the pre-fix code had redacted it. A REGRESSION, caught by the
11344
+ * security review, and the reason the rule is one predicate over the whole
11345
+ * leaf rather than a split.
11346
+ * - **disjoint from every span** -> REPLACED. The ordinary embedded secret.
11347
+ *
11348
+ * Scanning the WHOLE leaf in ONE pass is also what preserves what needle
11349
+ * PRECEDENCE there is. {@link buildNeedleRegex} sorts alternatives
11350
+ * longest-first, which decides only between alternatives matching at the SAME
11351
+ * offset; the scan itself is LEFTMOST-first, so a shorter secret starting
11352
+ * EARLIER still wins and the tail of the longer one survives in the clear
11353
+ * (`zzABCDEFzz` with needles `ABCD` / `BCDEF` leaves `EF`). That is regex
11354
+ * semantics, identical before and after this change, and is not something
11355
+ * this rule claims to fix.
11356
+ *
11357
+ * What the single pass DOES restore is the same-offset ordering across a span
11358
+ * boundary. The two-rule form scanned each BETWEEN-span stretch separately,
11359
+ * so a long straddling needle was never even a candidate and a short one
11360
+ * starting later in the tail won by default — the leaf took the WRONG
11361
+ * expression, which the replay then re-resolves and applies (the issue #1910
11362
+ * class).
11363
+ *
11364
+ * TWO KNOWN RESIDUALS around a STRAY `{{resolve:` opener, both pinned by
11365
+ * tests rather than left as prose, and they fail in OPPOSITE directions
11366
+ * because the span grammar is greedy `[^}]+` (deliberately — it is the
11367
+ * resolver's own spelling, unified by issue #1936):
11368
+ *
11369
+ * - **no later `}}` in the leaf** -> no span, so a needle after the opener is
11370
+ * REPLACED and the result reads as a reference to a bogus secret id.
11371
+ * Identical to the pre-fix code. Refusing to redact there would leave
11372
+ * PLAINTEXT behind two characters any string can contain.
11373
+ * - **a later `}}` anywhere in the leaf** -> the opener and that `}}` form
11374
+ * ONE span swallowing everything between them, so a needle in that region
11375
+ * is KEPT — the only shape where this rule redacts LESS than the code it
11376
+ * replaced. Narrow but real, and the reachable carrier is named rather than
11377
+ * waved at: the resolver shares this grammar, so such a leaf could not have
11378
+ * resolved on the deploy path, which leaves an `observedProperties`
11379
+ * READBACK (arbitrary text from AWS) as the way one arrives.
11380
+ *
11381
+ * Narrowing the span pattern here would close the second and open two worse
11382
+ * holes: it would re-fork the one grammar issue #1936 unified, and it would
11383
+ * make an ALREADY-MANGLED legacy leaf parse differently and be spliced again,
11384
+ * contradicting this change's own "not repaired, not made worse" property.
11385
+ * So the residual is documented, not fixed.
11386
+ */
11387
+ const scanLeaf = (value, needles) => {
11388
+ const spans = isDynamicReferenceString(value) ? dynamicReferenceSpans(value) : [];
11389
+ return value.replace(needles, (match, offset) => {
11390
+ const end = offset + match.length;
11391
+ if (spans.some((span) => span.start <= offset && end <= span.end && (span.start !== offset || span.end !== end))) return match;
11392
+ return secrets.get(match) ?? "***";
11393
+ });
11394
+ };
11286
11395
  const walk = (value) => {
11287
11396
  if (typeof value === "string") {
11288
11397
  const whole = wholeValueExpr(value);
@@ -11291,8 +11400,7 @@ function redactSecretsForState(bag, secrets, source, rules = TEMPLATE_DERIVED_RU
11291
11400
  if (!regex) return value;
11292
11401
  regex.lastIndex = 0;
11293
11402
  if (!regex.test(value)) return value;
11294
- regex.lastIndex = 0;
11295
- return value.replace(regex, (m) => secrets.get(m) ?? "***");
11403
+ return scanLeaf(value, regex);
11296
11404
  }
11297
11405
  if (Array.isArray(value)) return value.map(walk);
11298
11406
  if (value !== null && typeof value === "object") {
@@ -16752,7 +16860,7 @@ var CloudControlProvider = class {
16752
16860
  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);
16753
16861
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
16754
16862
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
16755
- const { ASGProvider } = await import("./asg-provider-D5G53CUp.js").then((n) => n.n);
16863
+ const { ASGProvider } = await import("./asg-provider-DLU0EEhO.js").then((n) => n.n);
16756
16864
  return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
16757
16865
  }
16758
16866
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
@@ -24925,7 +25033,7 @@ const FLUSH_INTERVAL_MS = 2e3;
24925
25033
  const FLUSH_EVENT_THRESHOLD = 50;
24926
25034
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
24927
25035
  function getCdkdVersion() {
24928
- return "0.284.21";
25036
+ return "0.284.22";
24929
25037
  }
24930
25038
  /**
24931
25039
  * Generate a time-sortable unique run id, e.g.
@@ -25670,7 +25778,7 @@ var DeployEngine = class {
25670
25778
  */
25671
25779
  redactOutputs(outputs) {
25672
25780
  if (this.outputSecrets.size === 0) return outputs;
25673
- return redactSecretsForState(outputs, this.outputSecrets, this.outputsSourceUsable ? this.outputsTemplateSource : void 0);
25781
+ return redactSecretsForState(outputs, this.outputSecrets, this.outputsSourceUsable ? this.outputsTemplateSource : void 0, TEMPLATE_SOURCED_RULES);
25674
25782
  }
25675
25783
  /**
25676
25784
  * Redact resolved secret plaintext out of rollback-journal operations (GHSA
@@ -27465,4 +27573,4 @@ var DeployEngine = class {
27465
27573
 
27466
27574
  //#endregion
27467
27575
  export { isTerminationProtectionPropagationError as $, PartialFailureError as $n, getBootstrapMarkerKey as $t, renderStatefulReason as A, expectedOwnerParam as An, applyRoleArnIfSet as At, exportAliasCollisionScrubWarning as B, resetAwsClients as Bn, shouldRetainResource as Bt, isFinalSnapshotError as C, stateBucketExistenceConfirmed as Cn, redactSecretsForState as Ct, extractDeploymentEventError as D, MIGRATE_TMP_PREFIX as Dn, s3BucketDualStackDomainName as Dt, makeCanonicalizePropertiesFn as E, CFN_TEMPLATE_URL_LIMIT as En, s3BucketDomainName as Et, green as F, processStackMessages as Fn, DagBuilder as Ft, collectInlinePolicyNamesManagedBySiblings as G, DependencyError as Gn, createAssetRedirectResolver as Gt, secretBearingStateKeyWarning as H, AssetError as Hn, stringifyValue as Ht, red as I, clearBucketRegionCache as In, TemplateParser as It, findActionableSilentDrops as J, LocalMigrateError as Jn, escapeRegExp$1 as Jt, clearOnUpdateRemoval as K, DeployCancelledError as Kn, loadPublishableAssetManifest as Kt, yellow as L, resolveBucketRegion as Ln, LockManager as Lt, bold as M, canonicalizeRegion as Mn, INTRINSIC_KEYS as Mt, cyan as N, derivePartitionAndUrlSuffix as Nn, describeTypeWithThrottleRetry as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, findLargeInlineResources as On, s3BucketRegionalDomainName as Ot, gray as P, AssemblyReader as Pn, withRetry as Pt, disableInstanceApiTermination as Q, NestedStackChildDirectDestroyError as Qn, ensureAssetStorage as Qt, collectDeclaredOutputNames as R, AwsClients as Rn, S3StateBackend as Rt, createPreDeleteFinalSnapshot as S, resolveUseCdkBootstrapAssets as Sn, maskSecretsInText as St, unsupportedFinalSnapshotError as T, CFN_TEMPLATE_BODY_LIMIT as Tn, s3BucketArn as Tt, stateKeySecretExposure as U, CdkdError as Un, WorkGraph as Ut, isExportAliasCollision as V, setAwsClients as Vn, AssetPublisher as Vt, IAMRoleProvider as W, ConfigError as Wn, buildAssetRedirectMap as Wt, CloudControlProvider as X, LockError as Xn, AssetModeResolver as Xt, findSilentDropProperties as Y, LocalStartServiceError as Yn, stripControlChars as Yt, slowCcOperationTimeoutMs as Z, MissingCdkCliError as Zn, BOOTSTRAP_MARKER_PREFIX as Zt, computeImplicitDeleteEdges as _, resolveAutoAssetStorage as _n, STATE_SOURCED_READBACK_RULES as _t, DeploymentEventsStore as a, buildDockerImage as an, StateError as ar, normalizeAwsTagsToCfn as at, buildFinalSnapshotIdentifier as b, resolveStateBucketWithDefault as bn, dynamicReferenceTokens as bt, replayFailedOperations as c, runDockerForeground as cn, isCdkdError as cr, coerceCfnBoolean as ct, updatePartialReason as d, getDockerImageBySourceHash as dn, isMarkedNonRetryable as dr, readConfigString as dt, parseBootstrapMarker as en, ProvisioningError as er, IntrinsicFunctionResolver as et, UNSPECIFIED_SKIP_REASON as f, Synthesizer as fn, isRetryableTransientError as fr, replayWarn as ft, IMPLICIT_DELETE_DEPENDENCIES as g, resolveApp as gn, STATE_SOURCED_CROSS_GENERATION_RULES as gt, maskingRetryLogger as h, getLegacyStateBucketName as hn, __exportAll as hr, requireConfigString as ht, DeploymentEventsReader as i, buildDenyExternalAccessPolicy as in, StackTerminationProtectionError as ir, WAFv2WebACLProvider as it, formatResourceLine as j, PARTITION_TABLE as jn, DiffCalculator as jt, isStatefulRecreateTargetSync as k, uploadCfnTemplate as kn, s3BucketWebsiteUrl as kt, replayRollback as l, runDockerStreaming as ln, normalizeAwsError as lr, configBooleanRefusal as lt, withResourceDeadline as m, getDefaultStateBucketName as mn, markNonRetryable as mr, requireConfigObject as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, validateAssetBucketName as nn, ResourceUpdateNotSupportedError as nr, getAccountInfo as nt, planFailedOps as o, formatDockerLoginError as on, SynthesisError as or, resolveExplicitPhysicalId as ot, deleteSkipReason as p, synthesisStatusMessage as pn, isThrottlingError as pr, requireConfigArray as pt, ProviderRegistry as q, LocalInvokeBuildError as qn, rewriteTemplateAssetReferences as qt, DeployEngine as r, validateContainerRepoName as rn, StackHasActiveImportsError as rr, refStateLookupFromResource as rt, planRollback as s, getDockerCmd as sn, formatError as sr, assertRegionMatch as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, readBootstrapMarkerBody as tn, ResourceTimeoutError as tr, cfnRefValueFromPhysicalId as tt, updatePartialMessage as u, AssetManifestLoader as un, withErrorHandling as ur, configStringRefusal as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, resolveCaptureObservedState as vn, TEMPLATE_SOURCED_RULES as vt, refusesFinalSnapshot as w, warnDeprecatedNoPrefixCliFlag as wn, scrubResourceRecord as wt, ccRoutedFinalSnapshotError as x, resolveStateBucketWithDefaultAndSource as xn, isSingleDynamicReferenceToken as xt, PRE_DELETE_SNAPSHOT_TYPES as y, resolveSkipPrefix as yn, createSecretMasker as yt, collectPublishedOutputNames as z, getAwsClients as zn, rebuildClientForBucketRegion as zt };
27468
- //# sourceMappingURL=deploy-engine--N_xnvjI.js.map
27576
+ //# sourceMappingURL=deploy-engine-DOlhIeGv.js.map