@go-to-k/cdkd 0.283.31 → 0.283.32
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/{asg-provider-SQ0V0nFd.js → asg-provider-CVoisQ0L.js} +2 -2
- package/dist/{asg-provider-SQ0V0nFd.js.map → asg-provider-CVoisQ0L.js.map} +1 -1
- package/dist/cli.js +105 -25
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-AWFiXyhp.js → deploy-engine-rYTNzst0.js} +361 -25
- package/dist/deploy-engine-rYTNzst0.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-AWFiXyhp.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as getAccountInfo, $t as runDockerForeground, A as cyan, An as getAwsClients, At as S3StateBackend, B as stateKeySecretExposure, Bn as LocalStartServiceError, Bt as escapeRegExp, C as makeCanonicalizePropertiesFn, Cn as canonicalizeRegion, Ct as DiffCalculator, D as renderStatefulReason, Dt as DagBuilder, E as isStatefulRecreateTargetSync, En as processStackMessages, Et as withRetry, F as collectDeclaredOutputNames, Ft as WorkGraph, G as findActionableSilentDrops, Gn as ProvisioningError, Gt as getBootstrapMarkerKey, H as collectInlinePolicyNamesManagedBySiblings, Hn as MissingCdkCliError, Ht as AssetModeResolver, I as collectPublishedOutputNames, It as buildAssetRedirectMap, J as slowCcOperationTimeoutMs, Jn as StackHasActiveImportsError, Jt as validateContainerRepoName, K as findSilentDropProperties, Kn as ResourceTimeoutError, Kt as parseBootstrapMarker, L as exportAliasCollisionScrubWarning, Ln as DeployCancelledError, Lt as createAssetRedirectResolver, M as green, Mn as setAwsClients, Mt as shouldRetainResource, N as red, Nt as AssetPublisher, O as formatResourceLine, On as resolveBucketRegion, Ot as TemplateParser, P as yellow, Pn as CdkdError, Pt as stringifyValue, Q as cfnRefValueFromPhysicalId, Qt as getDockerCmd, R as isExportAliasCollision, Rn as LocalInvokeBuildError$1, Rt as loadPublishableAssetManifest, S as unsupportedFinalSnapshotError, Sn as PARTITION_TABLE, St as applyRoleArnIfSet, T as MULTI_REGION_RECREATE_BLOCKED_TYPES, Tn as AssemblyReader, Tt as describeTypeWithThrottleRetry, U as clearOnUpdateRemoval, Un as NestedStackChildDirectDestroyError, Ut as BOOTSTRAP_MARKER_PREFIX, V as IAMRoleProvider, Vt as stripControlChars, W as ProviderRegistry, Wn as PartialFailureError, Wt as ensureAssetStorage, X as isTerminationProtectionPropagationError, Xt as buildDockerImage, Y as disableInstanceApiTermination, Yn as StackTerminationProtectionError, Yt as buildDenyExternalAccessPolicy, Z as IntrinsicFunctionResolver, Zt as formatDockerLoginError, _ as buildFinalSnapshotIdentifier, _n as CFN_TEMPLATE_URL_LIMIT, _t as s3BucketArn, a as DeploymentEventsStore, an as getDefaultStateBucketName, ar as markNonRetryable, at as coerceCfnBoolean, b as isFinalSnapshotError, bn as uploadCfnTemplate, bt as s3BucketRegionalDomainName, c as replayFailedOperations, cn as resolveAutoAssetStorage, ct as readConfigString, d as deleteSkipReason, dn as resolveStateBucketWithDefault, dt as requireConfigObject, en as runDockerStreaming, er as normalizeAwsError, et as refStateLookupFromResource, f as withResourceDeadline, fn as resolveStateBucketWithDefaultAndSource, ft as requireConfigString, g as PRE_DELETE_SNAPSHOT_TYPES, gn as CFN_TEMPLATE_BODY_LIMIT, gt as scrubResourceRecord, h as ATOMIC_FINAL_SNAPSHOT_TYPES, hn as warnDeprecatedNoPrefixCliFlag, ht as redactSecretsForState, i as DeploymentEventsReader, in as synthesisStatusMessage, ir as isThrottlingError, it as assertRegionMatch, j as gray, jn as resetAwsClients, jt as rebuildClientForBucketRegion, k as bold, kn as AwsClients, kt as LockManager, l as replayRollback, ln as resolveCaptureObservedState, lt as replayWarn, m as computeImplicitDeleteEdges, mn as stateBucketExistenceConfirmed, mt as TEMPLATE_SOURCED_RULES, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as getDockerImageBySourceHash, nr as isMarkedNonRetryable, nt as normalizeAwsTagsToCfn, o as planFailedOps, on as getLegacyStateBucketName, ot as configBooleanRefusal, p as IMPLICIT_DELETE_DEPENDENCIES, pn as resolveUseCdkBootstrapAssets, pt as STATE_SOURCED_CROSS_GENERATION_RULES, q as CloudControlProvider, qn as ResourceUpdateNotSupportedError, qt as validateAssetBucketName, r as DeployEngine, rn as Synthesizer, rr as isRetryableTransientError, rt as resolveExplicitPhysicalId, s as planRollback, sn as resolveApp, st as configStringRefusal, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as AssetManifestLoader, tr as withErrorHandling, tt as WAFv2WebACLProvider, u as UNSPECIFIED_SKIP_REASON, un as resolveSkipPrefix, ut as requireConfigArray, v as ccRoutedFinalSnapshotError, vn as MIGRATE_TMP_PREFIX, vt as s3BucketDomainName, w as extractDeploymentEventError, wn as derivePartitionAndUrlSuffix, wt as INTRINSIC_KEYS, x as refusesFinalSnapshot, xn as expectedOwnerParam, xt as s3BucketWebsiteUrl, y as createPreDeleteFinalSnapshot, yn as findLargeInlineResources, yt as s3BucketDualStackDomainName, z as secretBearingStateKeyWarning, zn as LocalMigrateError, zt as rewriteTemplateAssetReferences } from "./deploy-engine-rYTNzst0.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-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-CVoisQ0L.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";
|
|
@@ -57389,6 +57389,25 @@ function isSecretDynamicReference(value) {
|
|
|
57389
57389
|
if (typeof value !== "string") return false;
|
|
57390
57390
|
return value.includes("{{resolve:secretsmanager:") || value.includes("{{resolve:ssm-secure:");
|
|
57391
57391
|
}
|
|
57392
|
+
/**
|
|
57393
|
+
* The same question asked of a whole template VALUE, walking every string leaf.
|
|
57394
|
+
*
|
|
57395
|
+
* The leaf predicate answers `false` for a non-string, and an output's `Value`
|
|
57396
|
+
* is very often an OBJECT: `secret.secretValueFromJson(...)` renders the
|
|
57397
|
+
* secret's ARN as a `Ref`, so the value is an `Fn::Join` / `Fn::Sub` — which
|
|
57398
|
+
* CLAUDE.md's #1916 note calls the DOMINANT CDK shape, not an edge case.
|
|
57399
|
+
* Feeding the raw value to the leaf predicate therefore reported "no secret
|
|
57400
|
+
* here" for exactly the templates most likely to have one, which silently
|
|
57401
|
+
* disarmed BOTH signals built on it: the export-alias decision below (issue
|
|
57402
|
+
* #1919) and the legacy-record withholding that keeps a pre-GHSA plaintext out
|
|
57403
|
+
* of the rendered diff (issue #1921).
|
|
57404
|
+
*/
|
|
57405
|
+
function containsSecretDynamicReference(value) {
|
|
57406
|
+
if (typeof value === "string") return isSecretDynamicReference(value);
|
|
57407
|
+
if (Array.isArray(value)) return value.some(containsSecretDynamicReference);
|
|
57408
|
+
if (value !== null && typeof value === "object") return Object.values(value).some(containsSecretDynamicReference);
|
|
57409
|
+
return false;
|
|
57410
|
+
}
|
|
57392
57411
|
/** Structural equality, mirroring the deploy engine's `outputMapsEqual` leaf rule. */
|
|
57393
57412
|
function valuesEqual(a, b) {
|
|
57394
57413
|
if (a === b) return true;
|
|
@@ -57446,6 +57465,7 @@ async function resolveTemplateOutputs(template, resolveFn, conditions) {
|
|
|
57446
57465
|
const failedKeys = /* @__PURE__ */ new Set();
|
|
57447
57466
|
const secretSourceKeys = /* @__PURE__ */ new Set();
|
|
57448
57467
|
let resolutionFailed = false;
|
|
57468
|
+
const publishedOutputNames = collectPublishedOutputNames(template.Outputs ?? {}, conditions);
|
|
57449
57469
|
if (!template.Outputs) return {
|
|
57450
57470
|
outputs,
|
|
57451
57471
|
exportNames,
|
|
@@ -57464,7 +57484,7 @@ async function resolveTemplateOutputs(template, resolveFn, conditions) {
|
|
|
57464
57484
|
if (typeof output.Export?.Name === "string") failedKeys.add(output.Export.Name);
|
|
57465
57485
|
};
|
|
57466
57486
|
for (const [outputKey, output] of Object.entries(template.Outputs)) {
|
|
57467
|
-
if (!
|
|
57487
|
+
if (!containsSecretDynamicReference(output.Value)) continue;
|
|
57468
57488
|
secretSourceKeys.add(outputKey);
|
|
57469
57489
|
if (typeof output.Export?.Name === "string") secretSourceKeys.add(output.Export.Name);
|
|
57470
57490
|
}
|
|
@@ -57493,6 +57513,7 @@ async function resolveTemplateOutputs(template, resolveFn, conditions) {
|
|
|
57493
57513
|
if (output.Export?.Name) {
|
|
57494
57514
|
let exportName = output.Export.Name;
|
|
57495
57515
|
const exportSourceUsedSub = templateUsesSub(output.Export.Name);
|
|
57516
|
+
const declaredExportIsIntrinsic = typeof output.Export.Name !== "string";
|
|
57496
57517
|
if (typeof exportName !== "string") try {
|
|
57497
57518
|
exportName = await resolveFn(structuredClone(exportName));
|
|
57498
57519
|
} catch (error) {
|
|
@@ -57500,10 +57521,17 @@ async function resolveTemplateOutputs(template, resolveFn, conditions) {
|
|
|
57500
57521
|
resolutionFailed = true;
|
|
57501
57522
|
continue;
|
|
57502
57523
|
}
|
|
57503
|
-
if (typeof exportName === "string" && !isUnresolvedValue(exportName, exportSourceUsedSub)) {
|
|
57524
|
+
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`);
|
|
57525
|
+
else if (isExportAliasCollision(exportName, outputKey, publishedOutputNames)) logger.debug(`Diff skipping export alias ${stripControlChars(exportName)} of ${stripControlChars(outputKey)} — collides with an output name`);
|
|
57526
|
+
else if (!declaredExportIsIntrinsic && secretSourceKeys.size > 0) {
|
|
57527
|
+
logger.debug(`Diff cannot decide the export alias of ${stripControlChars(outputKey)} — a literal name may contain a resolved secret`);
|
|
57528
|
+
failedKeys.add(exportName);
|
|
57529
|
+
resolutionFailed = true;
|
|
57530
|
+
} else {
|
|
57504
57531
|
outputs[exportName] = value;
|
|
57505
57532
|
exportNames.add(exportName);
|
|
57506
|
-
}
|
|
57533
|
+
}
|
|
57534
|
+
else resolutionFailed = true;
|
|
57507
57535
|
}
|
|
57508
57536
|
}
|
|
57509
57537
|
return {
|
|
@@ -57553,8 +57581,8 @@ function computeOutputsDiff(current, desired, exportNames, secretSourceKeys = /*
|
|
|
57553
57581
|
const changes = [];
|
|
57554
57582
|
const currentBag = current ?? {};
|
|
57555
57583
|
const legacyRecord = Object.entries(currentBag).some(([name, oldValue]) => {
|
|
57556
|
-
if (isSecretDynamicReference(oldValue)) return false;
|
|
57557
|
-
return
|
|
57584
|
+
if (typeof oldValue === "string" && isSecretDynamicReference(oldValue)) return false;
|
|
57585
|
+
return containsSecretDynamicReference(desired[name]) || secretSourceKeys.has(name);
|
|
57558
57586
|
});
|
|
57559
57587
|
const push = (change) => {
|
|
57560
57588
|
if (legacyRecord && change.changeType !== "ADD") {
|
|
@@ -58988,10 +59016,10 @@ const REDACTED_LEGACY_PLAINTEXT = "<redacted: legacy plaintext in state — run
|
|
|
58988
59016
|
* consumer may match on, and silently mutating it there would trade a real
|
|
58989
59017
|
* correctness regression for a display concern belonging to whatever renders
|
|
58990
59018
|
* the JSON. `JSON.stringify` already escapes everything below 0x20.
|
|
59019
|
+
*
|
|
59020
|
+
* The helper itself now lives in `src/utils/regexp.ts` — `outputs-export-alias`
|
|
59021
|
+
* prints the same class of string and had grown a second copy.
|
|
58991
59022
|
*/
|
|
58992
|
-
function stripControlChars(value) {
|
|
58993
|
-
return value.replace(/[\u0000-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "");
|
|
58994
|
-
}
|
|
58995
59023
|
/**
|
|
58996
59024
|
* The same guard for an already-JSON-SERIALIZED value: C1 and the bidi marks
|
|
58997
59025
|
* only, deliberately NOT C0.
|
|
@@ -59211,9 +59239,11 @@ function createDiffCommand() {
|
|
|
59211
59239
|
//#endregion
|
|
59212
59240
|
//#region src/cli/commands/scrub.ts
|
|
59213
59241
|
/**
|
|
59214
|
-
* Signals `cdkd scrub` found
|
|
59215
|
-
*
|
|
59216
|
-
*
|
|
59242
|
+
* Signals `cdkd scrub` found plaintext it is reporting rather than removing.
|
|
59243
|
+
* Thrown under `--fail`: with `--dry-run` when any plaintext secret is in state,
|
|
59244
|
+
* and on a REAL run when a leak was found that scrub cannot rewrite (a
|
|
59245
|
+
* secret-bearing output KEY, issue #1919). Carries no message — the plan was
|
|
59246
|
+
* already printed — and maps to a non-zero exit so CI can gate on it.
|
|
59217
59247
|
*/
|
|
59218
59248
|
var ScrubNeededError = class ScrubNeededError extends CdkdError {
|
|
59219
59249
|
silent = true;
|
|
@@ -59312,6 +59342,7 @@ async function scrubCommand(stacks, options) {
|
|
|
59312
59342
|
});
|
|
59313
59343
|
const lockManager = new LockManager(stateS3.s3, stateConfig);
|
|
59314
59344
|
let totalStacksScrubbed = 0;
|
|
59345
|
+
let totalStacksWithUnscrubbableKeys = 0;
|
|
59315
59346
|
for (const stack of targetStacks) {
|
|
59316
59347
|
const scrubbed = await scrubStack(stack, stack.region || region, stateBackend, lockManager, {
|
|
59317
59348
|
dryRun: options.dryRun ?? false,
|
|
@@ -59321,18 +59352,26 @@ async function scrubCommand(stacks, options) {
|
|
|
59321
59352
|
if (scrubbed.recordsChanged > 0) {
|
|
59322
59353
|
totalStacksScrubbed++;
|
|
59323
59354
|
logger.info(`${options.dryRun ? "Would scrub" : "Scrubbed"} ${scrubbed.recordsChanged} resource record(s) in ${stack.stackName}`);
|
|
59324
|
-
} else logger.info(`No plaintext secrets found in ${stack.stackName}`);
|
|
59355
|
+
} else if (scrubbed.secretBearingKeys === 0) logger.info(`No plaintext secrets found in ${stack.stackName}`);
|
|
59356
|
+
if (scrubbed.secretBearingKeys > 0) {
|
|
59357
|
+
totalStacksWithUnscrubbableKeys++;
|
|
59358
|
+
logger.warn(`${scrubbed.secretBearingKeys} output KEY(s) in ${stack.stackName} hold plaintext and CANNOT be scrubbed — rename the Export.Name and redeploy (see the warning above).`);
|
|
59359
|
+
}
|
|
59325
59360
|
}
|
|
59326
|
-
if (totalStacksScrubbed === 0) {
|
|
59361
|
+
if (totalStacksScrubbed === 0 && totalStacksWithUnscrubbableKeys === 0) {
|
|
59327
59362
|
logger.info("\nNo plaintext secrets found in any target stack state. Nothing to scrub.");
|
|
59328
59363
|
return;
|
|
59329
59364
|
}
|
|
59365
|
+
const keyNote = totalStacksWithUnscrubbableKeys > 0 ? ` ${totalStacksWithUnscrubbableKeys} stack(s) hold plaintext in an output KEY, which cdkd scrub cannot rewrite — rename that output's Export.Name and redeploy.` : "";
|
|
59330
59366
|
if (options.dryRun) {
|
|
59331
|
-
logger.info(`\nPlan: ${totalStacksScrubbed} stack(s) hold plaintext secrets and would be scrubbed (--dry-run, no state written)
|
|
59367
|
+
if (totalStacksScrubbed > 0) logger.info(`\nPlan: ${totalStacksScrubbed} stack(s) hold plaintext secrets and would be scrubbed (--dry-run, no state written).${keyNote} ROTATE any exposed secret in Secrets Manager.`);
|
|
59368
|
+
else logger.info(`\nPlan: nothing can be scrubbed.${keyNote} ROTATE any exposed secret.`);
|
|
59332
59369
|
if (options.fail) throw new ScrubNeededError();
|
|
59333
59370
|
return;
|
|
59334
59371
|
}
|
|
59335
|
-
logger.info(`\nDone: scrubbed ${totalStacksScrubbed} stack(s). The plaintext is no longer stored, but a value that was ever persisted should be treated as compromised — ROTATE it in Secrets Manager (scrub matches the current value, so scrub BEFORE rotating)
|
|
59372
|
+
if (totalStacksScrubbed > 0) logger.info(`\nDone: scrubbed ${totalStacksScrubbed} stack(s). The plaintext is no longer stored there, but a value that was ever persisted should be treated as compromised — ROTATE it in Secrets Manager (scrub matches the current value, so scrub BEFORE rotating).${keyNote}`);
|
|
59373
|
+
else logger.info(`\nNothing could be rewritten.${keyNote} ROTATE any exposed secret.`);
|
|
59374
|
+
if (options.fail && totalStacksWithUnscrubbableKeys > 0) throw new ScrubNeededError();
|
|
59336
59375
|
}
|
|
59337
59376
|
/**
|
|
59338
59377
|
* Scrub one stack's state. Re-resolves the template's per-resource properties to
|
|
@@ -59351,7 +59390,8 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
59351
59390
|
logger.debug(`No state for ${stack.stackName} (${region}) — skipping`);
|
|
59352
59391
|
return {
|
|
59353
59392
|
recordsChanged: 0,
|
|
59354
|
-
secretsFound: 0
|
|
59393
|
+
secretsFound: 0,
|
|
59394
|
+
secretBearingKeys: 0
|
|
59355
59395
|
};
|
|
59356
59396
|
}
|
|
59357
59397
|
const state = loaded.state;
|
|
@@ -59399,12 +59439,42 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
59399
59439
|
perResourceTemplateProps.set(logicalId, templateResource.Properties);
|
|
59400
59440
|
}
|
|
59401
59441
|
const templateOutputs = stack.template.Outputs ?? {};
|
|
59442
|
+
const declaredOutputNames = collectDeclaredOutputNames(templateOutputs);
|
|
59443
|
+
const ambiguousKeys = /* @__PURE__ */ new Set();
|
|
59444
|
+
const collisions = [];
|
|
59445
|
+
let outputsSourceUntrusted = false;
|
|
59446
|
+
for (const [name, output] of Object.entries(templateOutputs)) {
|
|
59447
|
+
const declaredExportName = output.Export?.Name;
|
|
59448
|
+
let exportName = declaredExportName;
|
|
59449
|
+
if (declaredExportName !== void 0 && typeof declaredExportName !== "string") try {
|
|
59450
|
+
exportName = await resolver.resolve(declaredExportName, {
|
|
59451
|
+
template: stack.template,
|
|
59452
|
+
resources: state.resources,
|
|
59453
|
+
...Object.keys(parameters).length > 0 && { parameters },
|
|
59454
|
+
...Object.keys(conditions).length > 0 && { conditions },
|
|
59455
|
+
stackName: stack.stackName,
|
|
59456
|
+
recordedSecretValues: outputSecrets,
|
|
59457
|
+
bestEffort: true
|
|
59458
|
+
});
|
|
59459
|
+
} catch (err) {
|
|
59460
|
+
outputsSourceUntrusted = true;
|
|
59461
|
+
logger.warn(`Export.Name of output ${name} could not be resolved during scrub (${err instanceof Error ? err.message : String(err)}) — redacting this stack's outputs by value match instead of by template position, since state may be keyed under a name this run cannot reproduce.`);
|
|
59462
|
+
}
|
|
59463
|
+
if (declaredExportName !== void 0 && typeof exportName !== "string") outputsSourceUntrusted = true;
|
|
59464
|
+
if (declaredExportName !== void 0 && typeof exportName === "string" && isUnresolvedValue(exportName, templateUsesSub(declaredExportName))) {
|
|
59465
|
+
outputsSourceUntrusted = true;
|
|
59466
|
+
logger.warn(`Export.Name of output ${name} did not fully resolve during scrub — redacting this stack's outputs by value match instead of by template position, since state may be keyed under a name this run cannot reproduce.`);
|
|
59467
|
+
} else if (typeof exportName === "string" && isExportAliasCollision(exportName, name, declaredOutputNames)) {
|
|
59468
|
+
ambiguousKeys.add(exportName);
|
|
59469
|
+
collisions.push([name, exportName]);
|
|
59470
|
+
}
|
|
59471
|
+
}
|
|
59402
59472
|
for (const [name, output] of Object.entries(templateOutputs)) {
|
|
59403
59473
|
const value = output.Value;
|
|
59404
59474
|
if (value === void 0) continue;
|
|
59405
|
-
outputsTemplateSource[name] = value;
|
|
59475
|
+
if (!ambiguousKeys.has(name)) outputsTemplateSource[name] = value;
|
|
59406
59476
|
const exportName = output.Export?.Name;
|
|
59407
|
-
if (typeof exportName === "string") outputsTemplateSource[exportName] = value;
|
|
59477
|
+
if (typeof exportName === "string" && !ambiguousKeys.has(exportName)) outputsTemplateSource[exportName] = value;
|
|
59408
59478
|
try {
|
|
59409
59479
|
await resolver.resolve(value, {
|
|
59410
59480
|
template: stack.template,
|
|
@@ -59419,10 +59489,19 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
59419
59489
|
logger.debug(`Resolution of output ${name} during scrub was partial: ${err instanceof Error ? err.message : String(err)}`);
|
|
59420
59490
|
}
|
|
59421
59491
|
}
|
|
59492
|
+
for (const [name, exportName] of collisions) logger.warn(exportAliasCollisionScrubWarning(name, exportName, outputSecrets));
|
|
59493
|
+
const secretBearingKeys = [];
|
|
59494
|
+
for (const key of Object.keys(state.outputs ?? {})) {
|
|
59495
|
+
const exposure = stateKeySecretExposure(key, outputSecrets);
|
|
59496
|
+
if (!exposure) continue;
|
|
59497
|
+
secretBearingKeys.push(key);
|
|
59498
|
+
logger.warn(secretBearingStateKeyWarning(stack.stackName, key, exposure));
|
|
59499
|
+
}
|
|
59422
59500
|
const totalSecrets = outputSecrets.size + [...perResourceSecrets.values()].reduce((n, m) => n + m.size, 0);
|
|
59423
59501
|
if (totalSecrets === 0) return {
|
|
59424
59502
|
recordsChanged: 0,
|
|
59425
|
-
secretsFound: 0
|
|
59503
|
+
secretsFound: 0,
|
|
59504
|
+
secretBearingKeys: 0
|
|
59426
59505
|
};
|
|
59427
59506
|
let recordsChanged = 0;
|
|
59428
59507
|
const newResources = {};
|
|
@@ -59438,7 +59517,7 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
59438
59517
|
if (JSON.stringify(scrubbed) !== JSON.stringify(record)) recordsChanged++;
|
|
59439
59518
|
newResources[logicalId] = scrubbed;
|
|
59440
59519
|
}
|
|
59441
|
-
const newOutputs = outputSecrets.size > 0 ? redactSecretsForState(state.outputs, outputSecrets, outputsTemplateSource) : state.outputs;
|
|
59520
|
+
const newOutputs = outputSecrets.size > 0 ? redactSecretsForState(state.outputs, outputSecrets, outputsSourceUntrusted ? void 0 : outputsTemplateSource) : state.outputs;
|
|
59442
59521
|
if (JSON.stringify(newOutputs) !== JSON.stringify(state.outputs)) recordsChanged++;
|
|
59443
59522
|
if (recordsChanged > 0 && !opts.dryRun) {
|
|
59444
59523
|
const nextState = {
|
|
@@ -59451,7 +59530,8 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
59451
59530
|
}
|
|
59452
59531
|
return {
|
|
59453
59532
|
recordsChanged,
|
|
59454
|
-
secretsFound: totalSecrets
|
|
59533
|
+
secretsFound: totalSecrets,
|
|
59534
|
+
secretBearingKeys: secretBearingKeys.length
|
|
59455
59535
|
};
|
|
59456
59536
|
} finally {
|
|
59457
59537
|
if (acquired) await lockManager.releaseLock(stack.stackName, region).catch((err) => {
|
|
@@ -59460,7 +59540,7 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
59460
59540
|
}
|
|
59461
59541
|
}
|
|
59462
59542
|
function createScrubCommand() {
|
|
59463
|
-
const cmd = new Command("scrub").description("Rewrite persisted state so resolved secret dynamic references are stored as their {{resolve:...}} expression, not the plaintext value (no deploy).").argument("[stacks...]", "Stack name(s) to scrub (physical name or display path)").option("--all", "Scrub every stack in the synthesized app", false).option("--dry-run", "Report what would be scrubbed without writing state").option("--fail", "With --dry-run, exit non-zero if any plaintext secret is found (CI gate)");
|
|
59543
|
+
const cmd = new Command("scrub").description("Rewrite persisted state so resolved secret dynamic references are stored as their {{resolve:...}} expression, not the plaintext value (no deploy).").argument("[stacks...]", "Stack name(s) to scrub (physical name or display path)").option("--all", "Scrub every stack in the synthesized app", false).option("--dry-run", "Report what would be scrubbed without writing state").option("--fail", "With --dry-run, exit non-zero if any plaintext secret is found (CI gate). Also exits non-zero on a real run when a leak was found that scrub cannot rewrite.");
|
|
59464
59544
|
[
|
|
59465
59545
|
...commonOptions,
|
|
59466
59546
|
...appOptions,
|
|
@@ -80334,7 +80414,7 @@ function createMigrateCommand() {
|
|
|
80334
80414
|
*/
|
|
80335
80415
|
function buildProgram() {
|
|
80336
80416
|
const program = new Command();
|
|
80337
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.283.
|
|
80417
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.283.32");
|
|
80338
80418
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
80339
80419
|
const { profile } = actionCommand.optsWithGlobals();
|
|
80340
80420
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|