@go-to-k/cdkd 0.290.41 → 0.290.43

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.
@@ -1,6 +1,6 @@
1
1
  import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS, y as withoutGeneratedFallbackName } from "./logger-D-2HeLmd.js";
2
2
  import { a as setAssumedRoleCredentials, i as getPreAssumeEnvCredentials, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-BoIxyCir.js";
3
- import { t as getCdkdVersion } from "./version-Cxppiepi.js";
3
+ import { t as getCdkdVersion } from "./version-ChfV4K6h.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -25,7 +25,9 @@ import { CreateClusterSnapshotCommand, DescribeClusterSnapshotsCommand, Describe
25
25
  import { CreateSnapshotCommand as CreateSnapshotCommand$1, DescribeReplicationGroupsCommand, DescribeSnapshotsCommand as DescribeSnapshotsCommand$1, ElastiCacheClient } from "@aws-sdk/client-elasticache";
26
26
  import { ACMClient } from "@aws-sdk/client-acm";
27
27
  import { LambdaMicrovmsClient } from "@aws-sdk/client-lambda-microvms";
28
+ import * as fs from "node:fs";
28
29
  import { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
30
+ import * as path from "node:path";
29
31
  import { basename, isAbsolute, join, resolve } from "node:path";
30
32
  import { spawn } from "node:child_process";
31
33
  import { homedir, tmpdir } from "node:os";
@@ -33,7 +35,6 @@ import { GetHostedZoneCommand, ListHostedZonesByNameCommand, Route53Client } fro
33
35
  import { DescribeListenersCommand, DescribeLoadBalancersCommand, ElasticLoadBalancingV2Client } from "@aws-sdk/client-elastic-load-balancing-v2";
34
36
  import { KMSClient, ListAliasesCommand } from "@aws-sdk/client-kms";
35
37
  import { readFile } from "fs/promises";
36
- import { join as join$1 } from "path";
37
38
  import { CreateRepositoryCommand, DescribeImagesCommand as DescribeImagesCommand$1, DescribeRepositoriesCommand, ECRClient, GetAuthorizationTokenCommand, PutImageTagMutabilityCommand } from "@aws-sdk/client-ecr";
38
39
  import { inspect } from "node:util";
39
40
  import { hostname } from "os";
@@ -2803,6 +2804,231 @@ function parseEnvironment(env) {
2803
2804
  };
2804
2805
  }
2805
2806
 
2807
+ //#endregion
2808
+ //#region src/utils/assembly-path.ts
2809
+ /**
2810
+ * `true` when `candidate` names something strictly beneath `base`, both given
2811
+ * as already-resolved absolute paths.
2812
+ *
2813
+ * The `..` test is SEPARATOR-AWARE rather than a bare `startsWith('..')`,
2814
+ * which would also reject a legitimate sibling named `..foo`. The empty-string
2815
+ * case is `base` itself: a directory is never a template, a manifest or a
2816
+ * metadata side file, so `.` and `./` are refused rather than read.
2817
+ */
2818
+ function isInside(base, candidate) {
2819
+ const rel = path.relative(base, candidate);
2820
+ if (rel === "" || rel === "..") return false;
2821
+ if (rel.startsWith(`..${path.sep}`)) return false;
2822
+ return !path.isAbsolute(rel);
2823
+ }
2824
+ /**
2825
+ * `realpath(3)`, or `undefined` for a path that does not fully resolve.
2826
+ *
2827
+ * `.native` IS load-bearing and must not be simplified to `fs.realpathSync`.
2828
+ * The plain form is a JavaScript walker that folds `..` LEXICALLY — the same
2829
+ * mistake this module's own model branch is careful not to make — so it
2830
+ * disagrees with the kernel on a target carrying a `..` after a symlinked
2831
+ * component, in both directions:
2832
+ *
2833
+ * - It answers `ENOENT` for a path the kernel resolves and `readFileSync`
2834
+ * then reads. With `cdk.out/a -> <outside>/sub` and
2835
+ * `cdk.out/LINK.json -> a/../c.json` (both live), the JS walker folds to
2836
+ * `cdk.out/c.json`, finds nothing, and the verdict falls through to the
2837
+ * model — which folds the same way and calls it CONTAINED while the read
2838
+ * returns `<outside>/c.json`. That is go-to-k/cdkd#3489's original defect,
2839
+ * reopened by the guard meant to close it.
2840
+ * - On a case-insensitive filesystem (APFS by default) the same shape can make
2841
+ * it SPIN. A `try`/`catch` cannot catch a spin, so a hand-modified assembly
2842
+ * hung the very commands this check protects.
2843
+ *
2844
+ * `.native` is libuv's `realpath(3)` (`GetFinalPathNameByHandle` on Windows),
2845
+ * available since Node 8 against a floor of 22, and throws the same
2846
+ * `ENOENT` / `ELOOP` / `EACCES`. Using it is what makes the "exact whenever
2847
+ * the path resolves" claim above TRUE rather than aspirational.
2848
+ */
2849
+ function tryRealpath(p) {
2850
+ try {
2851
+ return fs.realpathSync.native(p);
2852
+ } catch {
2853
+ return;
2854
+ }
2855
+ }
2856
+ /** `fs.readlinkSync`, or `undefined` when `p` is not a symbolic link. */
2857
+ function tryReadlink(p) {
2858
+ try {
2859
+ return fs.readlinkSync(p);
2860
+ } catch {
2861
+ return;
2862
+ }
2863
+ }
2864
+ /**
2865
+ * Link follows before the walk gives up and reports the path as unresolvable.
2866
+ * It does NOT refuse: an exhausted budget answers `undefined`, which leaves
2867
+ * the symlink arm silent and the lexical verdict standing. That is safe
2868
+ * because the OS gives up first — macOS caps at 32 (`ELOOP`) — so a chain that
2869
+ * reaches this cap is one nothing can read or create through anyway.
2870
+ */
2871
+ const MAX_LINK_HOPS = 40;
2872
+ /**
2873
+ * Unresolvable path COMPONENTS the climb walks before giving up. Bounds the
2874
+ * recursion below, which is one frame per component: 20 000 components raised
2875
+ * an uncaught `RangeError` and 5 000 cost ~200 ms per call. Like the hop cap
2876
+ * this gives up rather than refusing, which is safe for the same reason —
2877
+ * nothing can read or create through a path that deep.
2878
+ */
2879
+ const MAX_PATH_COMPONENTS = 1e3;
2880
+ /**
2881
+ * Where `target` REALLY points, for a path that may not exist yet.
2882
+ *
2883
+ * `fs.realpathSync` answers only for a path that fully resolves, and it throws
2884
+ * `ENOENT` for a DANGLING symbolic link exactly as it does for an absent file
2885
+ * — the two are indistinguishable from its result. That difference is the
2886
+ * whole point here: a write FOLLOWS a dangling link and creates the file at
2887
+ * its target, so `cdk.out/Foo.template.json -> ~/.ssh/authorized_keys` (target
2888
+ * absent) is an out-of-directory write that a realpath-only check reports as
2889
+ * contained. Measured: `cdkd synth --verbose` created the victim file.
2890
+ *
2891
+ * So each unresolvable component is handled by hand: climb to the deepest
2892
+ * ancestor that DOES resolve, then re-apply the remaining components,
2893
+ * following any symbolic link with `readlink` and re-resolving its target.
2894
+ * `undefined` means the walk could not resolve the path at all, which for
2895
+ * every caller means their own open fails too.
2896
+ *
2897
+ * KNOW WHAT THIS IS EXACT ABOUT, because the distinction is what bounds it:
2898
+ *
2899
+ * - For a path that FULLY RESOLVES, the answer is `fs.realpathSync`'s and is
2900
+ * therefore exact. Every READ site is in this case by construction — a file
2901
+ * that does not resolve cannot be read — so the containment verdict there is
2902
+ * the kernel's, not this function's. The BASE side is exact there too: every
2903
+ * one is either user-supplied (`-a`, `--output`, `cdkOutDir`) or the
2904
+ * `dirname` of a file cdkd has just read successfully, so neither operand of
2905
+ * a read is in the model's territory.
2906
+ * - For a path that does NOT exist yet, this is a best-effort MODEL of kernel
2907
+ * resolution, and a model can be wrong at an edge. The known one: `..` INSIDE
2908
+ * an unresolvable link's target is folded lexically here, while the kernel
2909
+ * folds it only after following each preceding component, so a target of
2910
+ * `a/../c.json` under a symlinked `a` diverges.
2911
+ *
2912
+ * That edge decides which of the TWO sites that WRITE needs a second check,
2913
+ * and the difference is the BASE rather than the act of writing:
2914
+ *
2915
+ * - `resolveVerboseTemplatePath` (`src/cli/commands/synth.ts`) writes into
2916
+ * `--output`, which may be a pre-existing directory the assembly's author
2917
+ * supplied, so the leaf CAN already be a symbolic link. It therefore asks
2918
+ * the kernel directly with `lstat` and refuses one whatever it points at. A
2919
+ * path-string model can be argued around; `lstat` cannot.
2920
+ * - `resolveInlineCodeFilePath` (`src/cli/commands/local-invoke.ts`) writes
2921
+ * into a `mkdtemp` cdkd has just created, mode 0700 and empty, so no
2922
+ * component of the candidate can be a symbolic link at all and the edge is
2923
+ * unreachable. It needs no `lstat`.
2924
+ *
2925
+ * **A third write site over a base cdkd did not create needs the `lstat`**,
2926
+ * and inherits nothing from this function that would give it one.
2927
+ *
2928
+ * That second site is also the repo's first caller to `mkdirSync(...,
2929
+ * { recursive: true })` through a resolved path, which an earlier revision of
2930
+ * this comment leaned on not existing. It is still safe: the climb charges no
2931
+ * hop for a component, and exhausting `MAX_PATH_COMPONENTS` gives up into the
2932
+ * lexical verdict, which a freshly created directory has no link to defeat.
2933
+ */
2934
+ function resolveThroughLinks(target, hops = 0, climbs = 0) {
2935
+ const direct = tryRealpath(target);
2936
+ if (direct !== void 0) return direct;
2937
+ if (hops >= MAX_LINK_HOPS) return void 0;
2938
+ if (climbs >= MAX_PATH_COMPONENTS) return void 0;
2939
+ const parent = path.dirname(target);
2940
+ if (parent === target) return void 0;
2941
+ const realParent = resolveThroughLinks(parent, hops, climbs + 1);
2942
+ if (realParent === void 0) return void 0;
2943
+ const link = tryReadlink(target);
2944
+ if (link === void 0) return path.join(realParent, path.basename(target));
2945
+ return resolveThroughLinks(path.resolve(realParent, link), hops + 1, climbs);
2946
+ }
2947
+ /**
2948
+ * Resolve an assembly-supplied `candidate` against `dir` and report whether
2949
+ * the result stays inside it.
2950
+ *
2951
+ * The lexical arm joins exactly the way the call sites used to
2952
+ * (`path.join`, NOT `path.resolve`), so the verdict is about the path the
2953
+ * caller will actually open. `join`'s handling of an absolute candidate makes
2954
+ * this arm strictly more permissive than a `resolve`-based one would be; an
2955
+ * absolute value therefore stays a matter for each site's own tripwire.
2956
+ *
2957
+ * The SYMLINK arm exists because the lexical arm alone leaves an equivalent
2958
+ * hole: `cdk.out/link -> /etc` plus a candidate of `link/passwd` is lexically
2959
+ * contained and still reads `/etc/passwd`. Both sides go through
2960
+ * {@link resolveThroughLinks}, so an assembly directory REACHED through a link
2961
+ * (macOS spells `/tmp` as `/private/tmp`; a user may symlink `cdk.out` itself)
2962
+ * is unaffected, while a candidate that does not exist YET — the shape
2963
+ * `cdkd synth --verbose` writes, and the shape a DANGLING link presents — is
2964
+ * still resolved to where it would actually land. A real `cdk synth` writes no symbolic link
2965
+ * along any of these paths (measured against a CDK 2.268 assembly carrying a
2966
+ * Stage, two nesting levels and an asset manifest), so the arm costs a
2967
+ * legitimate assembly nothing.
2968
+ *
2969
+ * Windows shapes need no special case HERE: `path.relative` is the platform's
2970
+ * own, so a drive-relative or UNC candidate is judged by `path.win32` on
2971
+ * Windows, while on POSIX `C:\evil` and `..\..\evil` are single filename
2972
+ * components that no `readFileSync` can follow out of the directory.
2973
+ *
2974
+ * The verdict is about the assembly AS IT SITS ON DISK, which is the whole
2975
+ * threat model: a hand-modified or third-party `cdk.out` the user then points
2976
+ * cdkd at. It is NOT a defence against a process rewriting that directory
2977
+ * concurrently — the caller opens by path after this returns, so a link
2978
+ * swapped in between is outside what any check here can see.
2979
+ */
2980
+ function resolveAssemblyPath(dir, candidate, options) {
2981
+ const base = path.resolve(dir);
2982
+ const bound = options?.containWithin === void 0 ? base : path.resolve(options.containWithin);
2983
+ const joined = path.resolve(path.join(base, candidate));
2984
+ if (!isInside(bound, joined)) return {
2985
+ contained: false,
2986
+ escape: "lexical",
2987
+ path: joined
2988
+ };
2989
+ const realBase = resolveThroughLinks(bound);
2990
+ if (realBase !== void 0) {
2991
+ const realTarget = resolveThroughLinks(joined);
2992
+ if (realTarget !== void 0 && !isInside(realBase, realTarget)) return {
2993
+ contained: false,
2994
+ escape: "symlink",
2995
+ path: joined,
2996
+ realPath: realTarget
2997
+ };
2998
+ }
2999
+ return {
3000
+ contained: true,
3001
+ path: joined
3002
+ };
3003
+ }
3004
+ /**
3005
+ * The shared tail of every containment refusal: what the value resolved to,
3006
+ * what it escaped, and why that means the assembly is not CDK-generated. Each
3007
+ * call site supplies its own subject ("Stack 'X' has templateFile='...' which
3008
+ * ") and its own error class, because the sites throw four different types.
3009
+ * `action` completes "Refusing to ..." for a caller that declines something
3010
+ * other than loading the file — `renderNestedTemplateTreeDefect` says "deploy"
3011
+ * or "diff", matching its own sibling refusals.
3012
+ *
3013
+ * Every interpolation goes through `displaySafe` for the reason
3014
+ * `AssemblyReader`'s own refusals give (go-to-k/cdkd#3277): this text exists
3015
+ * FOR a hand-modified assembly, so the candidate, the resolved path and even
3016
+ * `dir` (below a Stage it derives from the manifest's `directoryName`) are all
3017
+ * attacker-chosen, and `formatError` sanitizes only an error's `cause`, never
3018
+ * its own `message`.
3019
+ */
3020
+ function renderAssemblyPathEscape(escape, dir, action = "load", provenanceOverride) {
3021
+ const provenance = provenanceOverride ?? `CDK emits assembly paths that stay inside the assembly directory; one that leaves it indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to ${action}.`;
3022
+ const base = path.resolve(dir);
3023
+ const realBase = resolveThroughLinks(base) ?? base;
3024
+ if (escape.escape === "symlink") {
3025
+ if (escape.realPath === realBase) return `resolves to '${displaySafe(escape.path)}', a symbolic link to the directory '${displaySafe(base)}' itself rather than to a file inside it. ${provenance}`;
3026
+ return `resolves to '${displaySafe(escape.path)}', which leads through a symbolic link to '${displaySafe(escape.realPath)}', outside '${displaySafe(base)}'. ${provenance}`;
3027
+ }
3028
+ if (escape.path === base) return `names the directory '${displaySafe(base)}' itself rather than a file inside it. ${provenance}`;
3029
+ return `resolves to '${displaySafe(escape.path)}', outside '${displaySafe(base)}'. ${provenance}`;
3030
+ }
3031
+
2806
3032
  //#endregion
2807
3033
  //#region src/synthesis/stack-messages.ts
2808
3034
  /**
@@ -2833,7 +3059,9 @@ const MESSAGE_ENTRY_TYPES = {
2833
3059
  function collectStackMessages(assemblyDir, artifact) {
2834
3060
  const merged = { ...artifact.metadata ?? {} };
2835
3061
  if (artifact.additionalMetadataFile) {
2836
- const metadataPath = join(assemblyDir, artifact.additionalMetadataFile);
3062
+ const resolved = resolveAssemblyPath(assemblyDir, artifact.additionalMetadataFile);
3063
+ if (!resolved.contained) throw new SynthesisError(`Stack metadata file '${displaySafe(artifact.additionalMetadataFile)}' ${renderAssemblyPathEscape(resolved, assemblyDir)}`);
3064
+ const metadataPath = resolved.path;
2837
3065
  let sideFile;
2838
3066
  try {
2839
3067
  sideFile = JSON.parse(readFileSync(metadataPath, "utf-8"));
@@ -2935,7 +3163,7 @@ var AssemblyReader = class {
2935
3163
  /**
2936
3164
  * Get all stacks from assembly (recursively traverses nested assemblies / Stages)
2937
3165
  */
2938
- getAllStacks(assemblyDir, manifest) {
3166
+ getAllStacks(assemblyDir, manifest, assetOutdir = assemblyDir) {
2939
3167
  if (!manifest.artifacts) {
2940
3168
  this.logger.warn("No artifacts found in manifest");
2941
3169
  return [];
@@ -2943,15 +3171,17 @@ var AssemblyReader = class {
2943
3171
  const assetManifestMap = this.buildAssetManifestMap(assemblyDir, manifest);
2944
3172
  const stacks = [];
2945
3173
  for (const [artifactId, artifact] of Object.entries(manifest.artifacts)) if (artifact.type === "aws:cloudformation:stack") {
2946
- const stackInfo = this.extractStackInfo(assemblyDir, artifactId, artifact, manifest, assetManifestMap);
3174
+ const stackInfo = this.extractStackInfo(assemblyDir, artifactId, artifact, manifest, assetManifestMap, assetOutdir);
2947
3175
  stacks.push(stackInfo);
2948
3176
  } else if (artifact.type === "cdk:cloud-assembly") {
2949
3177
  const props = artifact.properties;
2950
3178
  if (props?.directoryName) {
2951
- const nestedDir = join(assemblyDir, props.directoryName);
3179
+ const resolved = resolveAssemblyPath(assemblyDir, props.directoryName);
3180
+ if (!resolved.contained) throw new SynthesisError(`Nested assembly '${displaySafe(props.directoryName)}' ${renderAssemblyPathEscape(resolved, assemblyDir)}`);
3181
+ const nestedDir = resolved.path;
2952
3182
  try {
2953
3183
  const nestedManifest = this.readManifest(nestedDir);
2954
- const nestedStacks = this.getAllStacks(nestedDir, nestedManifest);
3184
+ const nestedStacks = this.getAllStacks(nestedDir, nestedManifest, assetOutdir);
2955
3185
  stacks.push(...nestedStacks);
2956
3186
  } catch (error) {
2957
3187
  this.logger.warn(`Failed to read nested assembly '${displaySafe(props.directoryName)}': ${displaySafe(error instanceof Error ? error.message : String(error))}`);
@@ -2985,19 +3215,25 @@ var AssemblyReader = class {
2985
3215
  for (const [artifactId, artifact] of Object.entries(manifest.artifacts)) {
2986
3216
  if (artifact.type !== "cdk:asset-manifest") continue;
2987
3217
  const props = artifact.properties;
2988
- if (props?.file) map.set(artifactId, join(assemblyDir, props.file));
3218
+ if (props?.file) {
3219
+ const resolved = resolveAssemblyPath(assemblyDir, props.file);
3220
+ if (!resolved.contained) throw new SynthesisError(`Asset manifest artifact '${displaySafe(artifactId)}' has file='${displaySafe(props.file)}' which ${renderAssemblyPathEscape(resolved, assemblyDir)}`);
3221
+ map.set(artifactId, resolved.path);
3222
+ }
2989
3223
  }
2990
3224
  return map;
2991
3225
  }
2992
3226
  /**
2993
3227
  * Extract stack info from artifact
2994
3228
  */
2995
- extractStackInfo(assemblyDir, artifactId, artifact, manifest, assetManifestMap) {
3229
+ extractStackInfo(assemblyDir, artifactId, artifact, manifest, assetManifestMap, assetOutdir) {
2996
3230
  const props = artifact.properties;
2997
3231
  const stackName = props?.stackName || artifactId;
2998
3232
  const templateFile = props?.templateFile;
2999
3233
  if (!templateFile) throw new SynthesisError(`Stack '${displaySafe(stackName)}' has no templateFile property`);
3000
- const templatePath = join(assemblyDir, templateFile);
3234
+ const resolvedTemplate = resolveAssemblyPath(assemblyDir, templateFile);
3235
+ if (!resolvedTemplate.contained) throw new SynthesisError(`Stack '${displaySafe(stackName)}' has templateFile='${displaySafe(templateFile)}' which ${renderAssemblyPathEscape(resolvedTemplate, assemblyDir)}`);
3236
+ const templatePath = resolvedTemplate.path;
3001
3237
  let template;
3002
3238
  try {
3003
3239
  const content = readFileSync(templatePath, "utf-8");
@@ -3031,7 +3267,9 @@ var AssemblyReader = class {
3031
3267
  const assetPath = resource.Metadata?.["aws:asset:path"];
3032
3268
  if (typeof assetPath !== "string" || assetPath.length === 0) continue;
3033
3269
  if (isAbsolute(assetPath) || /^[a-zA-Z]:[\\/]/.test(assetPath) || assetPath.startsWith("\\\\")) throw new SynthesisError(`Stack '${displaySafe(stackName)}' nested-stack '${displaySafe(logicalId)}' has Metadata['aws:asset:path']='${displaySafe(assetPath)}' which is absolute. CDK emits relative asset paths for nested templates; an absolute path indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to load.`);
3034
- nestedTemplates[logicalId] = join(assemblyDir, assetPath);
3270
+ const resolvedNested = resolveAssemblyPath(assemblyDir, assetPath);
3271
+ if (!resolvedNested.contained) throw new SynthesisError(`Stack '${displaySafe(stackName)}' nested-stack '${displaySafe(logicalId)}' has Metadata['aws:asset:path']='${displaySafe(assetPath)}' which ${renderAssemblyPathEscape(resolvedNested, assemblyDir)}`);
3272
+ nestedTemplates[logicalId] = resolvedNested.path;
3035
3273
  }
3036
3274
  return {
3037
3275
  stackName,
@@ -3039,6 +3277,7 @@ var AssemblyReader = class {
3039
3277
  artifactId,
3040
3278
  template,
3041
3279
  assetManifestPath,
3280
+ assetOutdir,
3042
3281
  dependencyNames,
3043
3282
  region: env?.region !== "unknown-region" ? env?.region : void 0,
3044
3283
  account: env?.account !== "unknown-account" ? env?.account : void 0,
@@ -4411,6 +4650,141 @@ function decodeListingKey(value) {
4411
4650
  }
4412
4651
  }
4413
4652
 
4653
+ //#endregion
4654
+ //#region src/state/record-keys.ts
4655
+ /**
4656
+ * The ONE encoding anything that identifies a producer-side thing by a pair of
4657
+ * runtime strings goes through — a warned-once `Set`, a dedupe `Set`, a
4658
+ * memoization `Map`, a tree's node index.
4659
+ *
4660
+ * An import-free LEAF, the same shape as `src/utils/display-safe.ts` and
4661
+ * `src/utils/parameter-types.ts`, and that is load-bearing rather than tidy:
4662
+ * the consumers span the `cli`, `state` and `deployment` layers, and one of
4663
+ * them — `src/cli/commands/state-list-tree.ts` — is deliberately kept with NO
4664
+ * imports at all so the tree-building logic is unit-testable without mocking
4665
+ * anything. These functions previously lived in `malformed-resources-bag.ts`,
4666
+ * which reaches `error-handler` / `display-safe` / `lock-contention-message`,
4667
+ * and that module still RE-EXPORTS them so no existing importer moved.
4668
+ *
4669
+ * ENCODED, not separated. Issue
4670
+ * [#3308](https://github.com/go-to-k/cdkd/issues/3308) is the measurement
4671
+ * behind that and issue [#3323](https://github.com/go-to-k/cdkd/issues/3323)
4672
+ * is the one that found how far the class ran. Both halves of every key here
4673
+ * are attacker-influenced, and a separator only NARROWS a collision: with a
4674
+ * NUL separator, stack `Evil<NUL>us-east-1` in `ap-northeast-1` and stack
4675
+ * `Evil` in region `us-east-1<NUL>ap-northeast-1` produce the SAME key.
4676
+ *
4677
+ * `JSON.stringify` of a two-element array is injective over string pairs: the
4678
+ * quoting escapes anything that could imitate the separator, so no planted
4679
+ * value can produce another pair's key.
4680
+ *
4681
+ * **What a collision COSTS is per SITE, not a property of the key, and a note
4682
+ * here once claimed otherwise** — "one dropped warning LINE, never a wrong
4683
+ * resolution". That held for the two warned-once sets it was written for and
4684
+ * is false at three `cdkd scrub` sites, where a collision is a wrong ANSWER
4685
+ * feeding the pre-pass that decides whether a producer still holds plaintext.
4686
+ * Do not restore the narrower sentence: it reads as a licence to leave a
4687
+ * separator at a site nobody has re-checked.
4688
+ *
4689
+ * **Whether a separator was EVER injective is also per site**, because it
4690
+ * depends on each half's PROVENANCE. A half read out of an S3 key segment
4691
+ * cannot carry a NUL — measured on go-to-k/cdkd#3323, S3 refuses to store such
4692
+ * a key — so a key whose LEFT half is a segment splits uniquely at the FIRST
4693
+ * NUL. **The rule is two-sided and the dual is easy to miss**: a NUL-free
4694
+ * RIGHT half splits uniquely at the LAST one, which is what actually held at
4695
+ * one of the sites here. A half read out of a TEMPLATE or a state body is
4696
+ * gated by neither.
4697
+ *
4698
+ * Do not settle a site by asking whether the SEPARATOR is storable. That
4699
+ * question was asked twice on go-to-k/cdkd#3323 and gave the wrong answer both
4700
+ * times, in opposite directions. Encoding removes the need to ask at all,
4701
+ * which is why it is the fix rather than a wider separator.
4702
+ */
4703
+ /** The ONE spelling of the encoding, private so no caller can re-derive it. */
4704
+ function injectivePairKey(first, second) {
4705
+ return JSON.stringify([first, second]);
4706
+ }
4707
+ /**
4708
+ * The same rule for a key of MORE than two parts.
4709
+ *
4710
+ * The named wrappers above stay, because a `(stack, region)` record and a
4711
+ * `(stack, export)` coordinate are things worth naming. This one is for the
4712
+ * keys that are just a tuple — an idempotency-token memo key, a resource-pair
4713
+ * edge — where a name would add nothing and a positional list is the honest
4714
+ * shape. It is exported rather than private for the same reason the two are:
4715
+ * so a call site can adopt the rule without re-spelling the encoding
4716
+ * (go-to-k/cdkd#3496).
4717
+ *
4718
+ * `number` is accepted because several of these tuples carry one, and
4719
+ * stringifying at the call site would put the coercion back where a caller can
4720
+ * get it wrong. `JSON.stringify` distinguishes `1` from `"1"`, so a mixed
4721
+ * tuple stays injective across the two.
4722
+ *
4723
+ * KNOWN HOLE in that, recorded rather than guarded: `NaN`, `Infinity` and
4724
+ * `-Infinity` all stringify to `null`, so those three collapse onto one key
4725
+ * and onto a literal `null`. No call site passes one — every numeric argument
4726
+ * today is a literal bound (`64`, `32`) — and a runtime refusal would turn a
4727
+ * key builder into a throwing path for a value that cannot arrive. Check this
4728
+ * note before passing a COMPUTED number.
4729
+ *
4730
+ * Note what this does to a DIGEST that hashes the result: the encoded string
4731
+ * carries no raw control character at all — `JSON.stringify` escapes a NUL to
4732
+ * the six-character text `\u0000` — so a hash input that separates this key
4733
+ * from its neighbours with a NUL is injective too, without the separator
4734
+ * having to be chosen carefully.
4735
+ */
4736
+ function injectiveKey(...parts) {
4737
+ return JSON.stringify(parts);
4738
+ }
4739
+ /**
4740
+ * The prefix every {@link injectiveKey} OF TWO OR MORE PARTS whose first part
4741
+ * is `first` begins with. The arity matters and an earlier revision of this
4742
+ * line omitted it: a ONE-part key ends with a bracket where this prefix ends
4743
+ * with a comma, so it does not match one. Both call sites build two parts.
4744
+ *
4745
+ * It exists for the one reader shape an encoded key otherwise breaks: a cache that
4746
+ * evicts by scanning its keys for a leading component.
4747
+ *
4748
+ * DERIVED from the same encoder rather than spelled again, and that is the
4749
+ * whole point. go-to-k/cdkd#3496's first cut encoded three provider caches and
4750
+ * left `invalidateAttributeCache` testing the OLD `<physicalId>:` prefix, so
4751
+ * the scan matched nothing and every post-update `Fn::GetAtt` read the
4752
+ * pre-update value — silently, with every gate green. A prefix computed here
4753
+ * cannot drift from the key computed two functions up.
4754
+ *
4755
+ * Exact, not a heuristic: `JSON.stringify([first])` quotes and escapes `first`,
4756
+ * so replacing its closing `]` with `,` yields a string no OTHER first part can
4757
+ * begin with. Deriving it by searching the encoded key for a comma would NOT
4758
+ * be exact — a comma inside the value is not escaped by JSON.
4759
+ */
4760
+ function injectiveKeyPrefix(first) {
4761
+ return JSON.stringify([first]).slice(0, -1) + ",";
4762
+ }
4763
+ /**
4764
+ * The key for a state RECORD: the `(stackName, region)` pair that names one
4765
+ * `state.json`.
4766
+ */
4767
+ function producerRecordKey(stackName, region) {
4768
+ return injectivePairKey(stackName, region);
4769
+ }
4770
+ /**
4771
+ * The key for a producer COORDINATE: a `(stack, export-or-output name)` pair,
4772
+ * used by `cdkd scrub`'s re-export chain walk and its per-coordinate verdict
4773
+ * cache.
4774
+ *
4775
+ * A separate NAME because the subject is a different thing — a coordinate is
4776
+ * not a record, and a call site reading `producerRecordKey(stack, exportName)`
4777
+ * would say something false. ONE implementation, though, which is the property
4778
+ * the "one spelling" rule is actually about: two names over one implementation
4779
+ * cannot drift, two implementations can.
4780
+ *
4781
+ * An `exportName` is REDACTED on the way into state
4782
+ * (`StateImportEntry`'s JSDoc), so this half may hold any string at all.
4783
+ */
4784
+ function producerCoordinateKey(stackName, exportOrOutputName) {
4785
+ return injectivePairKey(stackName, exportOrOutputName);
4786
+ }
4787
+
4414
4788
  //#endregion
4415
4789
  //#region src/state/s3-replication-purge-gap.ts
4416
4790
  /**
@@ -4730,7 +5104,7 @@ async function runProbe(s3Client, bucket, requestFields) {
4730
5104
  }
4731
5105
  }
4732
5106
  function probe(s3Client, bucket, requestFields) {
4733
- const key = `${bucket}\u0000${requestFields.ExpectedBucketOwner ?? ""}`;
5107
+ const key = injectiveKey(bucket, requestFields.ExpectedBucketOwner ?? "");
4734
5108
  const cached = replicationProbeCache.get(key);
4735
5109
  if (cached) return cached;
4736
5110
  const pending = runProbe(s3Client, bucket, requestFields).then((result) => {
@@ -4790,11 +5164,7 @@ async function warnIfPurgeIsReplicated(s3Client, bucket, keys, options = {}) {
4790
5164
  const covered = result.rules.filter((rule) => keys.some((key) => key.startsWith(rule.prefix)));
4791
5165
  if (covered.length === 0) return;
4792
5166
  const destinations = [...new Set(covered.map((rule) => rule.enabled ? rule.destination : `${rule.destination} (rule currently Disabled -- it stops FUTURE replication, not what it already copied)`))].sort();
4793
- const warnKey = [
4794
- bucket,
4795
- options.objectDescription ?? "",
4796
- ...destinations
4797
- ].join("\0");
5167
+ const warnKey = injectiveKey(bucket, options.objectDescription ?? "", ...destinations);
4798
5168
  if (warnedWarnings.has(warnKey)) {
4799
5169
  emitDebug(`S3 replication on s3://${safeBucket} still covers purged keys (${options.objectDescription ?? "the body of an object cdkd has just reported as removed"}); already reported once this run for ${destinations.map((d) => displaySafe(d, { asciiOnly: true })).join(", ")}.`);
4800
5170
  return;
@@ -6235,6 +6605,24 @@ function setsEqual(a, b) {
6235
6605
  //#endregion
6236
6606
  //#region src/assets/asset-manifest-loader.ts
6237
6607
  /**
6608
+ * THE one spelling of "where does this file asset's source live", shared by
6609
+ * {@link AssetManifestLoader.getAssetSourcePath} and `FileAssetPublisher`
6610
+ * (issue [#3489](https://github.com/go-to-k/cdkd/issues/3489)).
6611
+ *
6612
+ * Both used to `join(cdkOutputDir, asset.source.path)` independently. The
6613
+ * manifest is chosen by whoever wrote the assembly, `path.join` folds `..`,
6614
+ * and the publisher ZIPs whatever it finds and `PutObject`s it to a bucket the
6615
+ * same manifest names — so an unchecked `source.path` is an exfiltration
6616
+ * primitive with the caller's own credentials, which is the harm the manifest
6617
+ * FILE's own containment check cites. One function so the loader and the
6618
+ * publisher cannot disagree about which file is published.
6619
+ */
6620
+ function resolveFileAssetSourcePath(manifestDir, asset, assetOutdir = manifestDir) {
6621
+ const resolved = resolveAssemblyPath(manifestDir, asset.source.path, { containWithin: assetOutdir });
6622
+ if (!resolved.contained) throw new Error(`File asset '${displaySafe(asset.displayName)}' has source.path='${displaySafe(asset.source.path)}' which ${renderAssemblyPathEscape(resolved, assetOutdir, "publish it")}`);
6623
+ return resolved.path;
6624
+ }
6625
+ /**
6238
6626
  * Whether a file asset's `source.path` is a CloudFormation template asset
6239
6627
  * (the stack template `<Stack>.template.json` or a nested-stack template
6240
6628
  * `<Stack>.<Nested>.nested.template.json`). cdkd deploys templates itself and
@@ -6266,7 +6654,9 @@ var AssetManifestLoader = class {
6266
6654
  * @returns Asset manifest or null if not found
6267
6655
  */
6268
6656
  async loadManifest(cdkOutputDir, stackName) {
6269
- const manifestPath = join$1(cdkOutputDir, `${stackName}.assets.json`);
6657
+ const resolved = resolveAssemblyPath(cdkOutputDir, `${stackName}.assets.json`);
6658
+ if (!resolved.contained) throw new Error(`Asset manifest for stack '${displaySafe(stackName)}' ${renderAssemblyPathEscape(resolved, cdkOutputDir)}`);
6659
+ const manifestPath = resolved.path;
6270
6660
  try {
6271
6661
  this.logger.debug(`Loading asset manifest from: ${manifestPath}`);
6272
6662
  const content = await readFile(manifestPath, "utf-8");
@@ -6302,12 +6692,18 @@ var AssetManifestLoader = class {
6302
6692
  /**
6303
6693
  * Get asset source path (absolute path)
6304
6694
  *
6695
+ * Refuses a `source.path` resolving outside `cdkOutputDir` (issue
6696
+ * [#3489](https://github.com/go-to-k/cdkd/issues/3489)). The manifest is
6697
+ * assembly-supplied and `cdkd deploy -a <dir>` consumes a pre-synthesized
6698
+ * one, so a `source.path` of `../../../home/<user>/.aws` would otherwise be
6699
+ * packaged and uploaded to a bucket the SAME manifest names.
6700
+ *
6305
6701
  * @param cdkOutputDir CDK output directory
6306
6702
  * @param asset File asset
6307
6703
  * @returns Absolute path to asset source
6308
6704
  */
6309
- getAssetSourcePath(cdkOutputDir, asset) {
6310
- return join$1(cdkOutputDir, asset.source.path);
6705
+ getAssetSourcePath(cdkOutputDir, asset, assetOutdir) {
6706
+ return resolveFileAssetSourcePath(cdkOutputDir, asset, assetOutdir ?? cdkOutputDir);
6311
6707
  }
6312
6708
  /**
6313
6709
  * Resolve asset destination values (replace ${AWS::AccountId}, ${AWS::Region}, etc.)
@@ -6399,7 +6795,8 @@ var FileAssetPublisher = class {
6399
6795
  * @param region AWS region
6400
6796
  * @param profile AWS profile (optional)
6401
6797
  */
6402
- async publish(assetHash, asset, cdkOutputDir, accountId, region, _profile) {
6798
+ async publish(assetHash, asset, cdkOutputDir, accountId, region, _profile, assetOutdir) {
6799
+ const sourcePath = resolveFileAssetSourcePath(cdkOutputDir, asset, assetOutdir ?? cdkOutputDir);
6403
6800
  for (const [, dest] of Object.entries(asset.destinations)) {
6404
6801
  const bucketName = this.resolvePlaceholders(dest.bucketName, accountId, region);
6405
6802
  const objectKey = this.resolvePlaceholders(dest.objectKey, accountId, region);
@@ -6414,7 +6811,6 @@ var FileAssetPublisher = class {
6414
6811
  this.logger.debug(`Asset already exists, skipping: s3://${bucketName}/${objectKey}`);
6415
6812
  continue;
6416
6813
  }
6417
- const sourcePath = join(cdkOutputDir, asset.source.path);
6418
6814
  if (asset.source.packaging === "zip") await this.uploadZip(client, sourcePath, bucketName, objectKey);
6419
6815
  else await this.uploadFile(client, sourcePath, bucketName, objectKey);
6420
6816
  this.logger.debug(`✅ Published: s3://${bucketName}/${objectKey}`);
@@ -7734,6 +8130,28 @@ function describeDockerCapturedOutput(error, args, fallback) {
7734
8130
  //#endregion
7735
8131
  //#region src/assets/docker-build.ts
7736
8132
  /**
8133
+ * Where a Docker asset's build context lives, refusing a `source.directory`
8134
+ * that resolves outside `cdkOutDir` (issue
8135
+ * [#3489](https://github.com/go-to-k/cdkd/issues/3489)).
8136
+ *
8137
+ * The field is assembly-supplied and used BOTH as the executable's cwd and as
8138
+ * the `docker build` cwd, so an escaping value sends a directory from outside
8139
+ * `cdk.out` to BuildKit and bakes it into the image pushed to a repository the
8140
+ * same manifest names. Both sites used raw `${a}/${b}` concatenation, which
8141
+ * honours `..` exactly as `join` does; this is the one spelling they share.
8142
+ *
8143
+ * It throws through the CALLER's `wrapError` so each consumer keeps its own
8144
+ * typed error class, exactly as this module's other pre-docker refusals do.
8145
+ *
8146
+ * Twin of `resolveFileAssetSourcePath` and `resolveVerboseTemplatePath`.
8147
+ * Exported for unit testing.
8148
+ */
8149
+ function resolveDockerContextDirectory(manifestDir, directory, wrapError, assetOutdir = manifestDir) {
8150
+ const resolved = resolveAssemblyPath(manifestDir, directory, { containWithin: assetOutdir });
8151
+ if (!resolved.contained) throw wrapError(`asset source.directory='${displaySafe(directory)}' which ${renderAssemblyPathEscape(resolved, assetOutdir, "build it")}`);
8152
+ return resolved.path;
8153
+ }
8154
+ /**
7737
8155
  * Build a Docker image from a CDK asset source. Returns the local image
7738
8156
  * tag the caller should use for `docker tag` / `docker push` (publisher)
7739
8157
  * or `docker run` (local-invoke).
@@ -7751,10 +8169,11 @@ function describeDockerCapturedOutput(error, args, fallback) {
7751
8169
  async function buildDockerImage(asset, cdkOutDir, options) {
7752
8170
  const source = asset.source;
7753
8171
  const logger = getLogger().child("docker-build");
8172
+ const contextDirectory = (directory) => resolveDockerContextDirectory(cdkOutDir, directory, options.wrapError, options.assetOutdir);
7754
8173
  if (source.executable && source.executable.length > 0) {
7755
8174
  const [cmd, ...args] = source.executable;
7756
8175
  if (!cmd) throw options.wrapError("asset source.executable[] is empty");
7757
- const cwd = source.directory ? `${cdkOutDir}/${source.directory}` : cdkOutDir;
8176
+ const cwd = source.directory ? contextDirectory(source.directory) : cdkOutDir;
7758
8177
  const shownExecutable = redactDockerArgvValues(source.executable).join(" ");
7759
8178
  logger.debug(`Building Docker image via executable: ${shownExecutable} (cwd=${cwd})`);
7760
8179
  let result;
@@ -7778,9 +8197,9 @@ async function buildDockerImage(asset, cdkOutDir, options) {
7778
8197
  }
7779
8198
  if (!options.tag) throw options.wrapError("buildDockerImage(directory mode) requires options.tag");
7780
8199
  const buildArgs = buildDockerBuildCommand(source, options.tag, options.platform);
7781
- const contextDir = `${cdkOutDir}/${source.directory}`;
8200
+ const contextDir = contextDirectory(source.directory);
7782
8201
  buildArgs.push(".");
7783
- logger.debug(`${getDockerCmd()} ${redactDockerArgvValues(buildArgs).join(" ")} (cwd=${contextDir})`);
8202
+ logger.debug(`${getDockerCmd()} ${redactDockerArgvValues(buildArgs).join(" ")} (cwd=${displaySafe(contextDir)})`);
7784
8203
  try {
7785
8204
  await runDockerStreaming(buildArgs, {
7786
8205
  cwd: contextDir,
@@ -7910,8 +8329,8 @@ var DockerAssetPublisher = class {
7910
8329
  * via `docker tag` so the downstream `push()` step (which is wired to
7911
8330
  * `localTag` at graph-construction time) keeps working unchanged.
7912
8331
  */
7913
- async build(asset, cdkOutputDir, localTag) {
7914
- await this.buildImage(asset, cdkOutputDir, localTag);
8332
+ async build(asset, cdkOutputDir, localTag, assetOutdir) {
8333
+ await this.buildImage(asset, cdkOutputDir, localTag, assetOutdir);
7915
8334
  }
7916
8335
  /**
7917
8336
  * Push a pre-built Docker image to ECR (public, used by WorkGraph asset-publish nodes)
@@ -7967,9 +8386,10 @@ var DockerAssetPublisher = class {
7967
8386
  * downstream push step finds the image under the deterministic
7968
8387
  * `cdkd-asset-<hash>` name it expects.
7969
8388
  */
7970
- async buildImage(asset, cdkOutputDir, tag) {
8389
+ async buildImage(asset, cdkOutputDir, tag, assetOutdir) {
7971
8390
  const actualTag = await buildDockerImage(asset, cdkOutputDir, {
7972
8391
  tag,
8392
+ ...assetOutdir !== void 0 && { assetOutdir },
7973
8393
  wrapError: (stderr) => new AssetError(`Docker build failed: ${stderr}`)
7974
8394
  });
7975
8395
  if (actualTag !== tag) {
@@ -9342,6 +9762,7 @@ var AssetPublisher = class {
9342
9762
  const content = readFileSync(manifestPath, "utf-8");
9343
9763
  const manifest = JSON.parse(content);
9344
9764
  const cdkOutputDir = manifestPath.replace(/\/[^/]+$/, "");
9765
+ const assetOutdir = options.assetOutdir ?? cdkOutputDir;
9345
9766
  const prefix = options.nodePrefix || "";
9346
9767
  const redirect = options.redirect;
9347
9768
  const nodeIds = [];
@@ -9358,6 +9779,7 @@ var AssetPublisher = class {
9358
9779
  hash,
9359
9780
  asset,
9360
9781
  cdkOutputDir,
9782
+ assetOutdir,
9361
9783
  accountId: options.accountId,
9362
9784
  region: options.region,
9363
9785
  ...options.profile && { profile: options.profile }
@@ -9380,6 +9802,7 @@ var AssetPublisher = class {
9380
9802
  hash,
9381
9803
  asset,
9382
9804
  cdkOutputDir,
9805
+ assetOutdir,
9383
9806
  localTag
9384
9807
  }
9385
9808
  });
@@ -9406,8 +9829,8 @@ var AssetPublisher = class {
9406
9829
  */
9407
9830
  async executeNode(node) {
9408
9831
  const data = node.data;
9409
- if (data.kind === "file") await this.filePublisher.publish(data.hash, data.asset, data.cdkOutputDir, data.accountId, data.region, data.profile);
9410
- else if (data.kind === "docker-build") await this.dockerPublisher.build(data.asset, data.cdkOutputDir, data.localTag);
9832
+ if (data.kind === "file") await this.filePublisher.publish(data.hash, data.asset, data.cdkOutputDir, data.accountId, data.region, data.profile, data.assetOutdir);
9833
+ else if (data.kind === "docker-build") await this.dockerPublisher.build(data.asset, data.cdkOutputDir, data.localTag, data.assetOutdir);
9411
9834
  else if (data.kind === "docker-publish") await this.dockerPublisher.push(data.asset, data.accountId, data.region, data.localTag);
9412
9835
  this.logger.debug(`✅ ${node.id}`);
9413
9836
  }
@@ -10162,85 +10585,6 @@ async function buildLockContentionMessage(args) {
10162
10585
  return `${head} ${advice}, run: ${recoveryCommand}`;
10163
10586
  }
10164
10587
 
10165
- //#endregion
10166
- //#region src/state/record-keys.ts
10167
- /**
10168
- * The ONE encoding anything that identifies a producer-side thing by a pair of
10169
- * runtime strings goes through — a warned-once `Set`, a dedupe `Set`, a
10170
- * memoization `Map`, a tree's node index.
10171
- *
10172
- * An import-free LEAF, the same shape as `src/utils/display-safe.ts` and
10173
- * `src/utils/parameter-types.ts`, and that is load-bearing rather than tidy:
10174
- * the consumers span the `cli`, `state` and `deployment` layers, and one of
10175
- * them — `src/cli/commands/state-list-tree.ts` — is deliberately kept with NO
10176
- * imports at all so the tree-building logic is unit-testable without mocking
10177
- * anything. These functions previously lived in `malformed-resources-bag.ts`,
10178
- * which reaches `error-handler` / `display-safe` / `lock-contention-message`,
10179
- * and that module still RE-EXPORTS them so no existing importer moved.
10180
- *
10181
- * ENCODED, not separated. Issue
10182
- * [#3308](https://github.com/go-to-k/cdkd/issues/3308) is the measurement
10183
- * behind that and issue [#3323](https://github.com/go-to-k/cdkd/issues/3323)
10184
- * is the one that found how far the class ran. Both halves of every key here
10185
- * are attacker-influenced, and a separator only NARROWS a collision: with a
10186
- * NUL separator, stack `Evil<NUL>us-east-1` in `ap-northeast-1` and stack
10187
- * `Evil` in region `us-east-1<NUL>ap-northeast-1` produce the SAME key.
10188
- *
10189
- * `JSON.stringify` of a two-element array is injective over string pairs: the
10190
- * quoting escapes anything that could imitate the separator, so no planted
10191
- * value can produce another pair's key.
10192
- *
10193
- * **What a collision COSTS is per SITE, not a property of the key, and a note
10194
- * here once claimed otherwise** — "one dropped warning LINE, never a wrong
10195
- * resolution". That held for the two warned-once sets it was written for and
10196
- * is false at three `cdkd scrub` sites, where a collision is a wrong ANSWER
10197
- * feeding the pre-pass that decides whether a producer still holds plaintext.
10198
- * Do not restore the narrower sentence: it reads as a licence to leave a
10199
- * separator at a site nobody has re-checked.
10200
- *
10201
- * **Whether a separator was EVER injective is also per site**, because it
10202
- * depends on each half's PROVENANCE. A half read out of an S3 key segment
10203
- * cannot carry a NUL — measured on go-to-k/cdkd#3323, S3 refuses to store such
10204
- * a key — so a key whose LEFT half is a segment splits uniquely at the FIRST
10205
- * NUL. **The rule is two-sided and the dual is easy to miss**: a NUL-free
10206
- * RIGHT half splits uniquely at the LAST one, which is what actually held at
10207
- * one of the sites here. A half read out of a TEMPLATE or a state body is
10208
- * gated by neither.
10209
- *
10210
- * Do not settle a site by asking whether the SEPARATOR is storable. That
10211
- * question was asked twice on go-to-k/cdkd#3323 and gave the wrong answer both
10212
- * times, in opposite directions. Encoding removes the need to ask at all,
10213
- * which is why it is the fix rather than a wider separator.
10214
- */
10215
- /** The ONE spelling of the encoding, private so no caller can re-derive it. */
10216
- function injectivePairKey(first, second) {
10217
- return JSON.stringify([first, second]);
10218
- }
10219
- /**
10220
- * The key for a state RECORD: the `(stackName, region)` pair that names one
10221
- * `state.json`.
10222
- */
10223
- function producerRecordKey(stackName, region) {
10224
- return injectivePairKey(stackName, region);
10225
- }
10226
- /**
10227
- * The key for a producer COORDINATE: a `(stack, export-or-output name)` pair,
10228
- * used by `cdkd scrub`'s re-export chain walk and its per-coordinate verdict
10229
- * cache.
10230
- *
10231
- * A separate NAME because the subject is a different thing — a coordinate is
10232
- * not a record, and a call site reading `producerRecordKey(stack, exportName)`
10233
- * would say something false. ONE implementation, though, which is the property
10234
- * the "one spelling" rule is actually about: two names over one implementation
10235
- * cannot drift, two implementations can.
10236
- *
10237
- * An `exportName` is REDACTED on the way into state
10238
- * (`StateImportEntry`'s JSDoc), so this half may hold any string at all.
10239
- */
10240
- function producerCoordinateKey(stackName, exportOrOutputName) {
10241
- return injectivePairKey(stackName, exportOrOutputName);
10242
- }
10243
-
10244
10588
  //#endregion
10245
10589
  //#region src/utils/bucket-region-client.ts
10246
10590
  /**
@@ -12875,10 +13219,17 @@ function recordedExpressionsOf(secrets) {
12875
13219
  * the shape PR #2415 was forced to WITHDRAW (`provenPublicExpressions`,
12876
13220
  * residual #2425): keyed on a value alone, one stack's answer is served to
12877
13221
  * another stack's identically-spelled read. Here the key names the producer
12878
- * stack, its region and the output — so a hit is served only to a resolution
12879
- * that asked for that exact output of that exact stack, i.e. to precisely the
12880
- * reader that would have received the plaintext before this feature existed.
12881
- * Nothing is widened.
13222
+ * stack, its region and the output — so a hit is served to a resolution that
13223
+ * asked for that output of that stack, i.e. to the reader that would have
13224
+ * received the plaintext before this feature existed.
13225
+ *
13226
+ * `precisely`, in an earlier revision, overstated it. The key is
13227
+ * NUL-SEPARATED rather than encoded, and the halves are not all
13228
+ * charset-constrained, so a forged coordinate CAN collide with another's
13229
+ * key — see {@link maskedOutputKey} for why that is left standing and what
13230
+ * would change the answer (go-to-k/cdkd#3496). The bound that does hold is
13231
+ * the one above it: whoever can forge such a coordinate can already aim at
13232
+ * the real one, so nothing is widened by the collision either.
12882
13233
  *
12883
13234
  * A RECOVERED VALUE IS STILL SECRET, and every reader re-registers it as a
12884
13235
  * mask-only needle in its OWN bag before using it — the recovery hands back the
@@ -12911,11 +13262,23 @@ const CONFLICTING_CROSS_STACK = Symbol("conflicting cross-stack association");
12911
13262
  /**
12912
13263
  * Separator for the composite keys {@link crossStackSourceKey} builds.
12913
13264
  *
12914
- * A NUL rather than a printable character because no AWS export name, stack
12915
- * name, output name, region or role ARN can contain one, so no two distinct
12916
- * source leaves can spell a single key. A printable separator (`:` / `|`) does
12917
- * occur inside a real export name — CDK's own convention is
13265
+ * A NUL rather than a printable character: a printable separator (`:` / `|`)
13266
+ * does occur inside a real export name — CDK's own convention is
12918
13267
  * `Stack:ExportName` — which would let one leaf's key be read as another's.
13268
+ *
13269
+ * **It does NOT follow that no two distinct source leaves can spell a single
13270
+ * key, and this note asserted that for a while** (go-to-k/cdkd#3496). The
13271
+ * reason given was that no AWS export name, stack name, output name, region or
13272
+ * role ARN can contain a NUL — a claim about what AWS ACCEPTS, while these
13273
+ * halves are TEMPLATE literals read straight out of the intrinsic. A hand-written
13274
+ * template can put a NUL in any of them.
13275
+ *
13276
+ * What makes the key safe is not injectivity but what happens on a collision:
13277
+ * the association store POISONS a slot recorded against a differing
13278
+ * (expression, plaintext) pair rather than overwriting it, and the scope is a
13279
+ * `WeakMap` on the pass's own bag. So a forged key degrades to a REFUSAL, never
13280
+ * to one leaf's expression being served for another's. Keep that property if
13281
+ * this separator is ever revisited; it, not the charset, is the guarantee.
12919
13282
  */
12920
13283
  const CROSS_STACK_KEY_SEPARATOR = "\0";
12921
13284
  /** A non-empty literal string, or `undefined` for anything else. */
@@ -17149,7 +17512,7 @@ function extractLambdaVpcDeleteDeps(resources) {
17149
17512
  for (const targetId of targets) {
17150
17513
  if (targetId === lambdaId) continue;
17151
17514
  if (!(targetId in resources)) continue;
17152
- const key = `${lambdaId}\u0000${targetId}`;
17515
+ const key = injectiveKey(lambdaId, targetId);
17153
17516
  if (seen.has(key)) continue;
17154
17517
  seen.add(key);
17155
17518
  edges.push({
@@ -27809,7 +28172,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27809
28172
  */
27810
28173
  cachedDynamicReferences = /* @__PURE__ */ new Map();
27811
28174
  /**
27812
- * `<parameter name>\u0000<reported Type>` pairs this resolver has already
28175
+ * `(parameter name, reported Type)` pairs this resolver has already
27813
28176
  * warned about (issue #1933 review).
27814
28177
  *
27815
28178
  * Keyed on the PAIR rather than the name alone: two different anomalous types
@@ -30427,7 +30790,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30427
30790
  * contract as {@link lookupCfnExport}.
30428
30791
  */
30429
30792
  async lookupCfnStackOutputs(stackName, region, context, loggedRegionText) {
30430
- const cacheKey = `${region}\0${stackName}`;
30793
+ const cacheKey = injectiveKey(region, stackName);
30431
30794
  let fetch = this.cfnStackOutputsCache.get(cacheKey);
30432
30795
  if (!fetch) {
30433
30796
  fetch = this.fetchCfnStackOutputs(stackName, region);
@@ -31926,8 +32289,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
31926
32289
  if (paramValue === void 0 || paramValue === null) throw new Error(`Dynamic reference: SSM parameter '${loggedParameterName}' not found or has no value`);
31927
32290
  const paramType = response.Parameter?.Type;
31928
32291
  const secure = paramType !== "String" && paramType !== "StringList";
31929
- if (secure && paramType !== "SecureString" && !this.warnedUnrecognizedSsmTypes.has(`${parameterName}\u0000${String(paramType)}`)) {
31930
- this.warnedUnrecognizedSsmTypes.add(`${parameterName}\u0000${String(paramType)}`);
32292
+ if (secure && paramType !== "SecureString" && !this.warnedUnrecognizedSsmTypes.has(injectiveKey(parameterName, String(paramType)))) {
32293
+ this.warnedUnrecognizedSsmTypes.add(injectiveKey(parameterName, String(paramType)));
31931
32294
  const reported = paramType === void 0 ? "(absent)" : `'${String(paramType)}'`;
31932
32295
  this.logger.warn(`SSM parameter '${loggedParameterName}' reported an unrecognized Type ${reported} — treating its value as a secret, so cdkd will persist the {{resolve:${service}:...}} expression rather than the resolved value. Declare the parameter as String / StringList if it is public config.`);
31933
32296
  }
@@ -33612,7 +33975,7 @@ var CloudControlProvider = class {
33612
33975
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
33613
33976
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
33614
33977
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
33615
- const { ASGProvider } = await import("./asg-provider-CPWB3m4b.js").then((n) => n.n);
33978
+ const { ASGProvider } = await import("./asg-provider-C9R-ND4b.js").then((n) => n.n);
33616
33979
  const asgProvider = new ASGProvider();
33617
33980
  return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
33618
33981
  }
@@ -47320,5 +47683,5 @@ var DeployEngine = class DeployEngine {
47320
47683
  };
47321
47684
 
47322
47685
  //#endregion
47323
- export { yellow as $, ConfigError as $i, STATE_SOURCED_BASELINE_RULES as $n, readBootstrapMarkerBody as $r, requireConfigObject as $t, createPreDeleteFinalSnapshot as A, safeStringify$1 as Aa, warnDeprecatedNoPrefixCliFlag as Ai, malformedResourcePropertiesWarning as An, hasReadableExportSet as Ar, carriesDynamicReference as At, isStatefulRecreateTargetSync as B, PARTITION_TABLE as Bi, refuseMalformedState as Bn, stringifyValue as Br, isSettledInstanceState as Bt, namesAnyDeclaredParameter as C, isRetryableTransientError as Ca, resolveAutoAssetStorage as Ci, malformedExportSourceWarning as Cn, buildLockContentionMessage as Cr, slowCcOperationTimeoutMs as Ct, PRE_DELETE_SNAPSHOT_TYPES as D, markRedactedCause as Da, resolveStateBucketWithDefaultAndSource as Di, malformedOutputsWarning as Dn, CUSTOM_RESOURCE_RESPONSE_PREFIX as Dr, disableInstanceApiTermination as Dt, ATOMIC_FINAL_SNAPSHOT_TYPES as E, markNonRetryable as Ea, resolveStateBucketWithDefault as Ei, malformedOutputsRefusalMessage as En, shellQuote as Er, deleteSkipReason as Et, extractDeploymentEventError as F, findLargeInlineResources as Fi, refuseMalformedOutputs as Fn, isReadableBag as Fr, isImpossibleEmptyStoredAttribute as Ft, toCfnInteger as G, clearBucketRegionCache as Gi, safeRegion as Gn, rewriteTemplateAssetReferences as Gr, coerceCfnBoolean as Gt, WARM_THROUGHPUT_MEMBERS as H, derivePartitionAndUrlSuffix as Hi, repairMalformedResourceEntriesForReadOnly as Hn, buildAssetRedirectMap as Hr, normalizeAwsTagsToCfn as Ht, makeSiblingClaimReader as I, uploadCfnTemplate as Ii, refuseMalformedOutputsForDestroy as In, orphansAfterRollback as Ir, isNamelessDynamicReferenceError as It, bold as J, getAwsClients as Ji, findSilentDropProperties as Jn, assertAssetBucketRegion as Jr, configIntegerRefusal as Jt, toFiniteNumber as K, resolveBucketRegion as Ki, safeStackName as Kn, AssetModeResolver as Kr, coerceCfnInteger as Kt, planOrphanAdoption as L, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Li, refuseMalformedResourceEntries as Ln, orphansCarriedFrom as Lr, isUnboundTemplateParameter as Lt, refusesFinalSnapshot as M, CFN_TEMPLATE_BODY_LIMIT as Mi, malformedStateRefusalMessage as Mn, hasUnverifiableParameterRefusal as Mr, cfnRefValueFromPhysicalId as Mt, unsupportedFinalSnapshotError as N, CFN_TEMPLATE_URL_LIMIT as Ni, refuseDivergentRecordRegionForDestroy as Nn, importableOutputKeys as Nr, coerceParameterTypedValue as Nt, buildFinalSnapshotIdentifier as O, retryClassificationText as Oa, resolveUseCdkBootstrapAssets as Oi, malformedRenderedContainersWarning as On, DEFAULT_STATE_PREFIX as Or, isTerminationProtectionPropagationError as Ot, makeCanonicalizePropertiesFn as P, MIGRATE_TMP_PREFIX as Pi, refuseMalformedNestedChildOutputs as Pn, importableOutputs as Pr, getAccountInfo as Pt, red as Q, CdkdError as Qi, TemplateParser as Qn, parseBootstrapMarker as Qr, requireConfigArray as Qt, MULTI_REGION_RECREATE_BLOCKED_TYPES as R, decodeListingKey as Ri, refuseMalformedResourcePropertiesForOrphan as Rn, shouldRetainResource as Rr, parameterTypeMayLoseSecretIdentity as Rt, computeParameterTaint as S, isMarkedNonRetryable as Sa, resolveApp as Si, malformedExportNamesWarning as Sn, buildForceUnlockCommand as Sr, isWaitAbandonedError as St, resourcesNamingDeclaredParameter as T, isTransientServerError as Ta, resolveSkipPrefix as Ti, malformedOrphanRecordsWarning as Tn, formatLockExpiry as Tr, deleteIndeterminateGuards as Tt, coerceWarmThroughput as U, AssemblyReader as Ui, repairMalformedResourcePropertiesForReadOnly as Un, createAssetRedirectResolver as Ur, resolveExplicitPhysicalId as Ut, renderStatefulReason as V, canonicalizeRegion as Vi, repairMalformedOutputsForReadOnly as Vn, WorkGraph as Vr, WAFv2WebACLProvider as Vt, isWarmThroughputDecrease as W, processStackMessages as Wi, repairMalformedResourcesForReadOnly as Wn, loadPublishableAssetManifest as Wr, assertRegionMatch as Wt, gray as X, setAwsClients as Xi, withRetry as Xn, getBootstrapMarkerKey as Xr, readConfigString as Xt, cyan as Y, resetAwsClients as Yi, describeTypeWithThrottleRetry as Yn, ensureAssetStorage as Yr, configStringRefusal as Yt, green as Z, AssetError as Zi, DagBuilder as Zn, isCrossRegionRedirect as Zr, replayWarn as Zt, bagHoldsSecretExpression as _, SynthesisError as _a, getDockerImageBySourceHash as _i, UNREADABLE_RESOURCES_MAP_ROW as _n, S3StateBackend as _r, endCommandInterruptScope as _t, DeploymentEventsStore as a, LocalInvokeBuildError as aa, describeDockerExecFailure as ai, s3BucketDomainName as an, dynamicReferenceTokens as ar, secretBearingStateKeyWarning as at, mergeNoChangeOutputs as b, normalizeAwsError as ba, getDefaultStateBucketName as bi, hasReadableResources as bn, producerRecordKey as br, startInterruptWatch as bt, replayFailedOperations as c, MalformedProducerRecordRefusalError as ca, formatDockerLoginError as ci, s3BucketWebsiteUrl as cn, isSingleDynamicReferenceToken as cr, IAMRoleProvider as ct, updatePartialMessage as d, ProvisioningError as da, redactDockerArgvValues as di, applyCallerIdentityCredentials as dn, maskSecretsInText as dr, ProviderRegistry as dt, CrossAccountSecretRefusalError as ea, validateAssetBucketName as ei, requireConfigString as en, STATE_SOURCED_CROSS_GENERATION_RULES as er, collectDeclaredOutputNames as et, updatePartialReason as f, ResourceTimeoutError as fa, runDockerForeground as fi, callerEnvCredentials as fn, recordMaskOnlyValue as fr, wouldReturnToSdkProvider as ft, computeImplicitDeleteEdges as g, StateError as ga, AssetManifestLoader as gi, STATE_RESOURCES_MALFORMED as gn, LockManager as gr, beginCommandInterruptScope as gt, IMPLICIT_DELETE_DEPENDENCIES as h, StackTerminationProtectionError as ha, stripControlChars as hi, INTRINSIC_KEYS as hn, scrubResourceRecord as hr, maskerOrIdentity as ht, DeploymentEventsReader as i, IntrinsicResolutionRefusalError as ia, describeDockerCapturedOutput as ii, s3BucketArn as in, createSecretMasker as ir, secretBearing as it, isFinalSnapshotError as j, __exportAll as ja, ssmResolvedValueType as ji, malformedResourcesWarning as jn, hasReasonlessBaselineRefusal as jr, carriesFetchableDynamicReference as jt, ccRoutedFinalSnapshotError as k, describeAwsFailure as ka, stateBucketExistenceConfirmed as ki, malformedResourceEntriesWarning as kn, exportNamesCarriedFrom as kr, IntrinsicFunctionResolver as kt, replayRollback as l, NestedStackChildDirectDestroyError as la, getDockerCmd as li, applyRoleArnIfSet as ln, markSameGenerationBag as lr, collectInlinePolicyNamesManagedBySiblings as lt, maskingRetryLogger as m, StackHasActiveImportsError as ma, escapeRegExp$1 as mi, DiffCalculator as mn, redactSecretsForState as mr, maskDeep as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, DeployCancelledError as na, buildDenyExternalAccessPolicy as ni, producerRegionsFromState as nn, TEMPLATE_SOURCED_RULES as nr, exportAliasCollisionScrubWarning as nt, planFailedOps as o, LocalStartServiceError as oa, describeDockerFailure as oi, s3BucketDualStackDomainName as on, errorCauseChain as or, secretSafeKeyDisplay as ot, withResourceDeadline as p, ResourceUpdateNotSupportedError as pa, runDockerStreaming as pi, envCredentialsAreAssumedRole as pn, recoverMaskedOutput as pr, createMaskedRetryLogger as pt, formatResourceLine as q, AwsClients as qi, findActionableSilentDrops as qn, BOOTSTRAP_MARKER_PREFIX as qr, configBooleanRefusal as qt, DeployEngine as r, DynamicReferenceRegionAmbiguousError as ra, buildDockerImage as ri, withSharedDrainBudget as rn, carriesSecretMask as rr, isExportAliasCollision as rt, planRollback as s, LockError as sa, dockerSpawnEnvWithSensitive as si, s3BucketRegionalDomainName as sn, identityKeyFor as sr, getCurrentResourceSecrets as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, DependencyError as ta, validateContainerRepoName as ti, classifyReplaySecretRegion as tn, STATE_SOURCED_READBACK_RULES as tr, collectPublishedOutputNames as tt, resolveReplacementOldType as u, PartialFailureError as ua, partitionSensitiveEnv as ui, AWS_CREDENTIAL_ENV_KEYS as un, maskSecretsInError as ur, clearOnUpdateRemoval as ut, isSecretBearingReferenceString as v, formatError as va, Synthesizer as vi, displayLogicalId as vn, rebuildClientForBucketRegion as vr, interruptWatchListenerCount as vt, reachableDivergentParameters as w, isThrottlingError as wa, resolveCaptureObservedState as wi, malformedLocalOutputsWarning as wn, forceQuitRecoveryClause as wr, UNSPECIFIED_SKIP_REASON as wt, bindingSkippedOutputs as x, withErrorHandling as xa, getLegacyStateBucketName as xi, isReadableResourceEntry as xn, UNREPRODUCIBLE_LOCK_CLAUSE as xr, CloudControlProvider as xt, keptWholeReasonText as y, isCdkdError as ya, synthesisStatusMessage as yi, hasReadableOutputs as yn, producerCoordinateKey as yr, isInterruptedWaitError as yt, isStatefulRecreateTargetForReplace as z, expectedOwnerParam as zi, refuseMalformedResourcesForDestroy as zn, AssetPublisher as zr, refStateLookupFromResource as zt };
47324
- //# sourceMappingURL=deploy-engine-RoGuHEgf.js.map
47686
+ export { yellow as $, getAwsClients as $i, STATE_SOURCED_BASELINE_RULES as $n, validateContainerRepoName as $r, requireConfigObject as $t, createPreDeleteFinalSnapshot as A, isTransientServerError as Aa, ssmResolvedValueType as Ai, malformedResourcePropertiesWarning as An, hasUnverifiableParameterRefusal as Ar, carriesDynamicReference as At, isStatefulRecreateTargetSync as B, producerRecordKey as Bi, refuseMalformedState as Bn, buildAssetRedirectMap as Br, isSettledInstanceState as Bt, namesAnyDeclaredParameter as C, formatError as Ca, resolveCaptureObservedState as Ci, malformedExportSourceWarning as Cn, formatLockExpiry as Cr, slowCcOperationTimeoutMs as Ct, PRE_DELETE_SNAPSHOT_TYPES as D, isMarkedNonRetryable as Da, resolveUseCdkBootstrapAssets as Di, malformedOutputsWarning as Dn, exportNamesCarriedFrom as Dr, disableInstanceApiTermination as Dt, ATOMIC_FINAL_SNAPSHOT_TYPES as E, withErrorHandling as Ea, resolveStateBucketWithDefaultAndSource as Ei, malformedOutputsRefusalMessage as En, DEFAULT_STATE_PREFIX as Er, deleteSkipReason as Et, extractDeploymentEventError as F, safeStringify$1 as Fa, uploadCfnTemplate as Fi, refuseMalformedOutputs as Fn, orphansCarriedFrom as Fr, isImpossibleEmptyStoredAttribute as Ft, toCfnInteger as G, derivePartitionAndUrlSuffix as Gi, safeRegion as Gn, BOOTSTRAP_MARKER_PREFIX as Gr, coerceCfnBoolean as Gt, WARM_THROUGHPUT_MEMBERS as H, expectedOwnerParam as Hi, repairMalformedResourceEntriesForReadOnly as Hn, loadPublishableAssetManifest as Hr, normalizeAwsTagsToCfn as Ht, makeSiblingClaimReader as I, __exportAll as Ia, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Ii, refuseMalformedOutputsForDestroy as In, shouldRetainResource as Ir, isNamelessDynamicReferenceError as It, bold as J, renderAssemblyPathEscape as Ji, findSilentDropProperties as Jn, getBootstrapMarkerKey as Jr, configIntegerRefusal as Jt, toFiniteNumber as K, AssemblyReader as Ki, safeStackName as Kn, assertAssetBucketRegion as Kr, coerceCfnInteger as Kt, planOrphanAdoption as L, injectiveKey as Li, refuseMalformedResourceEntries as Ln, AssetPublisher as Lr, isUnboundTemplateParameter as Lt, refusesFinalSnapshot as M, markRedactedCause as Ma, CFN_TEMPLATE_URL_LIMIT as Mi, malformedStateRefusalMessage as Mn, importableOutputs as Mr, cfnRefValueFromPhysicalId as Mt, unsupportedFinalSnapshotError as N, retryClassificationText as Na, MIGRATE_TMP_PREFIX as Ni, refuseDivergentRecordRegionForDestroy as Nn, isReadableBag as Nr, coerceParameterTypedValue as Nt, buildFinalSnapshotIdentifier as O, isRetryableTransientError as Oa, stateBucketExistenceConfirmed as Oi, malformedRenderedContainersWarning as On, hasReadableExportSet as Or, isTerminationProtectionPropagationError as Ot, makeCanonicalizePropertiesFn as P, describeAwsFailure as Pa, findLargeInlineResources as Pi, refuseMalformedNestedChildOutputs as Pn, orphansAfterRollback as Pr, getAccountInfo as Pt, red as Q, AwsClients as Qi, TemplateParser as Qn, validateAssetBucketName as Qr, requireConfigArray as Qt, MULTI_REGION_RECREATE_BLOCKED_TYPES as R, injectiveKeyPrefix as Ri, refuseMalformedResourcePropertiesForOrphan as Rn, stringifyValue as Rr, parameterTypeMayLoseSecretIdentity as Rt, computeParameterTaint as S, SynthesisError as Sa, resolveAutoAssetStorage as Si, malformedExportNamesWarning as Sn, forceQuitRecoveryClause as Sr, isWaitAbandonedError as St, resourcesNamingDeclaredParameter as T, normalizeAwsError as Ta, resolveStateBucketWithDefault as Ti, malformedOrphanRecordsWarning as Tn, CUSTOM_RESOURCE_RESPONSE_PREFIX as Tr, deleteIndeterminateGuards as Tt, coerceWarmThroughput as U, PARTITION_TABLE as Ui, repairMalformedResourcePropertiesForReadOnly as Un, rewriteTemplateAssetReferences as Ur, resolveExplicitPhysicalId as Ut, renderStatefulReason as V, decodeListingKey as Vi, repairMalformedOutputsForReadOnly as Vn, createAssetRedirectResolver as Vr, WAFv2WebACLProvider as Vt, isWarmThroughputDecrease as W, canonicalizeRegion as Wi, repairMalformedResourcesForReadOnly as Wn, AssetModeResolver as Wr, assertRegionMatch as Wt, gray as X, clearBucketRegionCache as Xi, withRetry as Xn, parseBootstrapMarker as Xr, readConfigString as Xt, cyan as Y, resolveAssemblyPath as Yi, describeTypeWithThrottleRetry as Yn, isCrossRegionRedirect as Yr, configStringRefusal as Yt, green as Z, resolveBucketRegion as Zi, DagBuilder as Zn, readBootstrapMarkerBody as Zr, replayWarn as Zt, bagHoldsSecretExpression as _, ResourceTimeoutError as _a, Synthesizer as _i, UNREADABLE_RESOURCES_MAP_ROW as _n, S3StateBackend as _r, endCommandInterruptScope as _t, DeploymentEventsStore as a, CrossAccountSecretRefusalError as aa, describeDockerFailure as ai, s3BucketDomainName as an, dynamicReferenceTokens as ar, secretBearingStateKeyWarning as at, mergeNoChangeOutputs as b, StackTerminationProtectionError as ba, getLegacyStateBucketName as bi, hasReadableResources as bn, buildForceUnlockCommand as br, startInterruptWatch as bt, replayFailedOperations as c, DynamicReferenceRegionAmbiguousError as ca, getDockerCmd as ci, s3BucketWebsiteUrl as cn, isSingleDynamicReferenceToken as cr, IAMRoleProvider as ct, updatePartialMessage as d, LocalStartServiceError as da, runDockerForeground as di, applyCallerIdentityCredentials as dn, maskSecretsInText as dr, ProviderRegistry as dt, resetAwsClients as ea, buildDenyExternalAccessPolicy as ei, requireConfigString as en, STATE_SOURCED_CROSS_GENERATION_RULES as er, collectDeclaredOutputNames as et, updatePartialReason as f, LockError as fa, runDockerStreaming as fi, callerEnvCredentials as fn, recordMaskOnlyValue as fr, wouldReturnToSdkProvider as ft, computeImplicitDeleteEdges as g, ProvisioningError as ga, getDockerImageBySourceHash as gi, STATE_RESOURCES_MALFORMED as gn, LockManager as gr, beginCommandInterruptScope as gt, IMPLICIT_DELETE_DEPENDENCIES as h, PartialFailureError as ha, AssetManifestLoader as hi, INTRINSIC_KEYS as hn, scrubResourceRecord as hr, maskerOrIdentity as ht, DeploymentEventsReader as i, ConfigError as ia, describeDockerExecFailure as ii, s3BucketArn as in, createSecretMasker as ir, secretBearing as it, isFinalSnapshotError as j, markNonRetryable as ja, CFN_TEMPLATE_BODY_LIMIT as ji, malformedResourcesWarning as jn, importableOutputKeys as jr, carriesFetchableDynamicReference as jt, ccRoutedFinalSnapshotError as k, isThrottlingError as ka, warnDeprecatedNoPrefixCliFlag as ki, malformedResourceEntriesWarning as kn, hasReasonlessBaselineRefusal as kr, IntrinsicFunctionResolver as kt, replayRollback as l, IntrinsicResolutionRefusalError as la, partitionSensitiveEnv as li, applyRoleArnIfSet as ln, markSameGenerationBag as lr, collectInlinePolicyNamesManagedBySiblings as lt, maskingRetryLogger as m, NestedStackChildDirectDestroyError as ma, stripControlChars as mi, DiffCalculator as mn, redactSecretsForState as mr, maskDeep as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, AssetError as na, resolveDockerContextDirectory as ni, producerRegionsFromState as nn, TEMPLATE_SOURCED_RULES as nr, exportAliasCollisionScrubWarning as nt, planFailedOps as o, DependencyError as oa, dockerSpawnEnvWithSensitive as oi, s3BucketDualStackDomainName as on, errorCauseChain as or, secretSafeKeyDisplay as ot, withResourceDeadline as p, MalformedProducerRecordRefusalError as pa, escapeRegExp$1 as pi, envCredentialsAreAssumedRole as pn, recoverMaskedOutput as pr, createMaskedRetryLogger as pt, formatResourceLine as q, processStackMessages as qi, findActionableSilentDrops as qn, ensureAssetStorage as qr, configBooleanRefusal as qt, DeployEngine as r, CdkdError as ra, describeDockerCapturedOutput as ri, withSharedDrainBudget as rn, carriesSecretMask as rr, isExportAliasCollision as rt, planRollback as s, DeployCancelledError as sa, formatDockerLoginError as si, s3BucketRegionalDomainName as sn, identityKeyFor as sr, getCurrentResourceSecrets as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, setAwsClients as ta, buildDockerImage as ti, classifyReplaySecretRegion as tn, STATE_SOURCED_READBACK_RULES as tr, collectPublishedOutputNames as tt, resolveReplacementOldType as u, LocalInvokeBuildError as ua, redactDockerArgvValues as ui, AWS_CREDENTIAL_ENV_KEYS as un, maskSecretsInError as ur, clearOnUpdateRemoval as ut, isSecretBearingReferenceString as v, ResourceUpdateNotSupportedError as va, synthesisStatusMessage as vi, displayLogicalId as vn, rebuildClientForBucketRegion as vr, interruptWatchListenerCount as vt, reachableDivergentParameters as w, isCdkdError as wa, resolveSkipPrefix as wi, malformedLocalOutputsWarning as wn, shellQuote as wr, UNSPECIFIED_SKIP_REASON as wt, bindingSkippedOutputs as x, StateError as xa, resolveApp as xi, isReadableResourceEntry as xn, buildLockContentionMessage as xr, CloudControlProvider as xt, keptWholeReasonText as y, StackHasActiveImportsError as ya, getDefaultStateBucketName as yi, hasReadableOutputs as yn, UNREPRODUCIBLE_LOCK_CLAUSE as yr, isInterruptedWaitError as yt, isStatefulRecreateTargetForReplace as z, producerCoordinateKey as zi, refuseMalformedResourcesForDestroy as zn, WorkGraph as zr, refStateLookupFromResource as zt };
47687
+ //# sourceMappingURL=deploy-engine-Bw8VoyEi.js.map