@go-to-k/cdkd 0.288.4 → 0.288.5
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-TexsJufw.js → asg-provider-C_AHAmgD.js} +2 -2
- package/dist/{asg-provider-TexsJufw.js.map → asg-provider-C_AHAmgD.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-BiPxTgKT.js → deploy-engine-Dsf9bTqH.js} +574 -139
- package/dist/deploy-engine-Dsf9bTqH.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-Ci4Y5UO9.js → program-CMhpftan.js} +470 -35
- package/dist/{program-Ci4Y5UO9.js.map → program-CMhpftan.js.map} +1 -1
- package/dist/{version-BmM4QJ9t.js → version-BFmq8S23.js} +2 -2
- package/dist/{version-BmM4QJ9t.js.map → version-BFmq8S23.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-BiPxTgKT.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as ProviderRegistry, $n as
|
|
1
|
+
import { $ as ProviderRegistry, $n as formatDockerLoginError, $r as PartialFailureError, $t as STATE_SOURCED_BASELINE_RULES, A as renderStatefulReason, An as stringifyValue, Ar as PARTITION_TABLE, At as configStringRefusal, B as red, Bn as getBootstrapMarkerKey, Br as setAwsClients, Bt as s3BucketDomainName, C as refusesFinalSnapshot, Cn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Cr as CFN_TEMPLATE_URL_LIMIT, Ct as refStateLookupFromResource, D as MULTI_REGION_RECREATE_BLOCKED_TYPES, Dn as importableOutputs, Dr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Dt as assertRegionMatch, E as extractDeploymentEventError, En as importableOutputKeys, Er as uploadCfnTemplate, Et as resolveExplicitPhysicalId, F as formatResourceLine, Fn as rewriteTemplateAssetReferences, Ft as requireConfigString, G as isExportAliasCollision, Gn as validateContainerRepoName, Gt as DiffCalculator, H as collectDeclaredOutputNames, Hn as parseBootstrapMarker, Hr as CdkdError, Ht as s3BucketRegionalDomainName, I as bold, In as AssetModeResolver, Ir as resolveBucketRegion, It as classifyReplaySecretRegion, J as secretSafeKeyDisplay, Jn as buildDockerImage, Jr as IntrinsicResolutionRefusalError, Jt as findSilentDropProperties, K as secretBearing, Kn as buildDenyExternalAccessPolicy, Kr as DeployCancelledError, Kt as INTRINSIC_KEYS, L as cyan, Ln as BOOTSTRAP_MARKER_PREFIX, Lr as AwsClients, Lt as producerRegionsFromState, M as coerceWarmThroughput$1, Mn as buildAssetRedirectMap, Mr as derivePartitionAndUrlSuffix, Mt as replayWarn, N as isWarmThroughputDecrease, Nn as createAssetRedirectResolver, Nr as AssemblyReader, Nt as requireConfigArray, O as isStatefulRecreateTargetForReplace, On as shouldRetainResource, Or as displaySafe, Ot as coerceCfnBoolean, P as toFiniteNumber, Pn as loadPublishableAssetManifest, Pr as processStackMessages, Pt as requireConfigObject, Q as clearOnUpdateRemoval, Qn as dockerSpawnEnvWithSensitive, Qr as NestedStackChildDirectDestroyError, Qt as TemplateParser, R as gray, Rn as assertAssetBucketRegion, Rr as getAwsClients, Rt as withSharedDrainBudget, S as isFinalSnapshotError, Sn as shellQuote, Sr as CFN_TEMPLATE_BODY_LIMIT, St as parameterTypeMayLoseSecretIdentity, T as makeCanonicalizePropertiesFn, Tn as exportNamesCarriedFrom, Tr as findLargeInlineResources, Tt as normalizeAwsTagsToCfn, U as collectPublishedOutputNames, Un as readBootstrapMarkerBody, Ut as s3BucketWebsiteUrl, V as yellow, Vn as isCrossRegionRedirect, Vt as s3BucketDualStackDomainName, W as exportAliasCollisionScrubWarning, Wn as validateAssetBucketName, Wr as CrossAccountSecretRefusalError, Wt as applyRoleArnIfSet, X as IAMRoleProvider, Xn as describeDockerExecFailure, Xr as LocalStartServiceError, Xt as withRetry, Y as getCurrentResourceSecrets, Yn as describeDockerCapturedOutput, Yr as LocalInvokeBuildError$1, Yt as describeTypeWithThrottleRetry, Z as collectInlinePolicyNamesManagedBySiblings, Zn as describeDockerFailure, Zt as DagBuilder, _ as ATOMIC_FINAL_SNAPSHOT_TYPES, _i as retryClassificationText, _n as rebuildClientForBucketRegion, _r as resolveStateBucketWithDefault, _t as carriesDynamicReference, a as DeploymentEventsStore, an as dynamicReferenceTokens, ar as escapeRegExp, at as endCommandInterruptScope, b as ccRoutedFinalSnapshotError, bn as buildLockContentionMessage, br as stateBucketExistenceConfirmed, bt as getAccountInfo, c as replayFailedOperations, cn as isSingleDynamicReferenceToken, cr as getDockerImageBySourceHash, ct as startInterruptWatch, d as updatePartialReason, di as isMarkedNonRetryable, dn as recordMaskOnlyValue, dr as getDefaultStateBucketName, dt as UNSPECIFIED_SKIP_REASON, ei as ProvisioningError, en as STATE_SOURCED_CROSS_GENERATION_RULES, er as getDockerCmd, et as wouldReturnToSdkProvider, f as withResourceDeadline, fi as isRetryableTransientError, fn as recoverMaskedOutput, fr as getLegacyStateBucketName, ft as deleteIndeterminateGuards, g as bindingSkippedOutputs, gi as markRedactedCause, gn as S3StateBackend, gr as resolveSkipPrefix, gt as IntrinsicFunctionResolver, h as computeImplicitDeleteEdges, hi as markNonRetryable, hn as LockManager, hr as resolveCaptureObservedState, ht as isTerminationProtectionPropagationError, i as DeploymentEventsReader, ii as StackTerminationProtectionError, in as createSecretMasker, ir as runDockerStreaming, it as beginCommandInterruptScope, j as WARM_THROUGHPUT_MEMBERS, jn as WorkGraph, jr as canonicalizeRegion, jt as readConfigString, k as isStatefulRecreateTargetSync, kn as AssetPublisher, kr as expectedOwnerParam, kt as configBooleanRefusal, l as replayRollback, li as normalizeAwsError, ln as maskSecretsInError, lr as Synthesizer, lt as CloudControlProvider, m as IMPLICIT_DELETE_DEPENDENCIES, mi as isTransientServerError, mn as scrubResourceRecord, mr as resolveAutoAssetStorage, mt as disableInstanceApiTermination, n as DEFAULT_RESOURCE_WARN_AFTER_MS, ni as ResourceUpdateNotSupportedError, nn as TEMPLATE_SOURCED_RULES, nr as redactDockerArgvValues, nt as maskDeep, o as planFailedOps, on as errorCauseChain, or as stripControlChars, ot as interruptWatchListenerCount, p as maskingRetryLogger, pi as isThrottlingError, pn as redactSecretsForState, pr as resolveApp, pt as deleteSkipReason, q as secretBearingStateKeyWarning, qn as describeAwsFailure, qr as DynamicReferenceRegionAmbiguousError, qt as findActionableSilentDrops, r as DeployEngine, ri as StackHasActiveImportsError, rn as carriesSecretMask, rr as runDockerForeground, rt as maskerOrIdentity, s as planRollback, sn as identityKeyFor, sr as AssetManifestLoader, st as isInterruptedWaitError, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as ResourceTimeoutError, tn as STATE_SOURCED_READBACK_RULES, tr as partitionSensitiveEnv, tt as createMaskedRetryLogger, u as updatePartialMessage, ui as withErrorHandling, un as maskSecretsInText, ur as synthesisStatusMessage, ut as slowCcOperationTimeoutMs, v as PRE_DELETE_SNAPSHOT_TYPES, vn as UNRENDERABLE, vr as resolveStateBucketWithDefaultAndSource, vt as cfnRefValueFromPhysicalId, w as unsupportedFinalSnapshotError, wn as DEFAULT_STATE_PREFIX, wr as MIGRATE_TMP_PREFIX, wt as WAFv2WebACLProvider, x as createPreDeleteFinalSnapshot, xn as forceQuitRecoveryClause, xr as warnDeprecatedNoPrefixCliFlag, xt as isUnboundTemplateParameter, y as buildFinalSnapshotIdentifier, yn as buildForceUnlockCommand, yr as resolveUseCdkBootstrapAssets, yt as coerceParameterTypedValue, z as green, zn as ensureAssetStorage, zr as resetAwsClients, zt as s3BucketArn } from "./deploy-engine-Dsf9bTqH.js";
|
|
2
2
|
import { _ as withStackName, c as PATTERN_B_NAME_OPTIONS, f as generateResourceName, g as withSkipPrefix, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as getCurrentStackName, n as getLogger, o as runStackBuffered, p as generateResourceNameWithFallback, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES } from "./logger-Dw-3y48G.js";
|
|
3
3
|
import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
|
|
4
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
-
import { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as getCdkdVersion } from "./version-BFmq8S23.js";
|
|
5
|
+
import { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-C_AHAmgD.js";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
8
|
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, NotFound, 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";
|
|
@@ -44136,7 +44136,11 @@ var CognitoUserPoolProvider = class {
|
|
|
44136
44136
|
* Retry a Cognito control-plane call on transient "settling" errors. A
|
|
44137
44137
|
* SetUserPoolMfaConfig issued immediately after CreateUserPool (or another
|
|
44138
44138
|
* control-plane write) can briefly hit `ConcurrentModificationException` /
|
|
44139
|
-
* "please retry". Backoff
|
|
44139
|
+
* "please retry". Backoff is capped at 4s, but the DEFAULT 3 attempts means
|
|
44140
|
+
* only TWO sleeps -- 1s then 2s, 3s total -- so the 4s step is unreachable
|
|
44141
|
+
* unless a caller raises `maxAttempts`. (`retryable-errors.ts`'s Cognito
|
|
44142
|
+
* SMS-role entry depends on that arithmetic; an earlier revision of it
|
|
44143
|
+
* copied a 7s figure out of this comment's previous wording.)
|
|
44140
44144
|
*/
|
|
44141
44145
|
async retryOnTransientControlPlane(fn, label, maxAttempts = 3) {
|
|
44142
44146
|
for (let attempt = 1;; attempt++) try {
|
|
@@ -70046,7 +70050,7 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
|
|
|
70046
70050
|
logger.warn(`${logicalId} (${resource.resourceType}): ` + (refused ? "refused to resolve a dynamic reference this resource's state records, so its secret-bearing properties are NOT compared — " : "could not resolve the dynamic reference(s) this resource's state records, so its secret-bearing properties are NOT compared — ") + `${maskSecretsInText(err instanceof Error ? err.message : String(err), secrets)}`);
|
|
70047
70051
|
secrets.clear();
|
|
70048
70052
|
}
|
|
70049
|
-
if (unresolvedTokens.size > 0) logger.warn(`${logicalId} (${resource.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — those properties are NOT compared. A revert leaves a property whose WHOLE value is one of these tokens untouched; where a token is EMBEDDED in a longer string, the revert writes
|
|
70053
|
+
if (unresolvedTokens.size > 0) logger.warn(`${logicalId} (${resource.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — those properties are NOT compared. A revert leaves a property whose WHOLE value is one of these tokens untouched where its position can be matched against AWS's report (a list element pairs by an identity field, or by the list's own unchanged literal values); where it cannot be matched, and where a token is EMBEDDED in a longer string, the revert writes the token literal, exactly as 'cdkd deploy' does — so a resolved value AWS holds there WOULD be overwritten. cdkd resolves 'secretsmanager', 'ssm' and 'ssm-secure' references; anything else is left as written.`);
|
|
70050
70054
|
const observedIgnorePaths = useObserved ? undeclaredEmptyObservedKeys(resource.observedProperties, resource.properties ?? {}) : [];
|
|
70051
70055
|
const normalized = await canonicalizePrincipalUniqueIds(comparisonBaseline, aws, resolvePrincipalUniqueId);
|
|
70052
70056
|
const protocolNormalized = canonicalizeIpProtocols(normalized.baseline, normalized.aws, resource.resourceType);
|
|
@@ -70523,6 +70527,22 @@ function isPlainRecord(value) {
|
|
|
70523
70527
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70524
70528
|
}
|
|
70525
70529
|
/**
|
|
70530
|
+
* Does this value carry an ORDINARY object prototype?
|
|
70531
|
+
*
|
|
70532
|
+
* {@link isPlainRecord} admits `Date` / `Map` / `Set` / class instances, whose
|
|
70533
|
+
* own enumerable keys are `[]` — so a key-walk over one reports "no
|
|
70534
|
+
* contradiction" between two values that actually differ. The redaction walk's
|
|
70535
|
+
* `deepEqualJsonValue` carries the same guard for the same reason
|
|
70536
|
+
* (`secret-redaction.ts`, issue #2869): a `Date` anchor corroborates NOTHING.
|
|
70537
|
+
* Kept file-local rather than imported because `secret-redaction.js` is
|
|
70538
|
+
* `vi.mock`ed by several drift suites, and a value import from it reds them
|
|
70539
|
+
* with a missing-export failure.
|
|
70540
|
+
*/
|
|
70541
|
+
function hasPlainPrototype(value) {
|
|
70542
|
+
const proto = Object.getPrototypeOf(value);
|
|
70543
|
+
return proto === Object.prototype || proto === null;
|
|
70544
|
+
}
|
|
70545
|
+
/**
|
|
70526
70546
|
* Merge the AWS-current value with the revert baseline, KEEPING every path the
|
|
70527
70547
|
* baseline does not declare (issue
|
|
70528
70548
|
* [#1626](https://github.com/go-to-k/cdkd/issues/1626) items 2 + 3).
|
|
@@ -70606,7 +70626,8 @@ function mergeUntemplatedValue(awsValue, desiredValue) {
|
|
|
70606
70626
|
return merged;
|
|
70607
70627
|
}
|
|
70608
70628
|
if (!isPlainRecord(awsValue) || !isPlainRecord(desiredValue)) return desiredValue;
|
|
70609
|
-
|
|
70629
|
+
if (!hasPlainPrototype(awsValue) || !hasPlainPrototype(desiredValue)) return desiredValue;
|
|
70630
|
+
const merged = Object.create(null);
|
|
70610
70631
|
for (const [key, value] of Object.entries(awsValue)) merged[key] = key in desiredValue ? mergeUntemplatedValue(value, desiredValue[key]) : value;
|
|
70611
70632
|
for (const [key, value] of Object.entries(desiredValue)) if (!(key in awsValue)) merged[key] = value;
|
|
70612
70633
|
return merged;
|
|
@@ -70640,9 +70661,26 @@ function mergeUntemplatedValue(awsValue, desiredValue) {
|
|
|
70640
70661
|
*
|
|
70641
70662
|
* Falls back to KEEPING the token wherever AWS has nothing at that position —
|
|
70642
70663
|
* that is what `cdkd deploy` sends, so it is the safe residual rather than
|
|
70643
|
-
* dropping a property the resource may require. Array descent
|
|
70644
|
-
*
|
|
70645
|
-
*
|
|
70664
|
+
* dropping a property the resource may require. Array descent pairs live
|
|
70665
|
+
* elements through {@link pairedLiveItems} — an identity field first, else the
|
|
70666
|
+
* array's own literal frame corroborated per LEAF — never by blind index
|
|
70667
|
+
* (issue [#2893](https://github.com/go-to-k/cdkd/issues/2893)): until that
|
|
70668
|
+
* issue the descent was positional behind a length test, so a readback AWS
|
|
70669
|
+
* REORDERED copied element *i*'s live value into element *i*'s token position
|
|
70670
|
+
* and `provider.update` shipped a value belonging to a DIFFERENT element. The
|
|
70671
|
+
* wildcard handed to the pairing is "whole token OR the redaction mask": a
|
|
70672
|
+
* token leaf cannot corroborate an order (its live counterpart is the resolved
|
|
70673
|
+
* value, so comparing would refuse every array this pass exists to serve), and
|
|
70674
|
+
* a mask leaf belongs to the SIBLING mask walk but must abstain here rather
|
|
70675
|
+
* than contradict. Where the pairing refuses, the token is KEPT — the same
|
|
70676
|
+
* residual as everywhere else in this pass, and deliberately NOT the mask
|
|
70677
|
+
* walk's drop-the-resource refusal: that one would abandon every other drifted
|
|
70678
|
+
* property over an alignment this pass never needed, while the kept token is
|
|
70679
|
+
* what `cdkd deploy` sends. The trade this accepts: an array whose OTHER
|
|
70680
|
+
* leaves genuinely drifted no longer takes a positional copy (the frame cannot
|
|
70681
|
+
* corroborate an order it no longer matches), so the token ships there —
|
|
70682
|
+
* a no-op for a record cdkd deployed, and for any other record a preserved
|
|
70683
|
+
* pre-existing breakage rather than another element's value written live.
|
|
70646
70684
|
*/
|
|
70647
70685
|
function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
70648
70686
|
const walk = (value, live) => {
|
|
@@ -70652,12 +70690,13 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
70652
70690
|
return live;
|
|
70653
70691
|
}
|
|
70654
70692
|
if (Array.isArray(value)) {
|
|
70655
|
-
const liveItems =
|
|
70693
|
+
const liveItems = pairedLiveItems(value, live, isTokenOrMaskLeaf, carriesTokenOrMask, true);
|
|
70656
70694
|
return value.map((item, i) => walk(item, liveItems?.[i]));
|
|
70657
70695
|
}
|
|
70658
70696
|
if (value !== null && typeof value === "object") {
|
|
70697
|
+
if (!hasPlainPrototype(value)) return value;
|
|
70659
70698
|
const liveObject = isPlainRecord(live) ? live : void 0;
|
|
70660
|
-
const out =
|
|
70699
|
+
const out = Object.create(null);
|
|
70661
70700
|
for (const [k, v] of Object.entries(value)) out[k] = walk(v, liveObject?.[k]);
|
|
70662
70701
|
return out;
|
|
70663
70702
|
}
|
|
@@ -70666,6 +70705,335 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
70666
70705
|
return walk(send, awsProperties);
|
|
70667
70706
|
}
|
|
70668
70707
|
/**
|
|
70708
|
+
* Paths in the bag `--revert` is about to send whose value is an unresolved
|
|
70709
|
+
* CloudFormation intrinsic OBJECT — `{ Ref: ... }` or a single-key
|
|
70710
|
+
* `{ 'Fn::*': ... }` (issue
|
|
70711
|
+
* [#2855](https://github.com/go-to-k/cdkd/issues/2855)).
|
|
70712
|
+
*
|
|
70713
|
+
* WHY THE SHAPE IS REACHABLE. `cdkd import`'s warn path persists raw intrinsic
|
|
70714
|
+
* objects into a record's `properties`, and issue #2842's baseline refusal is
|
|
70715
|
+
* what routes such a record here: it leaves `observedProperties` undefined, so
|
|
70716
|
+
* `runRevert`'s `revertBaseline` falls back to the raw bag.
|
|
70717
|
+
* `resolveStateSecretExpressions` re-resolves only `{{resolve:...}}` STRINGS,
|
|
70718
|
+
* so the OBJECT walks through it untouched, and the drift comparator's
|
|
70719
|
+
* `{{resolve:` skip is a string test too — the object simply differs from the
|
|
70720
|
+
* readback, drifts, and `buildRevertNewProperties` overlays it into the send
|
|
70721
|
+
* bag.
|
|
70722
|
+
*
|
|
70723
|
+
* WHY IT MUST BE REFUSED rather than shipped or live-preserved. Measured
|
|
70724
|
+
* (issue #2855's open question) with mocked SDK clients on both routes: NO
|
|
70725
|
+
* route fails loudly on cdkd's side. `SSMParameterProvider.update` puts the
|
|
70726
|
+
* raw object straight into `PutParameterCommand.input.Value`;
|
|
70727
|
+
* `CloudControlProvider.update` emits it verbatim as a JSON-patch value, and
|
|
70728
|
+
* for a `JSON_STRING_PROPERTIES` type (`AWS::Events::Rule.EventPattern`) it is
|
|
70729
|
+
* `JSON.stringify`ed into a schema-VALID string — a payload AWS cannot even
|
|
70730
|
+
* reject on type, i.e. guaranteed silent corruption. Live-preserving (the
|
|
70731
|
+
* token pass's answer) is no better: the intrinsic's real value depends on
|
|
70732
|
+
* template context a synth-free command does not have, and copying whatever
|
|
70733
|
+
* AWS holds would silently bless an out-of-band edit as the baseline.
|
|
70734
|
+
*
|
|
70735
|
+
* The predicate mirrors the resolver's own rule (a CloudFormation intrinsic is
|
|
70736
|
+
* ALWAYS a single-key object; `detectUnknownIntrinsicKey` in
|
|
70737
|
+
* `intrinsic-function-resolver.ts` states why single-key is what keeps a real
|
|
70738
|
+
* property literally named `Ref` from false-positiving). A non-plain object
|
|
70739
|
+
* (`Date`, `Uint8Array`) cannot be an intrinsic and is not descended. The scan
|
|
70740
|
+
* is scoped to `topLevelKeys` — the DRIFTED keys, the only ones
|
|
70741
|
+
* `buildRevertNewProperties` sources from the baseline — so a value that
|
|
70742
|
+
* merely looks intrinsic under a non-drifted key cannot refuse a revert it
|
|
70743
|
+
* never participates in. The caller hands it the BASELINE
|
|
70744
|
+
* (`desiredProperties`), never the post-overlay send bag: on the very
|
|
70745
|
+
* population this refusal exists for, `mergeUntemplatedValue`'s key-merge can
|
|
70746
|
+
* FUSE a single-key intrinsic with a plain-record live value into a multi-key
|
|
70747
|
+
* object the predicate no longer matches, while every overlay arm re-emits
|
|
70748
|
+
* every baseline path into the send bag in some shape — so the baseline scan
|
|
70749
|
+
* is complete where the merged-bag scan was dilutable, and an AWS-echoed
|
|
70750
|
+
* lookalike (which lives only in the overlay) stays exempt by construction.
|
|
70751
|
+
* The caller additionally gates the whole scan on
|
|
70752
|
+
* `observedProperties === undefined` — intrinsic objects reach a revert
|
|
70753
|
+
* baseline only through the raw-`properties` fallback, while an
|
|
70754
|
+
* `observedProperties` baseline is READBACK-derived, where a single-key map
|
|
70755
|
+
* named like an intrinsic is a real AWS value and a refusal would be
|
|
70756
|
+
* permanent (the call site's comment carries the full derivation).
|
|
70757
|
+
*
|
|
70758
|
+
* The whole flagged leaf is reported once; nothing beneath it is separately
|
|
70759
|
+
* scanned, since the caller refuses the resource on the first path anyway.
|
|
70760
|
+
*/
|
|
70761
|
+
function collectUnresolvedIntrinsicObjectPaths(bag, topLevelKeys) {
|
|
70762
|
+
const found = [];
|
|
70763
|
+
const visit = (value, path) => {
|
|
70764
|
+
if (Array.isArray(value)) {
|
|
70765
|
+
value.forEach((item, i) => visit(item, `${path}[${i}]`));
|
|
70766
|
+
return;
|
|
70767
|
+
}
|
|
70768
|
+
if (value !== null && typeof value === "object") {
|
|
70769
|
+
if (!hasPlainPrototype(value)) return;
|
|
70770
|
+
const record = value;
|
|
70771
|
+
const keys = Object.keys(record);
|
|
70772
|
+
const key = keys[0];
|
|
70773
|
+
if (keys.length === 1 && key !== void 0 && (key === "Ref" || key.startsWith("Fn::"))) {
|
|
70774
|
+
found.push(path);
|
|
70775
|
+
return;
|
|
70776
|
+
}
|
|
70777
|
+
for (const [k, v] of Object.entries(record)) visit(v, `${path}.${k}`);
|
|
70778
|
+
}
|
|
70779
|
+
};
|
|
70780
|
+
for (const key of topLevelKeys) if (Object.hasOwn(bag, key)) visit(bag[key], key);
|
|
70781
|
+
return found.sort();
|
|
70782
|
+
}
|
|
70783
|
+
/**
|
|
70784
|
+
* The live array to read a preserved leaf's replacement from, aligned to
|
|
70785
|
+
* `send`'s indices — or `undefined` when the two lists cannot be aligned at
|
|
70786
|
+
* all, which routes every mask beneath them to `unpreservablePaths` in the
|
|
70787
|
+
* mask walk and keeps the token in the token walk (see the wildcard
|
|
70788
|
+
* paragraph below for the split).
|
|
70789
|
+
*
|
|
70790
|
+
* WHY THIS IS NOT INDEX ALIGNMENT ANY MORE (issue
|
|
70791
|
+
* [#2884](https://github.com/go-to-k/cdkd/issues/2884)). Until issue
|
|
70792
|
+
* [#2852](https://github.com/go-to-k/cdkd/issues/2852) the only mask in a
|
|
70793
|
+
* persisted bag came from a `NoEcho` custom resource's `Data` (issue #2274),
|
|
70794
|
+
* which sits at a scalar leaf of a property the template names. #2852 added a
|
|
70795
|
+
* second source, and it lands in exactly the population index alignment is
|
|
70796
|
+
* WRONG on: a leaf is masked BECAUSE `refuseUncertifiedReadbackPositions` could
|
|
70797
|
+
* not pair the two lists — an unkeyed array AWS reordered, or a keyed element
|
|
70798
|
+
* whose identity AWS normalised. A resource that produced a mask is by
|
|
70799
|
+
* construction one whose list does not come back in a stable order.
|
|
70800
|
+
*
|
|
70801
|
+
* Traced consequence, which is the issue #1498 / #1501 data-loss class this
|
|
70802
|
+
* module's own doc calls "strictly worse than the disclosure": for
|
|
70803
|
+
* `Environment: [{Name:'DB_PASS', Value:<ref>}, {Name:'REGION', Value:'...'}]`
|
|
70804
|
+
* echoed back case-normalised and reordered, the baseline becomes
|
|
70805
|
+
* `[{Name:'REGION',...}, {Name:'***', Value:'***'}]`, and an index-aligned
|
|
70806
|
+
* revert copies `live[1]` into the masked slot — shipping a duplicated `REGION`
|
|
70807
|
+
* and DELETING the password variable from the live task definition.
|
|
70808
|
+
*
|
|
70809
|
+
* So the alignment must be one AWS's own reordering cannot break:
|
|
70810
|
+
* {@link identityKeyFor} — the same rule the redaction walk pairs by, so the
|
|
70811
|
+
* two can never disagree about which elements correspond. It is asked FIRST and
|
|
70812
|
+
* with NO length test, which is a change from the first cut of this function: a
|
|
70813
|
+
* length test ahead of it refuses a list AWS legitimately ADDED an element to,
|
|
70814
|
+
* while the keyed lookup needs no such partner — a `send` element whose identity
|
|
70815
|
+
* AWS does not report simply yields no live value, which is already the
|
|
70816
|
+
* refusing arm.
|
|
70817
|
+
*
|
|
70818
|
+
* WITHOUT AN IDENTITY FIELD the array's own literal FRAME has to vouch for the
|
|
70819
|
+
* order — the same corroboration `unkeyedArrayPairsByAnchors` requires on the
|
|
70820
|
+
* redaction side — and the test is PER-LEAF. Three rules, and the first cut of
|
|
70821
|
+
* this function had none of them right:
|
|
70822
|
+
*
|
|
70823
|
+
* - **the lengths must match**, positional alignment being all that is left;
|
|
70824
|
+
* - **every leaf of `send` that is not a mask must equal `live`'s leaf at the
|
|
70825
|
+
* same position** ({@link corroboratedLeafCount}), masked leaves wildcarded.
|
|
70826
|
+
* A whole-ELEMENT exemption is what shipped first, and it corroborated
|
|
70827
|
+
* NOTHING: {@link carriesSecretMask} is a DEEP, whole-subtree test, so an
|
|
70828
|
+
* element carrying one mask anywhere skipped the comparison entirely.
|
|
70829
|
+
* Measured on `[{Pw:'***',Role:'reader'},{Pw:'***',Role:'admin'}]` against a
|
|
70830
|
+
* live list AWS returned in the other order — the admin password was written
|
|
70831
|
+
* onto the reader entry and `provider.update` shipped it;
|
|
70832
|
+
* - **at most ONE element may carry a mask, and at least one non-mask leaf must
|
|
70833
|
+
* actually have been COMPARED.** Two masked slots leave the surviving literal
|
|
70834
|
+
* frame unable to say which mask goes where (two secret environment variables
|
|
70835
|
+
* in one container definition is the ordinary shape), and zero compared
|
|
70836
|
+
* leaves is the all-masked array {@link SECRET_MASK} produces when AWS
|
|
70837
|
+
* normalised the identity field too — there the old `every` was vacuously
|
|
70838
|
+
* true and the alignment rested on no evidence at all. With ONE masked slot
|
|
70839
|
+
* and every other position literally equal, the single live element left over
|
|
70840
|
+
* is the only value that can belong there.
|
|
70841
|
+
*
|
|
70842
|
+
* The `compared > 0` floor DELIBERATELY also refuses a single-element scalar
|
|
70843
|
+
* array — `[SECRET_MASK]` against one live value — even though one masked slot
|
|
70844
|
+
* against one live slot looks unambiguous. It is unambiguous only IF the live
|
|
70845
|
+
* list corresponds to this one, and with zero corroborated leaves the only
|
|
70846
|
+
* evidence for that is the length-1 match, which is no evidence: this is the
|
|
70847
|
+
* all-masked reading applied to its smallest shape, and the cost is a refusal
|
|
70848
|
+
* (the safe residual), not a wrong write. THAT COST SENTENCE IS THE MASK
|
|
70849
|
+
* WALK'S ALONE — for the token walk a refusal WRITES the token, so the same
|
|
70850
|
+
* floor inverts there and the singleton is accepted instead; see
|
|
70851
|
+
* `pairedLiveItems`' `acceptForcedSingleton` for the derivation.
|
|
70852
|
+
*
|
|
70853
|
+
* THE EVIDENCE MUST NOT BE MANUFACTURED BY A SIBLING PASS. `send` here must be
|
|
70854
|
+
* values the caller did not itself copy from `live` — see
|
|
70855
|
+
* {@link preserveLiveValuesAtMaskedLeaves}'s `corroborationSource` parameter
|
|
70856
|
+
* for the round-4 #2884 defect where the token pass's index-copied live values
|
|
70857
|
+
* corroborated their own indices.
|
|
70858
|
+
*
|
|
70859
|
+
* Refusing is the correct outcome for the MASK caller — it drops the whole
|
|
70860
|
+
* resource rather than send a guess. The TOKEN caller's refusal residual is
|
|
70861
|
+
* KEEPING the token (see below); both are the refusal `undefined` spells here,
|
|
70862
|
+
* and what it costs is the CALLER's contract, not this function's.
|
|
70863
|
+
*
|
|
70864
|
+
* PARAMETERISED WILDCARDS (issue #2893). This pairing now serves BOTH preserve
|
|
70865
|
+
* walks, because the two questions are identical — "which live element belongs
|
|
70866
|
+
* to this send element?" — and a second hand-rolled copy of the rule is
|
|
70867
|
+
* exactly how the `maskDeep` class of divergence starts. What differs per
|
|
70868
|
+
* caller is only WHICH leaf is the one being paired FOR (and so must be
|
|
70869
|
+
* wildcarded rather than compared) and what a refusal costs:
|
|
70870
|
+
*
|
|
70871
|
+
* - the MASK walk wildcards {@link SECRET_MASK} alone; a refusal drops the
|
|
70872
|
+
* resource, so over-refusal is a safe posture there;
|
|
70873
|
+
* - the TOKEN walk wildcards a WHOLE `{{resolve:...}}` token (its live
|
|
70874
|
+
* counterpart is the resolved value, so comparing would contradict on every
|
|
70875
|
+
* array that pass exists to serve) AND the mask (owned by the LATER mask
|
|
70876
|
+
* walk — comparing `***` against live would contradict spuriously, and
|
|
70877
|
+
* treating it as evidence would be wrong in the other direction). Its
|
|
70878
|
+
* refusal residual is keeping the token, which `provider.update` ships —
|
|
70879
|
+
* a no-op for a record cdkd deployed, a preserved pre-existing breakage
|
|
70880
|
+
* otherwise, and in both cases better than another element's live value
|
|
70881
|
+
* copied into the wrong position.
|
|
70882
|
+
*
|
|
70883
|
+
* `carriesWildcard` must be the DEEP form of `wildcardLeaf` (an element
|
|
70884
|
+
* carrying a wildcard anywhere is a slot whose alignment part-rests on
|
|
70885
|
+
* abstention), and the one-slot bound applies to it: with two such elements
|
|
70886
|
+
* the surviving literal frame cannot say which wildcard goes where.
|
|
70887
|
+
*
|
|
70888
|
+
* `acceptForcedSingleton` (PR #2912 review, blocker 1) is where the two
|
|
70889
|
+
* callers' refusal COSTS meet the evidence rules, and it exists because a
|
|
70890
|
+
* rule that is sound for one arm BECAUSE of what its refusal costs cannot be
|
|
70891
|
+
* shared without re-deriving that cost. At `send.length === 1` against
|
|
70892
|
+
* `live.length === 1` no evidence question remains OPEN: the position is
|
|
70893
|
+
* FORCED (there is no other candidate to mis-pair with), and a positional
|
|
70894
|
+
* copy writes each live value back to its own position, so no
|
|
70895
|
+
* cross-POSITION donation — the credential-swap class every bound above
|
|
70896
|
+
* exists to stop — is constructible. Cross-IDENTITY donation is: the
|
|
70897
|
+
* bypass sits above the identity arm and its wildcard-identity guard, so
|
|
70898
|
+
* `[{Name:'DB', Value:<token>}]` against a live `[{Name:'OTHER', Value:'X'}]`
|
|
70899
|
+
* ships `{Name:'DB', Value:'X'}` — a value from a differently-identified
|
|
70900
|
+
* element. That is position-neutral (X lands at the exact position AWS
|
|
70901
|
+
* holds it), it is what the pre-#2893 positional descent did for every such
|
|
70902
|
+
* array, and the alternative writes the token over X; the trade is stated
|
|
70903
|
+
* here rather than claimed away. What the evidence rules still doubt at
|
|
70904
|
+
* 1-vs-1 is only "does this live list correspond to this send list at
|
|
70905
|
+
* all", and the two callers answer that doubt oppositely because their
|
|
70906
|
+
* refusal residuals invert:
|
|
70907
|
+
*
|
|
70908
|
+
* - the MASK walk refuses (`false`, the default): its residual is dropping
|
|
70909
|
+
* the resource, so doubt costs a refusal — the safe direction, and the
|
|
70910
|
+
* `[SECRET_MASK]`-vs-one-live-value reading its own doc defends;
|
|
70911
|
+
* - the TOKEN walk accepts (`true`): its residual WRITES the literal token
|
|
70912
|
+
* over whatever AWS holds (`provider.update` ships it — the #1914
|
|
70913
|
+
* corruption), so refusing on a forced position DESTROYS the live value a
|
|
70914
|
+
* pairing would have preserved, while accepting is write-neutral even when
|
|
70915
|
+
* the correspondence doubt is real (the copied value lands at the exact
|
|
70916
|
+
* position AWS already holds it). Refusal buys nothing and costs a
|
|
70917
|
+
* destructive write, so the token walk takes the pairing — which is also
|
|
70918
|
+
* exactly the pre-#2893 behaviour for every 1-vs-1 array.
|
|
70919
|
+
*
|
|
70920
|
+
* The bypass sits ABOVE the identity arm deliberately: at 1-vs-1 an identity
|
|
70921
|
+
* mismatch (AWS normalised the one element's Name) changes which EVIDENCE
|
|
70922
|
+
* rule fails, not the forced position, and routing it through the identity
|
|
70923
|
+
* arm would re-open the same inverted cost through a different door. Every
|
|
70924
|
+
* bound below is untouched for `length >= 2`, where mis-pairing is real.
|
|
70925
|
+
*/
|
|
70926
|
+
function pairedLiveItems(send, live, wildcardLeaf = isMaskLeaf, carriesWildcard = carriesSecretMask, acceptForcedSingleton = false) {
|
|
70927
|
+
if (!Array.isArray(live)) return void 0;
|
|
70928
|
+
if (acceptForcedSingleton && send.length === 1 && live.length === 1) return live;
|
|
70929
|
+
const key = identityKeyFor(send, live);
|
|
70930
|
+
if (key !== void 0) {
|
|
70931
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
70932
|
+
for (const item of live) byIdentity.set(item[key], item);
|
|
70933
|
+
return send.map((item) => {
|
|
70934
|
+
const identity = item[key];
|
|
70935
|
+
return wildcardLeaf(identity) ? void 0 : byIdentity.get(identity);
|
|
70936
|
+
});
|
|
70937
|
+
}
|
|
70938
|
+
if (live.length !== send.length) return void 0;
|
|
70939
|
+
let wildcardSlots = 0;
|
|
70940
|
+
let compared = 0;
|
|
70941
|
+
for (let i = 0; i < send.length; i++) {
|
|
70942
|
+
if (carriesWildcard(send[i])) wildcardSlots++;
|
|
70943
|
+
const corroborated = corroboratedLeafCount(send[i], live[i], wildcardLeaf);
|
|
70944
|
+
if (corroborated === void 0) return void 0;
|
|
70945
|
+
compared += corroborated;
|
|
70946
|
+
}
|
|
70947
|
+
if (wildcardSlots > 1 || compared === 0) return void 0;
|
|
70948
|
+
return live;
|
|
70949
|
+
}
|
|
70950
|
+
/** The MASK walk's wildcard: the redaction mask alone. */
|
|
70951
|
+
function isMaskLeaf(leaf) {
|
|
70952
|
+
return leaf === "***";
|
|
70953
|
+
}
|
|
70954
|
+
/**
|
|
70955
|
+
* The TOKEN walk's wildcard: a leaf whose WHOLE value is a `{{resolve:...}}`
|
|
70956
|
+
* token, plus the mask (which that walk never touches but must abstain on —
|
|
70957
|
+
* see {@link pairedLiveItems}). A MIXED leaf (a token embedded in a longer
|
|
70958
|
+
* string) is deliberately NOT a wildcard: the token walk does not preserve it
|
|
70959
|
+
* either, and its live counterpart genuinely differs, so letting it abstain
|
|
70960
|
+
* would make every partially-resolved string an unbounded free pass.
|
|
70961
|
+
*/
|
|
70962
|
+
function isTokenOrMaskLeaf(leaf) {
|
|
70963
|
+
if (leaf === "***") return true;
|
|
70964
|
+
return typeof leaf === "string" && leaf.includes("{{resolve:") && isSingleDynamicReferenceToken(leaf);
|
|
70965
|
+
}
|
|
70966
|
+
/**
|
|
70967
|
+
* Deep form of {@link isTokenOrMaskLeaf}, for the one-slot bound.
|
|
70968
|
+
*
|
|
70969
|
+
* Cycle-guarded like its sibling `carriesSecretMask`, via the same inner
|
|
70970
|
+
* closure so the visited set is not a callable parameter (PR #2912 review):
|
|
70971
|
+
* the value walked here is an element of `buildRevertNewProperties`' output,
|
|
70972
|
+
* which carries RAW `readCurrentState` returns — provider-authored, not
|
|
70973
|
+
* JSON-round-tripped — so a self-referential object must answer `false`
|
|
70974
|
+
* rather than throw `RangeError` out of the pairing. The guard bounds the
|
|
70975
|
+
* PAIRING frame only: a cyclic PLAIN element still overflows the enclosing
|
|
70976
|
+
* preserve walk one frame later (pre-existing, caught by `runRevert`'s
|
|
70977
|
+
* per-resource payload-build catch); what this guard keeps alive is the
|
|
70978
|
+
* NON-PLAIN cyclic element, which that walk returns by identity and only
|
|
70979
|
+
* this slot count ever descends.
|
|
70980
|
+
*/
|
|
70981
|
+
function carriesTokenOrMask(value) {
|
|
70982
|
+
const seen = /* @__PURE__ */ new Set();
|
|
70983
|
+
const walkNode = (node) => {
|
|
70984
|
+
if (isTokenOrMaskLeaf(node)) return true;
|
|
70985
|
+
if (node === null || typeof node !== "object") return false;
|
|
70986
|
+
if (seen.has(node)) return false;
|
|
70987
|
+
seen.add(node);
|
|
70988
|
+
if (Array.isArray(node)) return node.some(walkNode);
|
|
70989
|
+
return Object.values(node).some(walkNode);
|
|
70990
|
+
};
|
|
70991
|
+
return walkNode(value);
|
|
70992
|
+
}
|
|
70993
|
+
/**
|
|
70994
|
+
* How many of `send`'s leaves the live side CORROBORATES, or `undefined` the
|
|
70995
|
+
* moment one of them CONTRADICTS it.
|
|
70996
|
+
*
|
|
70997
|
+
* A WILDCARD leaf (the caller's `wildcardLeaf` — the mask for the mask walk,
|
|
70998
|
+
* a whole token or the mask for the token walk) contributes 0 and is never
|
|
70999
|
+
* compared: it is the value being paired FOR, so requiring it to match would
|
|
71000
|
+
* refuse every array {@link pairedLiveItems} exists to align. Everything else
|
|
71001
|
+
* must agree — including the container SHAPE around it, since a subtree of a
|
|
71002
|
+
* different shape is a contradiction rather than an absent comparison.
|
|
71003
|
+
*
|
|
71004
|
+
* A COUNT rather than a boolean, because "nothing contradicted me" is exactly
|
|
71005
|
+
* the answer an all-wildcard array gives and it is not evidence. The caller
|
|
71006
|
+
* requires a non-zero count before it reads the order as corroborated.
|
|
71007
|
+
*/
|
|
71008
|
+
function corroboratedLeafCount(send, live, wildcardLeaf = isMaskLeaf) {
|
|
71009
|
+
if (wildcardLeaf(send)) return 0;
|
|
71010
|
+
if (Array.isArray(send)) {
|
|
71011
|
+
if (!Array.isArray(live) || live.length !== send.length) return void 0;
|
|
71012
|
+
let total = 0;
|
|
71013
|
+
for (let i = 0; i < send.length; i++) {
|
|
71014
|
+
const corroborated = corroboratedLeafCount(send[i], live[i], wildcardLeaf);
|
|
71015
|
+
if (corroborated === void 0) return void 0;
|
|
71016
|
+
total += corroborated;
|
|
71017
|
+
}
|
|
71018
|
+
return total;
|
|
71019
|
+
}
|
|
71020
|
+
if (isPlainRecord(send)) {
|
|
71021
|
+
if (!hasPlainPrototype(send)) return void 0;
|
|
71022
|
+
if (!isPlainRecord(live) || !hasPlainPrototype(live)) return void 0;
|
|
71023
|
+
const keys = Object.keys(send);
|
|
71024
|
+
if (keys.length !== Object.keys(live).length) return void 0;
|
|
71025
|
+
let total = 0;
|
|
71026
|
+
for (const k of keys) {
|
|
71027
|
+
if (!Object.prototype.hasOwnProperty.call(live, k)) return void 0;
|
|
71028
|
+
const corroborated = corroboratedLeafCount(send[k], live[k], wildcardLeaf);
|
|
71029
|
+
if (corroborated === void 0) return void 0;
|
|
71030
|
+
total += corroborated;
|
|
71031
|
+
}
|
|
71032
|
+
return total;
|
|
71033
|
+
}
|
|
71034
|
+
return deepEqualUnordered(send, live) ? 1 : void 0;
|
|
71035
|
+
}
|
|
71036
|
+
/**
|
|
70669
71037
|
* Keep `cdkd drift --revert` from pushing a REDACTION MASK to AWS (issue
|
|
70670
71038
|
* [#2274](https://github.com/go-to-k/cdkd/issues/2274)).
|
|
70671
71039
|
*
|
|
@@ -70689,9 +71057,16 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
70689
71057
|
* revision's reason for not doing so was wrong: it said "the live value is one
|
|
70690
71058
|
* cdkd never resolved and cannot recognise, so there is no needle to record".
|
|
70691
71059
|
* The needle is `liveValue -> {@link SECRET_MASK}`, i.e. a MASK-ONLY entry, and
|
|
70692
|
-
* the evidence for it is the position —
|
|
70693
|
-
*
|
|
70694
|
-
* a leaf.
|
|
71060
|
+
* the evidence for it is the position — the masked position proves the value
|
|
71061
|
+
* is ONE CDKD CHOSE TO MASK, since nothing but this module's own redaction
|
|
71062
|
+
* puts a mask at a leaf. That is deliberately weaker than "proof the value is
|
|
71063
|
+
* secret" (issue #2881): a #2852 fail-closed mask deliberately over-masks a
|
|
71064
|
+
* value the walk merely could not certify — a normalised `US-EAST-1` as
|
|
71065
|
+
* readily as a credential — so the live value registered here may be an
|
|
71066
|
+
* ordinary literal. The consequence is bounded to over-masking log output,
|
|
71067
|
+
* the safe direction, and registering it is still right: when the position
|
|
71068
|
+
* DOES hold a secret, the registration is what keeps the moved plaintext out
|
|
71069
|
+
* of every reader below. Without it this function copies live plaintext into the
|
|
70695
71070
|
* send bag and then `collectNarrowedTopLevelKeys` persists that delta into
|
|
70696
71071
|
* `observedProperties` against a `secrets` map holding no entry for it — the
|
|
70697
71072
|
* same disclosure the mask exists to prevent, arriving through the mechanism
|
|
@@ -70718,17 +71093,58 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
|
|
|
70718
71093
|
* would be the corruption; dropping the key would delete a property the
|
|
70719
71094
|
* resource may require.
|
|
70720
71095
|
*
|
|
70721
|
-
* Array descent is
|
|
70722
|
-
*
|
|
70723
|
-
*
|
|
70724
|
-
*
|
|
70725
|
-
*
|
|
70726
|
-
*
|
|
70727
|
-
|
|
70728
|
-
|
|
71096
|
+
* Array descent is NOT positional and does NOT bail on a length mismatch —
|
|
71097
|
+
* {@link pairedLiveItems} owns that question, for BOTH walks since issue
|
|
71098
|
+
* #2893 (the sibling's earlier answer, index alignment behind a length test,
|
|
71099
|
+
* was wrong by construction on the population that produces these masks, and
|
|
71100
|
+
* wrong under any reordered readback for the sibling's own tokens). An
|
|
71101
|
+
* earlier revision of this paragraph still claimed the sibling's rule after
|
|
71102
|
+
* the pairing had changed underneath it.
|
|
71103
|
+
*
|
|
71104
|
+
* `corroborationSource` is the bag the PAIRING EVIDENCE is read from, and it
|
|
71105
|
+
* exists because of a round-4 #2884 defect: `runRevert` runs
|
|
71106
|
+
* {@link preserveLiveValuesAtUnresolvedTokens} FIRST, and that pass copies
|
|
71107
|
+
* `live` values into whole-token leaves — BY INDEX when the defect was found;
|
|
71108
|
+
* by certified pairing since issue #2893, which does not retire this
|
|
71109
|
+
* parameter, because a copied leaf is trivially deep-equal to `live` at its
|
|
71110
|
+
* own position HOWEVER it was chosen, so corroborating `send` (the post-token
|
|
71111
|
+
* bag) against that same `live` still counts evidence a sibling pass
|
|
71112
|
+
* manufactured rather than evidence AWS's readback supplied. Under the
|
|
71113
|
+
* original index copies, an unkeyed array
|
|
71114
|
+
* whose only non-mask leaves were tokens satisfied both the
|
|
71115
|
+
* `compared > 0` floor and the one-mask bound on evidence the sibling pass
|
|
71116
|
+
* manufactured, and a reordered `live` wrote another element's secret into the
|
|
71117
|
+
* masked slot — the credential-swap class the guards were added for. The same
|
|
71118
|
+
* fabrication reached the IDENTITY arm: an identity field that was itself a
|
|
71119
|
+
* token took its live value by index, and {@link pairedLiveItems}' keyed
|
|
71120
|
+
* lookup then "paired" it right back to that index. So the caller passes the
|
|
71121
|
+
* PRE-token bag here; both the identity lookup and the per-leaf corroboration
|
|
71122
|
+
* run over values no sibling pass copied from `live`, a token leaf compares
|
|
71123
|
+
* against the resolved live value and CONTRADICTS (its live counterpart is
|
|
71124
|
+
* unknowable, and an abstention would make token leaves unbounded wildcards
|
|
71125
|
+
* beside the deliberately bounded one-mask rule), and the array refuses. The
|
|
71126
|
+
* masked VALUES the walk writes still come from `send` — only the evidence
|
|
71127
|
+
* source changes. Defaults to `send` (self-corroboration, the pre-round-4
|
|
71128
|
+
* behaviour) for a caller with no sibling pass; a structural mismatch between
|
|
71129
|
+
* the two bags — which the token pass CAN produce, by returning the live
|
|
71130
|
+
* CONTAINER AWS holds over a whole-token string leaf — refuses the masks
|
|
71131
|
+
* beneath it rather than falling back to the fabricable bag. That refusal is
|
|
71132
|
+
* a deliberate fail-closed cost, not a reachable loss: the mismatching
|
|
71133
|
+
* subtree is live-derived and carries no mask, so nothing preservable is
|
|
71134
|
+
* refused today.
|
|
71135
|
+
*
|
|
71136
|
+
* Returns the input bag BY IDENTITY when it holds no mask — checked FIRST via
|
|
71137
|
+
* {@link carriesSecretMask}, so a mask-free revert (the ordinary case) pays
|
|
71138
|
+
* one boolean walk instead of a full rebuild plus per-array pairing.
|
|
71139
|
+
*/
|
|
71140
|
+
function preserveLiveValuesAtMaskedLeaves(send, awsProperties, secrets, corroborationSource = send) {
|
|
71141
|
+
if (!carriesSecretMask(send)) return {
|
|
71142
|
+
properties: send,
|
|
71143
|
+
unpreservablePaths: []
|
|
71144
|
+
};
|
|
70729
71145
|
const unpreservablePaths = [];
|
|
70730
71146
|
let changed = false;
|
|
70731
|
-
const walk = (value, live, path) => {
|
|
71147
|
+
const walk = (value, corr, live, path) => {
|
|
70732
71148
|
if (value === "***") {
|
|
70733
71149
|
if (live === void 0) {
|
|
70734
71150
|
unpreservablePaths.push(path);
|
|
@@ -70739,18 +71155,24 @@ function preserveLiveValuesAtMaskedLeaves(send, awsProperties, secrets) {
|
|
|
70739
71155
|
return live;
|
|
70740
71156
|
}
|
|
70741
71157
|
if (Array.isArray(value)) {
|
|
70742
|
-
const
|
|
70743
|
-
|
|
71158
|
+
const corrItems = Array.isArray(corr) && corr.length === value.length ? corr : void 0;
|
|
71159
|
+
const liveItems = corrItems === void 0 ? void 0 : pairedLiveItems(corrItems, live);
|
|
71160
|
+
return value.map((item, i) => walk(item, corrItems?.[i], liveItems?.[i], `${path}[${i}]`));
|
|
70744
71161
|
}
|
|
70745
71162
|
if (value !== null && typeof value === "object") {
|
|
71163
|
+
if (!hasPlainPrototype(value)) {
|
|
71164
|
+
if (carriesSecretMask(value)) unpreservablePaths.push(path);
|
|
71165
|
+
return value;
|
|
71166
|
+
}
|
|
70746
71167
|
const liveObject = isPlainRecord(live) ? live : void 0;
|
|
70747
|
-
const
|
|
70748
|
-
|
|
71168
|
+
const corrObject = isPlainRecord(corr) ? corr : void 0;
|
|
71169
|
+
const out = Object.create(null);
|
|
71170
|
+
for (const [k, v] of Object.entries(value)) out[k] = walk(v, corrObject?.[k], liveObject?.[k], path === "" ? k : `${path}.${k}`);
|
|
70749
71171
|
return out;
|
|
70750
71172
|
}
|
|
70751
71173
|
return value;
|
|
70752
71174
|
};
|
|
70753
|
-
const properties = walk(send, awsProperties, "");
|
|
71175
|
+
const properties = walk(send, corroborationSource, awsProperties, "");
|
|
70754
71176
|
if (!changed && unpreservablePaths.length === 0) return {
|
|
70755
71177
|
properties: send,
|
|
70756
71178
|
unpreservablePaths
|
|
@@ -70997,15 +71419,28 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
70997
71419
|
return;
|
|
70998
71420
|
}
|
|
70999
71421
|
const unrevertablePaths = outcome.changes.map((c) => c.path).filter((path) => (path.split(".", 1)[0] ?? "").includes("***"));
|
|
71000
|
-
if (
|
|
71001
|
-
|
|
71422
|
+
if (stateResource.observedProperties === void 0) {
|
|
71423
|
+
const driftedTopLevelKeys = /* @__PURE__ */ new Set();
|
|
71424
|
+
for (const change of outcome.changes) {
|
|
71425
|
+
const topLevelKey = change.path.split(".", 1)[0];
|
|
71426
|
+
if (topLevelKey) driftedTopLevelKeys.add(topLevelKey);
|
|
71427
|
+
}
|
|
71428
|
+
const intrinsicObjectPaths = collectUnresolvedIntrinsicObjectPaths(desiredProperties, driftedTopLevelKeys);
|
|
71429
|
+
if (intrinsicObjectPaths.length > 0) {
|
|
71430
|
+
totalUnresolvable++;
|
|
71431
|
+
logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskSecretsInText(intrinsicObjectPaths.join(", "), secrets)} — the recorded baseline holds an unresolved CloudFormation intrinsic there (e.g. Fn::Join, Ref), which cdkd cannot resolve outside a deploy; writing it would set the live property to the raw intrinsic object instead of its value. Run 'cdkd deploy' for this stack — the deploy resolves the template and records a resolvable baseline — then re-run the revert if drift remains.`);
|
|
71432
|
+
return;
|
|
71433
|
+
}
|
|
71434
|
+
}
|
|
71435
|
+
if (unrevertablePaths.length > 0) logger.warn(` ! ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): cannot revert ${maskSecretsInText(unrevertablePaths.join(", "), secrets)} — cdkd cannot name the property, so it is left as AWS has it. ROTATE the secret that leaked into the property name.`);
|
|
71436
|
+
if (unresolvedTokens.size > 0) logger.warn(` ! [revert] ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — a property whose WHOLE value is one of these tokens is left UNCHANGED by this revert when cdkd can pair its position against AWS's report (a list element by an identity field, or by its list's own unchanged literal values). Where it cannot pair, and where a token is EMBEDDED in a longer string, the token is written literally, exactly as 'cdkd deploy' does, so a resolved value AWS holds there is overwritten.`);
|
|
71002
71437
|
let newProperties;
|
|
71003
71438
|
try {
|
|
71004
71439
|
const overlaid = buildRevertNewProperties(outcome.changes, desiredProperties, outcome.awsProperties, { preserveUntemplated: stateResource.observedProperties === void 0 });
|
|
71005
|
-
const maskPreserved = preserveLiveValuesAtMaskedLeaves(unresolvedTokens.size > 0 ? preserveLiveValuesAtUnresolvedTokens(overlaid, outcome.awsProperties) : overlaid, outcome.awsProperties, secrets);
|
|
71440
|
+
const maskPreserved = preserveLiveValuesAtMaskedLeaves(unresolvedTokens.size > 0 ? preserveLiveValuesAtUnresolvedTokens(overlaid, outcome.awsProperties) : overlaid, outcome.awsProperties, secrets, overlaid);
|
|
71006
71441
|
if (maskPreserved.unpreservablePaths.length > 0) {
|
|
71007
71442
|
totalUnresolvable++;
|
|
71008
|
-
logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskPreserved.unpreservablePaths.join(", ")} — the recorded baseline holds only the redaction mask there
|
|
71443
|
+
logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskSecretsInText(maskPreserved.unpreservablePaths.join(", "), secrets)} — the recorded baseline holds only the redaction mask there, and AWS reports nothing to preserve, so cdkd has no value it may write. Two causes leave such a mask, and the record does not say which: a NoEcho custom-resource value (force that custom resource to update — change one of its properties, e.g. a nonce — and re-deploy, so its handler runs again and supplies the value), or a readback position cdkd could not certify when the baseline was captured (deploy a change to this resource, so the baseline is recaptured against the template). An ordinary no-change re-deploy clears neither.`);
|
|
71009
71444
|
return;
|
|
71010
71445
|
}
|
|
71011
71446
|
newProperties = maskPreserved.properties;
|
|
@@ -71102,7 +71537,7 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
71102
71537
|
if (totalFailed > 0) summaryParts.push(`${totalFailed} failed`);
|
|
71103
71538
|
logger.info(`\nRevert summary: ${summaryParts.join(", ")}.`);
|
|
71104
71539
|
if (totalUnsupported > 0) logger.warn(`${totalUnsupported} resource(s) cannot be reverted in place — re-deploy the stack with cdkd deploy --replace, or destroy + redeploy to push the cdkd-state values back into AWS.`);
|
|
71105
|
-
if (totalFailed > 0 || totalUnsupported > 0 || totalUnresolvable > 0) throw new PartialFailureError(`Revert completed with ${totalFailed + totalUnsupported + totalUnresolvable} resource error(s) (${totalFailed} AWS update failure(s), ${totalUnsupported} update-not-supported, ${totalUnresolvable}
|
|
71540
|
+
if (totalFailed > 0 || totalUnsupported > 0 || totalUnresolvable > 0) throw new PartialFailureError(`Revert completed with ${totalFailed + totalUnsupported + totalUnresolvable} resource error(s) (${totalFailed} AWS update failure(s), ${totalUnsupported} update-not-supported, ${totalUnresolvable} refused or unresolvable — those never reached provider.update; each per-resource message above names its cause and remedy, e.g. missing secretsmanager:GetSecretValue / ssm:GetParameter grants for an unresolvable reference). Re-run 'cdkd drift <stack>' to see the remaining drift, then 'cdkd drift <stack> --revert' to retry.`);
|
|
71106
71541
|
}
|
|
71107
71542
|
function humanTextSink(json) {
|
|
71108
71543
|
return json ? {
|
|
@@ -76980,7 +77415,7 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
|
|
|
76980
77415
|
unsupported++;
|
|
76981
77416
|
return;
|
|
76982
77417
|
}
|
|
76983
|
-
resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS$1, resource.properties ?? {},
|
|
77418
|
+
resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS$1, resource.properties ?? {}, STATE_SOURCED_BASELINE_RULES);
|
|
76984
77419
|
refreshed++;
|
|
76985
77420
|
} catch (err) {
|
|
76986
77421
|
failed++;
|
|
@@ -90417,4 +90852,4 @@ function buildProgram() {
|
|
|
90417
90852
|
|
|
90418
90853
|
//#endregion
|
|
90419
90854
|
export { buildProgram };
|
|
90420
|
-
//# sourceMappingURL=program-
|
|
90855
|
+
//# sourceMappingURL=program-CMhpftan.js.map
|