@go-to-k/cdkd 0.278.37 → 0.278.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { $ as describeTypeWithThrottleRetry, $t as AwsClients, A as red, At as Synthesizer, B as isTerminationProtectionPropagationError, Bt as resolveUseCdkBootstrapAssets, C as isStatefulRecreateTargetSync, Ct as buildDockerImage, D as cyan, Dt as runDockerStreaming, E as bold, Et as runDockerForeground, F as ProviderRegistry, Ft as resolveAutoAssetStorage, G as normalizeAwsTagsToCfn, Gt as MIGRATE_TMP_PREFIX, H as cfnRefValueFromPhysicalId, Ht as warnDeprecatedNoPrefixCliFlag, I as findActionableSilentDrops, It as resolveCaptureObservedState, J as readConfigString, Jt as expectedOwnerParam, K as resolveExplicitPhysicalId, Kt as findLargeInlineResources, L as CloudControlProvider, Lt as resolveSkipPrefix, M as IAMRoleProvider, Mt as getDefaultStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as getLegacyStateBucketName, O as gray, Ot as AssetManifestLoader, P as clearOnUpdateRemoval, Pt as resolveApp, Q as DiffCalculator, Qt as resolveBucketRegion, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefault, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateContainerRepoName, T as formatResourceLine, Tn as withErrorHandling, Tt as getDockerCmd, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_BODY_LIMIT, V as IntrinsicFunctionResolver, Vt as stateBucketExistenceConfirmed, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_URL_LIMIT, X as requireConfigString, Xt as processStackMessages, Y as requireConfigArray, Yt as AssemblyReader, Z as applyRoleArnIfSet, _ as createPreDeleteFinalSnapshot, _n as ResourceUpdateNotSupportedError, _t as BOOTSTRAP_MARKER_PREFIX, a as DeploymentEventsStore, at as LockManager, b as unsupportedFinalSnapshotError, bt as parseBootstrapMarker, c as replayFailedOperations, cn as LocalInvokeBuildError$1, ct as shouldRetainResource, d as IMPLICIT_DELETE_DEPENDENCIES, dt as WorkGraph, en as getAwsClients, et as withRetry, f as computeImplicitDeleteEdges, fn as MissingCdkCliError, ft as buildAssetRedirectMap, g as ccRoutedFinalSnapshotError, gn as ResourceTimeoutError, gt as AssetModeResolver, h as buildFinalSnapshotIdentifier, hn as ProvisioningError, ht as rewriteTemplateAssetReferences, i as DeploymentEventsReader, in as CdkdError, it as TemplateParser, j as yellow, jt as synthesisStatusMessage, k as green, kt as getDockerImageBySourceHash, l as replayRollback, ln as LocalMigrateError, lt as AssetPublisher, m as PRE_DELETE_SNAPSHOT_TYPES, mn as PartialFailureError, mt as loadPublishableAssetManifest, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as setAwsClients, nt as isThrottlingError, o as planFailedOps, ot as S3StateBackend, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as NestedStackChildDirectDestroyError, pt as createAssetRedirectResolver, q as assertRegionMatch, qt as uploadCfnTemplate, r as DeployEngine, rt as DagBuilder, s as planRollback, sn as DeployCancelledError, st as rebuildClientForBucketRegion, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resetAwsClients, tt as isRetryableTransientError, u as withResourceDeadline, un as LocalStartServiceError, ut as stringifyValue, v as isFinalSnapshotError, vn as StackHasActiveImportsError, vt as ensureAssetStorage, w as renderStatefulReason, wn as normalizeAwsError, wt as formatDockerLoginError, x as extractDeploymentEventError, xt as validateAssetBucketName, y as refusesFinalSnapshot, yn as StackTerminationProtectionError, yt as getBootstrapMarkerKey, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefaultAndSource } from "./deploy-engine-bEhHOGVR.js";
2
+ import { $ as describeTypeWithThrottleRetry, $t as AwsClients, A as red, At as Synthesizer, B as isTerminationProtectionPropagationError, Bt as resolveUseCdkBootstrapAssets, C as isStatefulRecreateTargetSync, Ct as buildDockerImage, D as cyan, Dt as runDockerStreaming, E as bold, Et as runDockerForeground, F as ProviderRegistry, Ft as resolveAutoAssetStorage, G as normalizeAwsTagsToCfn, Gt as MIGRATE_TMP_PREFIX, H as cfnRefValueFromPhysicalId, Ht as warnDeprecatedNoPrefixCliFlag, I as findActionableSilentDrops, It as resolveCaptureObservedState, J as readConfigString, Jt as expectedOwnerParam, K as resolveExplicitPhysicalId, Kt as findLargeInlineResources, L as CloudControlProvider, Lt as resolveSkipPrefix, M as IAMRoleProvider, Mt as getDefaultStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as getLegacyStateBucketName, O as gray, Ot as AssetManifestLoader, P as clearOnUpdateRemoval, Pt as resolveApp, Q as DiffCalculator, Qt as resolveBucketRegion, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefault, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateContainerRepoName, T as formatResourceLine, Tn as withErrorHandling, Tt as getDockerCmd, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_BODY_LIMIT, V as IntrinsicFunctionResolver, Vt as stateBucketExistenceConfirmed, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_URL_LIMIT, X as requireConfigString, Xt as processStackMessages, Y as requireConfigArray, Yt as AssemblyReader, Z as applyRoleArnIfSet, _ as createPreDeleteFinalSnapshot, _n as ResourceUpdateNotSupportedError, _t as BOOTSTRAP_MARKER_PREFIX, a as DeploymentEventsStore, at as LockManager, b as unsupportedFinalSnapshotError, bt as parseBootstrapMarker, c as replayFailedOperations, cn as LocalInvokeBuildError$1, ct as shouldRetainResource, d as IMPLICIT_DELETE_DEPENDENCIES, dt as WorkGraph, en as getAwsClients, et as withRetry, f as computeImplicitDeleteEdges, fn as MissingCdkCliError, ft as buildAssetRedirectMap, g as ccRoutedFinalSnapshotError, gn as ResourceTimeoutError, gt as AssetModeResolver, h as buildFinalSnapshotIdentifier, hn as ProvisioningError, ht as rewriteTemplateAssetReferences, i as DeploymentEventsReader, in as CdkdError, it as TemplateParser, j as yellow, jt as synthesisStatusMessage, k as green, kt as getDockerImageBySourceHash, l as replayRollback, ln as LocalMigrateError, lt as AssetPublisher, m as PRE_DELETE_SNAPSHOT_TYPES, mn as PartialFailureError, mt as loadPublishableAssetManifest, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as setAwsClients, nt as isThrottlingError, o as planFailedOps, ot as S3StateBackend, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as NestedStackChildDirectDestroyError, pt as createAssetRedirectResolver, q as assertRegionMatch, qt as uploadCfnTemplate, r as DeployEngine, rt as DagBuilder, s as planRollback, sn as DeployCancelledError, st as rebuildClientForBucketRegion, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resetAwsClients, tt as isRetryableTransientError, u as withResourceDeadline, un as LocalStartServiceError, ut as stringifyValue, v as isFinalSnapshotError, vn as StackHasActiveImportsError, vt as ensureAssetStorage, w as renderStatefulReason, wn as normalizeAwsError, wt as formatDockerLoginError, x as extractDeploymentEventError, xt as validateAssetBucketName, y as refusesFinalSnapshot, yn as StackTerminationProtectionError, yt as getBootstrapMarkerKey, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefaultAndSource } from "./deploy-engine-DIvnBeMj.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as withSkipPrefix, i as runStackBuffered, n as getLogger, o as PATTERN_B_NAME_OPTIONS, p as withStackName, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-BYMEE-BS.js";
4
- import { t as ASGProvider } from "./asg-provider-BTeSlton.js";
4
+ import { t as ASGProvider } from "./asg-provider-CaulrRij.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";
8
- import { AddRoleToInstanceProfileCommand, AddUserToGroupCommand, AttachGroupPolicyCommand, AttachRolePolicyCommand, AttachUserPolicyCommand, CreateAccessKeyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreatePolicyCommand, CreatePolicyVersionCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeletePolicyCommand, DeletePolicyVersionCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachRolePolicyCommand, DetachUserPolicyCommand, GetAccessKeyLastUsedCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetPolicyCommand, GetPolicyVersionCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListEntitiesForPolicyCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListPoliciesCommand, ListPolicyTagsCommand, ListPolicyVersionsCommand, ListUserPoliciesCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagPolicyCommand, TagUserCommand, UntagPolicyCommand, UntagUserCommand, UpdateAccessKeyCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
8
+ import { AddRoleToInstanceProfileCommand, AddUserToGroupCommand, AttachGroupPolicyCommand, AttachRolePolicyCommand, AttachUserPolicyCommand, CreateAccessKeyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreatePolicyCommand, CreatePolicyVersionCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeletePolicyCommand, DeletePolicyVersionCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachRolePolicyCommand, DetachUserPolicyCommand, GetAccessKeyLastUsedCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetPolicyCommand, GetPolicyVersionCommand, GetRoleCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListEntitiesForPolicyCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListPoliciesCommand, ListPolicyTagsCommand, ListPolicyVersionsCommand, ListUserPoliciesCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagPolicyCommand, TagUserCommand, UntagPolicyCommand, UntagUserCommand, UpdateAccessKeyCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
9
9
  import { CreateQueueCommand, DeleteQueueCommand, GetQueueAttributesCommand, GetQueueUrlCommand, ListQueueTagsCommand, QueueDoesNotExist, SQSClient, SetQueueAttributesCommand, TagQueueCommand, UntagQueueCommand } from "@aws-sdk/client-sqs";
10
10
  import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesCommand, GetTopicAttributesCommand, InvalidParameterException, ListSubscriptionsByTopicCommand, ListTagsForResourceCommand, ListTopicsCommand, NotFoundException, SNSClient, SetTopicAttributesCommand, SubscribeCommand, TagResourceCommand, UnsubscribeCommand, UntagResourceCommand } from "@aws-sdk/client-sns";
11
11
  import { AddPermissionCommand, CreateEventSourceMappingCommand, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteEventSourceMappingCommand, DeleteFunctionCommand, DeleteFunctionConcurrencyCommand, DeleteFunctionEventInvokeConfigCommand, DeleteFunctionUrlConfigCommand, DeleteLayerVersionCommand, GetEventSourceMappingCommand, GetFunctionCommand, GetFunctionConcurrencyCommand, GetFunctionEventInvokeConfigCommand, GetFunctionRecursionConfigCommand, GetFunctionUrlConfigCommand, GetLayerVersionByArnCommand, GetPolicyCommand as GetPolicyCommand$1, LambdaClient, ListTagsCommand, PublishLayerVersionCommand, PutFunctionConcurrencyCommand, PutFunctionEventInvokeConfigCommand, PutFunctionRecursionConfigCommand, RemovePermissionCommand, ResourceNotFoundException, TagResourceCommand as TagResourceCommand$1, UntagResourceCommand as UntagResourceCommand$1, UpdateEventSourceMappingCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
@@ -49069,6 +49069,208 @@ function isPlainObject(value) {
49069
49069
  return typeof value === "object" && value !== null;
49070
49070
  }
49071
49071
 
49072
+ //#endregion
49073
+ //#region src/analyzer/drift-principal-normalize.ts
49074
+ /**
49075
+ * IAM principal-form canonicalization for the drift comparator (issue #1515).
49076
+ *
49077
+ * AWS renders an IAM role / user principal inside a resource policy in TWO
49078
+ * forms and picks between them on its own schedule:
49079
+ *
49080
+ * - the ARN — `arn:aws:iam::<acct>:role/<name>`
49081
+ * - the principal's UNIQUE ID — `AROA…` (role) / `AIDA…` (user)
49082
+ *
49083
+ * The unique-id form is substituted when the referenced principal is
49084
+ * transiently unresolvable at write time (it is being created or replaced) and
49085
+ * AWS re-renders the ARN once it resolves. cdkd's deploy-time
49086
+ * `observedProperties` capture stores whichever form the read returned at that
49087
+ * moment, so a later `cdkd drift` can compare `AROA…` against the ARN for the
49088
+ * very same principal and report PERMANENT phantom drift — `--revert` writes
49089
+ * the recorded form back, AWS re-canonicalizes on write, and the next run
49090
+ * reports the identical difference (the class of issues #1096 / #1498, on a
49091
+ * different property).
49092
+ *
49093
+ * Unlike the order-normalization passes in `drift-normalize.ts`, this one
49094
+ * CANNOT be pure: the mapping between the two forms is not derivable from the
49095
+ * string. So the shape detection here is pure and an injected
49096
+ * {@link PrincipalUniqueIdResolver} does the one lookup, which lets the whole
49097
+ * pass be unit-tested without AWS and keeps the API cost at zero for the
49098
+ * overwhelmingly common case — a policy with no unique-id principal makes NO
49099
+ * AWS call at all.
49100
+ *
49101
+ * Both sides are rewritten to the ARN form, never one side only. The race that
49102
+ * produces the mismatch is symmetric (the 2026-08-10 evidence on the
49103
+ * `drift-revert` fixture had the baseline holding `AROA…` and AWS the ARN, but
49104
+ * the reverse ordering is equally reachable), and normalizing a single side is
49105
+ * the mistake `drift-normalize.ts`'s header already records for the unordered
49106
+ * passes — it manufactures drift on the `properties`-fallback baseline.
49107
+ *
49108
+ * FAILING IS VISIBLE, never silent: when the resolver cannot answer (the role
49109
+ * was deleted — which is exactly when AWS keeps the unique id forever — the
49110
+ * caller lacks `iam:GetRole`, or the ARN names another ACCOUNT / IAM PATH, for
49111
+ * which the name-only `GetRole` API would otherwise answer about the caller's
49112
+ * own same-named role), both sides are left untouched and the drift is still
49113
+ * reported. This pass only ever removes a difference it has PROVEN to be two
49114
+ * spellings of one principal; it can never hide a real principal change.
49115
+ *
49116
+ * One downstream note: because the comparator's inputs are canonicalized,
49117
+ * `cdkd drift --accept` records the ARN form for a policy subtree that drifts
49118
+ * for some OTHER reason while also carrying a canonicalized principal — i.e.
49119
+ * the accepted baseline can hold a spelling AWS did not literally return. That
49120
+ * is benign (it is the form AWS re-canonicalizes to, and a later run
49121
+ * normalizes either way) but it is a real difference from "state records
49122
+ * exactly what AWS said", so it is written down rather than left to be
49123
+ * rediscovered.
49124
+ *
49125
+ * Known bound: a policy carried as a JSON STRING rather than a parsed object is
49126
+ * not walked. Re-serializing a parsed string would risk changing its formatting
49127
+ * and manufacturing a different phantom drift, so the pass is scoped to the
49128
+ * object shape — which is what `AWS::S3::BucketPolicy` and the other
49129
+ * `PolicyDocument`-typed properties carry in cdkd state.
49130
+ */
49131
+ /**
49132
+ * An IAM unique id as it appears in a rendered policy. The prefix set is
49133
+ * deliberately narrow — `AROA` (role) and `AIDA` (user) are the only two that
49134
+ * can stand in for an ARN at a `Principal.AWS` position; `AGPA` (group),
49135
+ * `AKIA` (access key) and friends are never rendered there.
49136
+ */
49137
+ const PRINCIPAL_UNIQUE_ID_RE = /^A(?:ROA|IDA)[A-Z0-9]{6,}$/;
49138
+ /** An IAM role / user ARN — the two shapes a unique id can stand in for. */
49139
+ const IAM_PRINCIPAL_ARN_RE = /^arn:[a-z0-9-]+:iam::\d{12}:(role|user)\/(.+)$/;
49140
+ /**
49141
+ * Split an IAM role / user principal ARN into what the lookup API needs, or
49142
+ * `undefined` when the string is not one (a service principal, an
49143
+ * `assumed-role` STS ARN, `*`, …).
49144
+ *
49145
+ * Exported so the resolver in `drift.ts` recognizes exactly the ARNs
49146
+ * {@link collectPrincipalArns} collects — one regex, so the collector and the
49147
+ * lookup cannot drift apart and leave an ARN gathered but never resolvable.
49148
+ */
49149
+ function parseIamPrincipalArn(arn) {
49150
+ const match = IAM_PRINCIPAL_ARN_RE.exec(arn);
49151
+ if (!match) return void 0;
49152
+ const name = match[2].split("/").pop();
49153
+ if (!name) return void 0;
49154
+ return {
49155
+ kind: match[1],
49156
+ name
49157
+ };
49158
+ }
49159
+ /** Every string at a `Principal` / `NotPrincipal` position, at any depth. */
49160
+ function collectPrincipalStrings(value, out) {
49161
+ if (Array.isArray(value)) {
49162
+ for (const element of value) collectPrincipalStrings(element, out);
49163
+ return;
49164
+ }
49165
+ if (!value || typeof value !== "object") return;
49166
+ for (const [key, member] of Object.entries(value)) {
49167
+ if (key === "Principal" || key === "NotPrincipal") pushPrincipalValue(member, out);
49168
+ collectPrincipalStrings(member, out);
49169
+ }
49170
+ }
49171
+ /**
49172
+ * The strings a `Principal` / `NotPrincipal` value contributes: the bare string
49173
+ * form, and the `AWS` member of the object form (string or array). `Service` /
49174
+ * `Federated` / `CanonicalUser` are deliberately NOT read — none of them can
49175
+ * carry an IAM unique id or a role ARN.
49176
+ */
49177
+ function pushPrincipalValue(value, out) {
49178
+ if (typeof value === "string") {
49179
+ out.push(value);
49180
+ return;
49181
+ }
49182
+ if (Array.isArray(value)) {
49183
+ for (const element of value) if (typeof element === "string") out.push(element);
49184
+ return;
49185
+ }
49186
+ if (!value || typeof value !== "object") return;
49187
+ const awsMember = value["AWS"];
49188
+ if (typeof awsMember === "string") out.push(awsMember);
49189
+ else if (Array.isArray(awsMember)) {
49190
+ for (const element of awsMember) if (typeof element === "string") out.push(element);
49191
+ }
49192
+ }
49193
+ /**
49194
+ * Both interesting principal kinds in ONE walk — the pass runs per resource on
49195
+ * both comparison sides, so walking the bag twice to answer two questions about
49196
+ * the same strings is pure cost.
49197
+ */
49198
+ function collectPrincipalForms(value) {
49199
+ const strings = [];
49200
+ collectPrincipalStrings(value, strings);
49201
+ const uniqueIds = /* @__PURE__ */ new Set();
49202
+ const arns = /* @__PURE__ */ new Set();
49203
+ for (const s of strings) if (PRINCIPAL_UNIQUE_ID_RE.test(s)) uniqueIds.add(s);
49204
+ else if (IAM_PRINCIPAL_ARN_RE.test(s)) arns.add(s);
49205
+ return {
49206
+ uniqueIds,
49207
+ arns
49208
+ };
49209
+ }
49210
+ /**
49211
+ * Replace every principal string that is a KNOWN unique id with the ARN it was
49212
+ * proven to stand for. Only `Principal` / `NotPrincipal` positions are touched
49213
+ * — a unique id appearing in, say, a `Condition` value is left alone, where the
49214
+ * two forms are NOT interchangeable.
49215
+ */
49216
+ function rewritePrincipalUniqueIds(value, arnByUniqueId) {
49217
+ if (Array.isArray(value)) return value.map((el) => rewritePrincipalUniqueIds(el, arnByUniqueId));
49218
+ if (!value || typeof value !== "object") return value;
49219
+ const out = {};
49220
+ for (const [key, member] of Object.entries(value)) out[key] = key === "Principal" || key === "NotPrincipal" ? rewritePrincipalValue(member, arnByUniqueId) : rewritePrincipalUniqueIds(member, arnByUniqueId);
49221
+ return out;
49222
+ }
49223
+ function rewritePrincipalValue(value, arnByUniqueId) {
49224
+ const swap = (s) => typeof s === "string" ? arnByUniqueId.get(s) ?? s : rewritePrincipalUniqueIds(s, arnByUniqueId);
49225
+ if (typeof value === "string") return swap(value);
49226
+ if (Array.isArray(value)) return value.map(swap);
49227
+ if (!value || typeof value !== "object") return value;
49228
+ const out = {};
49229
+ for (const [key, member] of Object.entries(value)) {
49230
+ if (key !== "AWS") {
49231
+ out[key] = rewritePrincipalUniqueIds(member, arnByUniqueId);
49232
+ continue;
49233
+ }
49234
+ out[key] = Array.isArray(member) ? member.map(swap) : swap(member);
49235
+ }
49236
+ return out;
49237
+ }
49238
+ /**
49239
+ * Canonicalize the two comparison sides so that a principal AWS rendered as a
49240
+ * unique id on one side and as an ARN on the other stops reading as drift.
49241
+ *
49242
+ * Short-circuits — with NO resolver call — unless BOTH a unique id and an IAM
49243
+ * ARN are present across the two sides, since a unique id with no candidate ARN
49244
+ * opposite it has nothing to be equal to.
49245
+ */
49246
+ async function canonicalizePrincipalUniqueIds(baseline, aws, resolve) {
49247
+ const baselineForms = collectPrincipalForms(baseline);
49248
+ const awsForms = collectPrincipalForms(aws);
49249
+ const uniqueIds = /* @__PURE__ */ new Set([...baselineForms.uniqueIds, ...awsForms.uniqueIds]);
49250
+ if (uniqueIds.size === 0) return {
49251
+ baseline,
49252
+ aws
49253
+ };
49254
+ const arns = /* @__PURE__ */ new Set([...baselineForms.arns, ...awsForms.arns]);
49255
+ if (arns.size === 0) return {
49256
+ baseline,
49257
+ aws
49258
+ };
49259
+ const arnByUniqueId = /* @__PURE__ */ new Map();
49260
+ for (const arn of arns) {
49261
+ const uniqueId = await resolve(arn);
49262
+ if (uniqueId !== void 0 && uniqueIds.has(uniqueId)) arnByUniqueId.set(uniqueId, arn);
49263
+ }
49264
+ if (arnByUniqueId.size === 0) return {
49265
+ baseline,
49266
+ aws
49267
+ };
49268
+ return {
49269
+ baseline: rewritePrincipalUniqueIds(baseline, arnByUniqueId),
49270
+ aws: rewritePrincipalUniqueIds(aws, arnByUniqueId)
49271
+ };
49272
+ }
49273
+
49072
49274
  //#endregion
49073
49275
  //#region src/analyzer/drift-cc-api-deny-list.ts
49074
49276
  /**
@@ -49283,11 +49485,12 @@ async function driftCommand(stacks, options) {
49283
49485
  registerAllProviders(providerRegistry);
49284
49486
  providerRegistry.setCustomResourceResponseBucket(bucket);
49285
49487
  const ccApiFallback = new CloudControlProvider();
49488
+ const resolvePrincipalUniqueId = createIamPrincipalUniqueIdResolver(awsClients);
49286
49489
  const targetRefs = resolveTargetRefs(stacks, await stateBackend.listStacks(), options);
49287
49490
  const reports = [];
49288
49491
  for (const ref of targetRefs) {
49289
49492
  if (!ref.region) throw new Error(`Stack '${ref.stackName}' has only a legacy state record without a region. Run 'cdkd deploy ${ref.stackName}' (or any cdkd write) to migrate it to the region-scoped layout, then re-run drift detection.`);
49290
- const report = await runDriftForStack(ref.stackName, ref.region, stateBackend, providerRegistry, ccApiFallback);
49493
+ const report = await runDriftForStack(ref.stackName, ref.region, stateBackend, providerRegistry, ccApiFallback, resolvePrincipalUniqueId);
49291
49494
  reports.push(report);
49292
49495
  }
49293
49496
  if (options.json) writeJsonReport(reports);
@@ -49349,13 +49552,75 @@ function resolveTargetRefs(stacks, stateRefs, options) {
49349
49552
  return out;
49350
49553
  }
49351
49554
  /**
49555
+ * The live half of the #1515 principal canonicalization: resolve an IAM role /
49556
+ * user ARN to that principal's unique id (`AROA…` / `AIDA…`).
49557
+ *
49558
+ * Results are cached per ARN for the whole command — INCLUDING the failures, so
49559
+ * a deleted role (the case where AWS keeps the unique id forever, and therefore
49560
+ * the likeliest one to appear here) costs exactly one API call rather than one
49561
+ * per resource that references it.
49562
+ *
49563
+ * Every failure resolves to `undefined` rather than throwing: a missing
49564
+ * `iam:GetRole` permission, a cross-account principal, or a deleted role must
49565
+ * leave the comparison exactly as it was — the drift is then reported, which is
49566
+ * the safe direction. Drift detection must never fail because a cosmetic
49567
+ * normalization could not run.
49568
+ *
49569
+ * **The response ARN is compared back to the requested one, and that check is
49570
+ * what makes the "never hides a real change" claim true rather than aspirational.**
49571
+ * `GetRole` / `GetUser` are account-local and take a NAME, not an ARN, so the
49572
+ * lookup silently answers about the CALLER's account and about the wrong IAM
49573
+ * path: `arn:aws:iam::<other-acct>:role/Foo` and
49574
+ * `arn:aws:iam::<self>:role/prod/Foo` would both come back as this account's
49575
+ * root-path `Foo`, and the pass would then "prove" two DIFFERENT principals
49576
+ * equal and collapse a genuine drift. Round-tripping the ARN costs nothing and
49577
+ * turns both cases into an unresolved lookup, i.e. drift reported.
49578
+ */
49579
+ function createIamPrincipalUniqueIdResolver(awsClients) {
49580
+ const cache = /* @__PURE__ */ new Map();
49581
+ let warnedOnDenied = false;
49582
+ return async (arn) => {
49583
+ if (cache.has(arn)) return cache.get(arn);
49584
+ let uniqueId;
49585
+ const principal = parseIamPrincipalArn(arn);
49586
+ if (principal) try {
49587
+ let entityArn;
49588
+ let entityId;
49589
+ if (principal.kind === "role") {
49590
+ const role = (await awsClients.iam.send(new GetRoleCommand({ RoleName: principal.name }))).Role;
49591
+ entityArn = role?.Arn;
49592
+ entityId = role?.RoleId;
49593
+ } else {
49594
+ const user = (await awsClients.iam.send(new GetUserCommand({ UserName: principal.name }))).User;
49595
+ entityArn = user?.Arn;
49596
+ entityId = user?.UserId;
49597
+ }
49598
+ if (entityArn !== void 0 && entityArn.toLowerCase() === arn.toLowerCase()) uniqueId = entityId;
49599
+ else getLogger().debug(`Principal ${arn} resolved to ${entityArn ?? "no entity"} — same name in this account or under another IAM path; leaving the policy principal comparison untouched.`);
49600
+ } catch (error) {
49601
+ const message = error instanceof Error ? error.message : String(error);
49602
+ const name = error instanceof Error ? error.name : "";
49603
+ const status = error?.$metadata?.httpStatusCode;
49604
+ const denied = /^AccessDenied|^NotAuthorized/i.test(name) || status === 403;
49605
+ if (!warnedOnDenied && denied) {
49606
+ warnedOnDenied = true;
49607
+ getLogger().warn(`Cannot read IAM principals (${name || "access denied"}). A resource policy whose principal AWS rendered as a unique id (AROA…/AIDA…) may therefore report drift that is only a spelling difference; grant iam:GetRole / iam:GetUser to resolve it, or re-run with --verbose for the full error.`);
49608
+ }
49609
+ getLogger().debug(`Could not resolve the unique id of principal ${arn} (${message}); leaving the policy principal comparison untouched.`);
49610
+ if (isThrottlingError(error)) return void 0;
49611
+ }
49612
+ cache.set(arn, uniqueId);
49613
+ return uniqueId;
49614
+ };
49615
+ }
49616
+ /**
49352
49617
  * Run drift detection for one stack and shape the per-resource outcomes
49353
49618
  * into a {@link StackDriftReport}. The state object + etag are stored on
49354
49619
  * the report so `--accept` can write back without a re-read, and
49355
49620
  * `--revert` can pass the captured AWS-current snapshot to
49356
49621
  * `provider.update` as the `previousProperties` argument.
49357
49622
  */
49358
- async function runDriftForStack(stackName, region, stateBackend, providerRegistry, ccApiFallback) {
49623
+ async function runDriftForStack(stackName, region, stateBackend, providerRegistry, ccApiFallback, resolvePrincipalUniqueId) {
49359
49624
  const result = await stateBackend.getState(stackName, region);
49360
49625
  if (!result) throw new Error(`No state found for stack '${stackName}' (${region}). Run 'cdkd state list' to see available stacks.`);
49361
49626
  return await withStackName(stackName, async () => {
@@ -49421,7 +49686,8 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
49421
49686
  const useObserved = resource.observedProperties !== void 0;
49422
49687
  const baseline = useObserved ? resource.observedProperties : resource.properties ?? {};
49423
49688
  const observedIgnorePaths = useObserved ? undeclaredEmptyObservedKeys(resource.observedProperties, resource.properties ?? {}) : [];
49424
- const changes = calculateResourceDrift(baseline, aws, {
49689
+ const normalized = await canonicalizePrincipalUniqueIds(baseline, aws, resolvePrincipalUniqueId);
49690
+ const changes = calculateResourceDrift(normalized.baseline, normalized.aws, {
49425
49691
  ignorePaths: observedIgnorePaths.length ? [...ignorePaths, ...observedIgnorePaths] : ignorePaths,
49426
49692
  unionWalkObjects: useObserved,
49427
49693
  unorderedPaths
@@ -66897,7 +67163,7 @@ function createMigrateCommand() {
66897
67163
  */
66898
67164
  function buildProgram() {
66899
67165
  const program = new Command();
66900
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.37");
67166
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.38");
66901
67167
  program.hook("preAction", (_thisCommand, actionCommand) => {
66902
67168
  const { profile } = actionCommand.optsWithGlobals();
66903
67169
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;