cdk-local 0.149.3 → 0.149.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { a as runDockerForeground, c as warnFinchArgvExposure, d as getEmbedConfig, i as getDockerCmd, l as getLogger, n as finchSecretArgvRefusal, o as runDockerStreaming, p as setEmbedConfig, r as formatDockerLoginError, s as spawnStreaming, u as resolveConfiguredLogLevel } from "./docker-cmd-SocNyC9q.js";
1
+ import { C as resolveConfiguredLogLevel, E as setEmbedConfig, S as getLogger, _ as describeCredentialLoadFailure, a as runDockerForeground, b as sanitizeServiceExceptionMessage, c as warnFinchArgvExposure, d as displayUntrustedValue, f as namesTheSameDirectory, g as describeAwsFailureForWarn, h as clampErrorName, i as getDockerCmd, l as absoluteAssemblyPathEscape, m as resolveAssemblyPath, n as finchSecretArgvRefusal, o as runDockerStreaming, p as renderAssemblyPathEscape, r as formatDockerLoginError, s as spawnStreaming, u as assemblyPathEscape, v as flattenToOneLine, w as getEmbedConfig, x as stringifyThrown, y as isAwsServiceException } from "./docker-cmd-B5yU__zC.js";
2
2
  import { chmodSync, cpSync, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import * as path$1 from "node:path";
@@ -799,480 +799,6 @@ function buildStsClientConfig(args) {
799
799
  };
800
800
  }
801
801
 
802
- //#endregion
803
- //#region src/local/credential-error.ts
804
- /**
805
- * Rendering an AWS SDK failure into a log line cdk-local is willing to print
806
- * at DEFAULT level (issues #564, #570).
807
- *
808
- * # Why a shared module
809
- *
810
- * Issue #564 settled the policy for one site — the credential-chain failure
811
- * in {@link file://./sigv4-verify.ts} — and #570 found the same shape at nine
812
- * more, spread across five `src/cli/commands/*.ts` files. Every one of them
813
- * relays a third-party error's `message` into a `logger.warn`, so they share
814
- * one question and must not grow nine answers to it.
815
- *
816
- * SCOPE, stated so a later sweep finds a decision rather than an oversight:
817
- * this module governs those nine plus sigv4-verify's one, and its
818
- * {@link flattenToOneLine} additionally covers every other wire-derived value
819
- * printed on those lines (the role ARN, on the failure AND success paths).
820
- *
821
- * Issue #579 extended it to the AWS SDK error relays elsewhere under
822
- * `src/local/**` (plus `local-studio.ts`'s image-context warn), so the policy
823
- * is no longer scoped to `src/cli/commands/**`:
824
- * `cfn-local-state-provider.ts` (`formatAwsErrorForWarn`, six callers),
825
- * `ssm-parameter-resolver.ts` (whose `formatSsmError` was DELETED rather than
826
- * fixed — it was a second spelling of the same forging vector),
827
- * `state-resolver.ts`, `cloudfront-kvs-client.ts`, `cloudfront-s3-origin.ts`,
828
- * `layer-arn-materializer.ts`, `ecr-puller.ts`, `ecs-secrets-resolver.ts` and
829
- * `httpv2-service-integration.ts`. Each was decided on the SAME two axes below
830
- * rather than rewritten mechanically, and a `catch` around a purely LOCAL
831
- * operation was left alone: `agentcore-s3-bundle.ts`'s unzip and
832
- * `layer-arn-materializer.ts`'s presigned-URL download / unzip see no
833
- * credential chain and no service response, so there is nothing here for them.
834
- *
835
- * The test is what a `catch` CAN SEE, and applying it by LOCATION instead is
836
- * how the sweep first got `agentcore-s3-bundle.ts` wrong. Its `try` does wrap
837
- * `unzipSync` and nothing else — but that made the S3 `GetObject` above it
838
- * UNCOVERED, not out of scope: it had no `catch` at all, so the raw SDK error
839
- * propagated to `withErrorHandling` -> `formatError`, which prints
840
- * `${error.name}: ${error.message}` unflattened and unclamped at DEFAULT
841
- * level, and the default credential chain is what a run without
842
- * `--assume-role` uses. "Outside that `catch`" is not "outside the sweep";
843
- * ask which populations reach the site, and if the answer is "nothing catches
844
- * it here", follow it to the frame that does.
845
- *
846
- * #579 also widened what LEVEL means, and the widening is the useful part:
847
- * `httpv2-service-integration.ts`'s relay is not a log line at all, it is a
848
- * served HTTP RESPONSE BODY — the widest reader in the sweep, since it needs
849
- * neither `--verbose` nor access to the terminal, and the studio capture proxy
850
- * records it onto the timeline besides. The axis is about READERS, not about
851
- * logs.
852
- *
853
- * TWO CALLING CONVENTIONS this imposes, both found by #579 rather than
854
- * anticipated here:
855
- *
856
- * 1. Render each failure ONCE. {@link describeAwsFailureForWarn} EMITS the
857
- * `debug` line, so calling it twice for one error prints that line twice
858
- * (`cfn-local-state-provider.ts`'s `load` did, having rendered the same
859
- * failure for its warn and for `lastLoadError`).
860
- * 2. Re-raise cdk-local's OWN throws ABOVE the relay, with an identifiable
861
- * class. The discriminator is positive, so a `catch` that also catches
862
- * the caller's own `throw new Error('...')` withholds text cdk-local
863
- * wrote itself — which is the diagnostic loss this file's
864
- * {@link describeCredentialLoadFailure} note calls "a real diagnostic
865
- * loss, accepted". #579 stopped accepting it where it was cheap not to:
866
- * `layer-arn-materializer.ts`'s ARN-shape and missing-`Content.Location`
867
- * guards became `LayerMaterializationError`s and are re-raised, and
868
- * `httpv2-service-integration.ts`'s missing-RequestParameter 400 became a
869
- * `ServiceIntegrationRequestError` so an actionable 400 body did not
870
- * degrade into a class name and a character count.
871
- *
872
- * TWO CATCH-ALL relays remain outside it, and both are UNCOVERED. An earlier
873
- * revision of this note gave them opposite verdicts in adjacent paragraphs —
874
- * clearing one and rejecting the other on the same evidence — which was simply
875
- * wrong about the first, so both now read the same way:
876
- *
877
- * - `cloudfront-server.ts:129`'s `Request handling failed: ${err.message}`.
878
- * It is what makes `cloudfront-kvs-client.ts`'s throw a DEFAULT-level
879
- * line, and for THAT sub-path the text arrives pre-sanitized — but it is a
880
- * catch-all over the whole request pipeline, so an origin fetch, an
881
- * edge-function invoke or an SDK error raised anywhere below it still
882
- * reaches the line RAW.
883
- * - `ecs-service-emulator.ts`'s `logger.error` at three sites (`:909`,
884
- * `:1508`, `:1599`). One of them does carry
885
- * `ecs-secrets-resolver.ts`'s `EcsSecretsResolutionError`, which this
886
- * module sanitized on the way in — and reasoning from that to "the relay
887
- * is safe" is the SAME generalisation the bullet above rejects. All three
888
- * are catch-alls over a whole reload or replica roll, relaying
889
- * `err instanceof Error ? err.message : String(err)`, and what they
890
- * usually carry is docker CLI stderr, which is multi-line.
891
- *
892
- * So: one sub-path arriving pre-sanitized never clears a catch-all. Both need
893
- * the same per-occurrence call as everything else in this list. Neither is
894
- * done here — `cloudfront-server.ts` is out of this change's scope, and the
895
- * emulator's three are a different population (docker output, not SDK errors)
896
- * that wants the flattening half rather than the withholding half.
897
- *
898
- * # The two axes that decide it
899
- *
900
- * PROVENANCE alone says relay: a credential-chain error is about the
901
- * developer's own machine configuration, not something cdk-local fetched out
902
- * of a secret store (issue #555's criterion). Two further axes overrule it:
903
- *
904
- * LEVEL — these are `warn`, so they print on a plain `cdkl start-api` /
905
- * `cdkl invoke` run rather than only under `--verbose`, and `cdkl studio`
906
- * mirrors a serve child's output into a log ring it serves over HTTP.
907
- * Everybody sees a `warn`; only someone who asked sees a `debug`.
908
- *
909
- * RECONSTRUCTION — what can reach the string is not a hint about a secret,
910
- * it is the secret. `@aws-sdk/credential-provider-process` builds its
911
- * failure as `new CredentialsProviderError(error.message)` where `error` is
912
- * the rejection of `promisify(child_process.exec)`, i.e. Node's
913
- * `Command failed: <command line>\n<stderr>`, and a passphrase written on a
914
- * `credential_process` command line is an ordinary thing to have. (Verified
915
- * at `@aws-sdk/credential-provider-process@3.972.{39,41,43}`
916
- * `dist-cjs/index.js:56` in this repo's tree.)
917
- *
918
- * # Where these sites DIFFER from sigv4-verify's, and why the answer differs
919
- *
920
- * {@link file://./sigv4-verify.ts}'s `catch` wraps `client.config.credentials()`
921
- * and nothing else: credential-chain resolution, with no AWS operation
922
- * cdk-local asked for. Nothing actionable is lost by withholding all of it,
923
- * so that site withholds unconditionally and keeps the text at `debug`.
924
- *
925
- * The #570 sites wrap an SDK call cdk-local DID make — STS
926
- * `GetCallerIdentity` or `AssumeRole` — so two unrelated error populations
927
- * land in one `catch`:
928
- *
929
- * 1. the credential chain failed before the request went out, which is
930
- * #564's population exactly, and
931
- * 2. STS answered with a modeled service exception — `ExpiredTokenException`,
932
- * `AccessDenied` — whose message IS the diagnosis the user needs, and
933
- * which never went near `credential_process`. (Spelled as the SDK
934
- * spells them: `@aws-sdk/client-sts` models `ExpiredTokenException`
935
- * (`dist-cjs/models/errors.js:6`), while `AccessDenied` is unmodeled and
936
- * arrives as the wire code verbatim — which is exactly why the name is
937
- * clamped rather than trusted.)
938
- *
939
- * Blanket-withholding would turn the single commonest failure of these
940
- * commands (`ExpiredTokenException: The security token included in the
941
- * request is expired`) into a class name and a character count. So the split
942
- * is by population, via {@link describeAwsFailureForWarn}.
943
- *
944
- * # The safe state is defined POSITIVELY
945
- *
946
- * The discriminator is not a deny-list of bad error shapes — #564 rejected
947
- * that reasoning for the `Command failed:` first line, and it loses the same
948
- * race here. It is the SDK's own structural test for "this came off the wire
949
- * as a modeled service error" ({@link isAwsServiceException}). Anything that
950
- * does not match — a chain failure, a socket error, a bare `throw 'x'` — is
951
- * withheld. An unrecognised shape therefore fails toward withholding.
952
- *
953
- * A hostile endpoint cannot steer the discriminator into disclosing MORE.
954
- * `$fault` / `$metadata` are set by the SDK from the HTTP response, not from
955
- * anything the body names, so forging `x-amzn-errortype:
956
- * CredentialsProviderError` only changes `err.name` — the response is still a
957
- * service response, so the error stays on the KEPT branch and its message is
958
- * still the sanitized, capped one the endpoint could have sent under any
959
- * other name. What the endpoint cannot do is move a `credential_process`
960
- * command line onto that branch: that text originates locally, inside a
961
- * `CredentialsProviderError` that never acquires `$fault`.
962
- *
963
- * # What this does NOT close
964
- *
965
- * A single-LINE forged string still reaches readers other than a human. It no
966
- * longer redirects the studio capture proxy: issue #578 anchored every
967
- * `cdkl studio` ready pattern to the start of the line, made the serve manager
968
- * skip a line carrying cdk-local's own `WARN: ` / `ERROR: ` decoration
969
- * outright, and bounded the resolved upstream to loopback — so a message
970
- * containing `Server listening on http://...` is neither read as a banner nor
971
- * usable as a destination. What remains is the reader this sanitizer was
972
- * always aimed at: a HUMAN scanning the log, for whom a forged-looking line is
973
- * still misleading text, which is why the flattening + capping stay.
974
- */
975
- /**
976
- * Longest service-exception message relayed into a `warn` line, in
977
- * characters.
978
- *
979
- * A modeled STS error's message is WIRE-DERIVED — the SDK reads it out of the
980
- * response body — so its length is not cdk-local's to assume. Real ones run
981
- * to roughly 150 characters (`AccessDenied: User: arn:... is not authorized to
982
- * perform: sts:AssumeRole on resource: arn:...`), and the policy-quoting ones
983
- * are the longest, so the cap is set well above that: it exists to stop an
984
- * unbounded body from becoming an unbounded log line, not to trim ordinary
985
- * AWS text, which must survive intact or the branch buys nothing.
986
- *
987
- * Truncation is announced with the true length (see
988
- * {@link sanitizeServiceExceptionMessage}) so a capped line is never mistaken
989
- * for a complete one.
990
- */
991
- const SERVICE_MESSAGE_MAX = 512;
992
- /**
993
- * Total stringification of a thrown value's message.
994
- *
995
- * `String(err)` is NOT total. That throw would escape the `catch` it is
996
- * called from and turn a warn-and-continue branch into a hard failure — a 500
997
- * from the SigV4 authorizer in #564's testing, and a dead `cdkl invoke` here.
998
- * One helper rather than an expression repeated per site, so the length
999
- * reported in a `warn` and the text printed at `debug` can never describe
1000
- * different strings.
1001
- *
1002
- * CORRECTION to #564, measured on this repo's Node 24: `String(aSymbol)`
1003
- * does NOT throw — it returns `'Symbol(x)'`, and it is template
1004
- * INTERPOLATION (`` `${aSymbol}` ``) that raises
1005
- * `TypeError: Cannot convert a Symbol value to a string`. Since both the old
1006
- * code and this helper call `String()` explicitly, a `Symbol` throw was never
1007
- * one of the reachable cases. The two that ARE reachable, both verified:
1008
- * an object with a null prototype (`TypeError: Cannot convert object to
1009
- * primitive value`, because it has no `toString`), and any value whose
1010
- * `toString` / `Symbol.toPrimitive` throws — which a hostile or merely buggy
1011
- * third-party error object is free to do.
1012
- *
1013
- * Named for what it does rather than for one caller: #564 shipped it as
1014
- * `stringifyCredentialLoadFailure`, and #570 gave it a second population
1015
- * (service exceptions) for which that name was simply wrong.
1016
- */
1017
- function stringifyThrown(err) {
1018
- try {
1019
- return err instanceof Error ? String(err.message) : String(err);
1020
- } catch {
1021
- return "[unstringifiable throw]";
1022
- }
1023
- }
1024
- /**
1025
- * The throwing class's name, clamped to something no input can forge.
1026
- *
1027
- * `err.name` is NOT input-independent, which is the whole reason this is a
1028
- * function and not an interpolation. For an AWS service exception it is
1029
- * WIRE-DERIVED: `@aws-sdk/core` builds it from the `x-amzn-errortype` header
1030
- * / the body's `code` / `__type` through `sanitizeErrorCode`, which splits on
1031
- * ',' ':' and '#' and does nothing else — no length cap, no newline stripping
1032
- * (read at `@aws-sdk/core@3.974.13` `protocols/index.js:324`). Such an
1033
- * exception reaches these call sites, because `credential-provider-ini` calls
1034
- * the STS `roleAssumer` unwrapped and because the #570 sites invoke STS
1035
- * directly. So a hostile or hijacked credential endpoint
1036
- * (`AWS_CONTAINER_CREDENTIALS_FULL_URI`, a redirected IMDS) answering
1037
- * `x-amzn-errortype: Foo\nWARN: signature verified` could FORGE log lines
1038
- * into the `warn` stream — and into the studio ring served over HTTP.
1039
- *
1040
- * A bare identifier of at most 64 characters is what every real class name is
1041
- * and what no injected value can be; anything else degrades to `'unknown'`,
1042
- * which also covers a non-`Error` throw, so the field never names a class the
1043
- * throw was not.
1044
- *
1045
- * Note the WRONG fix: `err.name.slice(0, 64)` keeps the newline, so the
1046
- * forgery survives truncation.
1047
- */
1048
- function clampErrorName(err) {
1049
- let rawKind;
1050
- try {
1051
- rawKind = err instanceof Error ? err.name : "unknown";
1052
- } catch {
1053
- return "unknown";
1054
- }
1055
- return typeof rawKind === "string" && /^[A-Za-z0-9_.-]{1,64}$/.test(rawKind) ? rawKind : "unknown";
1056
- }
1057
- /**
1058
- * The thrown value's `code`, when it is a bare identifier, else `undefined`.
1059
- *
1060
- * This exists because withholding by class name alone is not discriminating
1061
- * enough for the population that actually reaches the withheld branch most
1062
- * often. `Region is missing`, `getaddrinfo ENOTFOUND sts.<region>.amazonaws.com`
1063
- * and `connect ETIMEDOUT 169.254.169.254:80` all arrive as a plain `Error`, so
1064
- * all three render as `Error; N-character message withheld` and the reader
1065
- * cannot tell a misconfiguration from an unreachable endpoint.
1066
- *
1067
- * Node sets `code` on its system errors (`ENOTFOUND`, `ETIMEDOUT`,
1068
- * `ECONNREFUSED`) and there it is a fixed enum chosen by the runtime. It is
1069
- * NOT input-independent in general, though, and the earlier draft of this note
1070
- * claimed it was: `@smithy/core`'s `decorateServiceException`
1071
- * (`dist-cjs/submodules/client/index.js:795-802`) copies every key of the
1072
- * PARSED RESPONSE BODY onto the exception, so an unmodeled error from a
1073
- * hostile endpoint can carry a `code` of its choosing. The clamp is therefore
1074
- * load-bearing rather than belt-and-braces, exactly as it is for
1075
- * {@link clampErrorName}: what survives is at most 64 characters matching
1076
- * `[A-Za-z0-9_.-]`, which cannot forge a line, and which is the same residue
1077
- * the class name already accepts.
1078
- *
1079
- * `undefined` rather than `'unknown'` when there is none, so the caller can
1080
- * omit the field entirely instead of printing a placeholder that says less
1081
- * than nothing.
1082
- */
1083
- function clampErrorCode(err) {
1084
- if (!err || typeof err !== "object") return void 0;
1085
- let raw;
1086
- try {
1087
- raw = err.code;
1088
- } catch {
1089
- return;
1090
- }
1091
- if (typeof raw !== "string") return void 0;
1092
- return /^[A-Za-z0-9_.-]{1,64}$/.test(raw) ? raw : void 0;
1093
- }
1094
- /**
1095
- * Is `err` a modeled AWS service exception — i.e. did the SDK parse it out of
1096
- * a service RESPONSE, rather than raise it while assembling the request?
1097
- *
1098
- * This is `ServiceException.isInstance`'s own structural test, reproduced
1099
- * rather than imported: cdk-local loads `@aws-sdk/client-sts` lazily at every
1100
- * one of these call sites (a dynamic `import()` inside the `try`), so taking
1101
- * a static dependency on `@smithy/core` purely to type-test an error would
1102
- * pull the SDK into the CLI's startup path. The structural form also keeps
1103
- * working across the several `@smithy/core` copies pnpm resolves in this
1104
- * tree, where an `instanceof` against one copy's class fails for an error
1105
- * minted by another.
1106
- *
1107
- * The test is deliberately the SDK's and not a name list: a name list would
1108
- * have to be extended for every new STS error code, and a missing entry would
1109
- * fail toward DISCLOSING less, which is safe, but also toward hiding the
1110
- * actionable message, which is the regression this split exists to avoid.
1111
- */
1112
- function isAwsServiceException(err) {
1113
- if (!err || typeof err !== "object") return false;
1114
- const candidate = err;
1115
- try {
1116
- return Boolean(candidate.$metadata) && (candidate.$fault === "client" || candidate.$fault === "server");
1117
- } catch {
1118
- return false;
1119
- }
1120
- }
1121
- /**
1122
- * Replace every character that could make one emitted line render as more than
1123
- * one line, or render in an order it was not written in, with a space.
1124
- *
1125
- * Four Unicode categories, each measured rather than assumed (see the fixture
1126
- * in `tests/unit/local/credential-error.test.ts`):
1127
- *
1128
- * - `Cc` — the C0/C1 controls, which is `\n` / `\r` (a forged extra line in
1129
- * the studio ring) and `\x1b` (an ANSI escape in a terminal). U+0085 NEL
1130
- * lives here too.
1131
- * - `Cf` — the format characters, which is U+202E RIGHT-TO-LEFT OVERRIDE and
1132
- * friends: they forge how the REST of the line reads. The cost is that a
1133
- * ZWJ inside an emoji or an Indic cluster is replaced too; an AWS error
1134
- * message is ASCII, so that is a trade with no observed downside.
1135
- * - `Zl` / `Zp` — U+2028 and U+2029. Neither is in `Cc` or `Cf`, and both are
1136
- * forced line breaks in the studio UI's `<pre>`, so leaving them out would
1137
- * have left the forged-line case open in HTML while closing it in a
1138
- * terminal.
1139
- * - `Cs` — a LONE surrogate, which is not a character at all and breaks JSON
1140
- * encoding of the log event. With the `u` flag a well-formed pair is one
1141
- * code point and does NOT match, so emoji survive.
1142
- *
1143
- * Used by both branches of {@link describeAwsFailureForWarn} — the kept
1144
- * message, and the `debug` line carrying the withheld one, because the `debug`
1145
- * stream is the SAME studio ring under `--verbose`, so a text unsafe to put on
1146
- * a line at `warn` is unsafe at `debug` — and, since issue #570's review
1147
- * rounds, by every OTHER wire-derived value that lands on one of these lines:
1148
- * `sigv4-verify`'s own `debug` line, and the role ARN at the four warn sites
1149
- * plus the five `info` / `debug` sites that print an ARN resolved from a live
1150
- * `GetFunctionConfiguration` / `GetAgentRuntime` response. The last group is
1151
- * the more reachable one — it fires on SUCCESS.
1152
- */
1153
- function flattenToOneLine(message) {
1154
- return message.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{Cs}]/gu, " ");
1155
- }
1156
- /**
1157
- * Render a wire-derived service message safe to put on one log line.
1158
- *
1159
- * Two properties, and both are about the LINE rather than about secrecy — the
1160
- * message itself is already judged safe to print by the time this is called:
1161
- *
1162
- * - No line breaks and no rendering-direction control, via
1163
- * {@link flattenToOneLine}.
1164
- * - Bounded length, with the true length named when it is exceeded. The
1165
- * suffix names the character count of the SANITIZED message rather than
1166
- * of the raw one, because the sanitized string is what the truncated
1167
- * prefix is a prefix OF; quoting the raw length would describe a string
1168
- * the reader can never see.
1169
- *
1170
- * The cut is made on CODE POINTS rather than UTF-16 units, so a truncation
1171
- * landing inside an astral character cannot emit half of a surrogate pair. The
1172
- * count in the suffix is the code-point count for the same reason: the prefix
1173
- * and the number must describe the same string.
1174
- */
1175
- function sanitizeServiceExceptionMessage(message) {
1176
- const points = [...flattenToOneLine(message)];
1177
- if (points.length <= SERVICE_MESSAGE_MAX) return points.join("");
1178
- return `${points.slice(0, SERVICE_MESSAGE_MAX).join("")}[... truncated; ${points.length}-character message]`;
1179
- }
1180
- /**
1181
- * Describe a credential-chain failure for a default-level log line WITHOUT
1182
- * relaying the chain's own message (issue #564).
1183
- *
1184
- * What is reported instead is input-INDEPENDENT: the clamped class name (see
1185
- * {@link clampErrorName}), which is the same discriminator
1186
- * `ecs-secrets-resolver.ts` reports for the JSON parse failure it must not
1187
- * echo, plus the clamped {@link clampErrorCode} when the throw carries one.
1188
- *
1189
- * Withholding is NOT free, and the cost lands on cdk-local's own text as well
1190
- * as on the SDK's. The worked example used to be `role-arn.ts`'s
1191
- * `AssumeRole(<arn>) returned no usable credentials.`: a plain `Error` with no
1192
- * `$fault`, so it was withheld like any other, and the note recorded that as a
1193
- * real diagnostic loss accepted because the alternative — an allow-list of
1194
- * messages that may print — is the deny-list this design rejects, wearing the
1195
- * other sign. It also named the right fix: make cdk-local's own throws
1196
- * IDENTIFIABLE rather than guess at their text.
1197
- *
1198
- * Issue #579 did that, for exactly this example. `role-arn.ts` now throws
1199
- * `AssumeRoleFailure`, carrying a `detail` its relaying callers print verbatim
1200
- * — and the same pattern closed the identical loss in
1201
- * `layer-arn-materializer.ts`, `httpv2-service-integration.ts`,
1202
- * `ecr-puller.ts`, `local-run-task.ts` and `ecs-service-emulator.ts`. The
1203
- * general point stands and is what a new site should apply: the policy cannot
1204
- * tell cdk-local's own sentence from a hostile endpoint's, so a `catch` that
1205
- * can see both must make its own throws recognisable BEFORE the relay.
1206
- *
1207
- * The remaining ARN-length question — an ARN is structured, so the bound
1208
- * belongs at the three resolution points rather than at the log line — is
1209
- * tracked separately at
1210
- * https://github.com/go-to-k/cdk-local/issues/607.
1211
- *
1212
- * The LENGTH is reported, unlike in `ecs-secrets-resolver.ts`, and the
1213
- * difference is deliberate: there the user already knows which secret it is
1214
- * and can read the value at its source, so a character count would be
1215
- * disclosure buying nothing. Here the user cannot see the withheld message at
1216
- * all, and the count is what separates a one-line
1217
- * `Could not load credentials from any providers` (45 characters, measured
1218
- * against `@aws-sdk/credential-provider-node@3.972.44` `dist-cjs/index.js:144`)
1219
- * from a multi-hundred-character `Command failed:` dump — which is what tells
1220
- * them whether their `credential_process` even ran.
1221
- *
1222
- * The count is a side channel, and a narrow one: against a KNOWN
1223
- * `credential_process` template the number is `constant + len(passphrase)`, so
1224
- * it yields the passphrase's LENGTH. That is accepted rather than overlooked.
1225
- * It buys the one thing the user cannot otherwise see, the `Command failed:`
1226
- * dump is not reachable on today's SDK versions anyway, and bucketing the
1227
- * number would blur exactly the boilerplate-vs-dump distinction the field
1228
- * exists for.
1229
- */
1230
- function describeCredentialLoadFailure(err) {
1231
- const code = clampErrorCode(err);
1232
- return `${code === void 0 ? clampErrorName(err) : `${clampErrorName(err)} ${code}`}; ${stringifyThrown(err).length}-character message withheld, logged at debug level under --verbose`;
1233
- }
1234
- /**
1235
- * Render an AWS SDK call failure for a `logger.warn` line, and emit the
1236
- * withheld text at `debug` when it is withheld (issue #570).
1237
- *
1238
- * `operation` names the call for the `debug` line — e.g.
1239
- * `'STS GetCallerIdentity'`. It is cdk-local's own literal at every call
1240
- * site, never anything read off an error or a response.
1241
- *
1242
- * # Why this emits the `debug` line itself
1243
- *
1244
- * Because the alternative is nine call sites each free to withhold a message
1245
- * and forget to print it anywhere. The pairing is the invariant — "withheld
1246
- * at `warn`" is only acceptable while "in full at `debug`" holds — so it
1247
- * lives in one function rather than in a convention nine sites must keep. A
1248
- * tenth site gets it for free.
1249
- *
1250
- * The `debug` line fires ONLY on the withheld branch. On the service-exception
1251
- * branch the `warn` already carries the message, and repeating it verbatim one
1252
- * level down would say nothing the reader did not just read.
1253
- *
1254
- * It is FLATTENED (not capped). Flattened because the `debug` stream is not a
1255
- * private channel: it is the same stdout `cdkl studio` mirrors into the log
1256
- * ring it serves over HTTP, so a `\n` in the withheld text would forge a line
1257
- * there exactly as it would at `warn`. Not capped, because being able to read
1258
- * the whole withheld message is the entire reason the line exists — the
1259
- * `warn`'s character count is what tells the reader how much they are about to
1260
- * see. Note that this puts the full text into that ring for a
1261
- * `--verbose` studio run; see `docs/troubleshooting.md`.
1262
- *
1263
- * ORDERING: the `debug` line is emitted when THIS function runs, which is
1264
- * before the `warn` at every site — inline in the `warn`'s template at eight of
1265
- * them, and a couple of statements earlier at the one that hoists the result
1266
- * into a `const reason`. Either way it prints immediately ABOVE the `warn` that
1267
- * refers to it under `--verbose`. Left as is: restructuring every call site to
1268
- * log afterwards would buy an ordering nobody reads top-down anyway.
1269
- */
1270
- function describeAwsFailureForWarn(err, operation) {
1271
- if (isAwsServiceException(err)) return `${clampErrorName(err)}: ${sanitizeServiceExceptionMessage(stringifyThrown(err))}`;
1272
- getLogger().debug(`${operation}: the AWS SDK's own failure message was: ${flattenToOneLine(stringifyThrown(err))}`);
1273
- return describeCredentialLoadFailure(err);
1274
- }
1275
-
1276
802
  //#endregion
1277
803
  //#region src/utils/role-arn.ts
1278
804
  /**
@@ -2383,297 +1909,6 @@ function stringifyValue(value) {
2383
1909
  }
2384
1910
  }
2385
1911
 
2386
- //#endregion
2387
- //#region src/utils/assembly-path.ts
2388
- /**
2389
- * `true` when `candidate` names something strictly beneath `base`, both given
2390
- * as already-resolved absolute paths.
2391
- *
2392
- * The `..` test is SEPARATOR-AWARE rather than a bare `startsWith('..')`,
2393
- * which would also reject a legitimate sibling named `..foo`. The empty-string
2394
- * case is `base` itself; callers decide whether that counts as an escape,
2395
- * because an asset path legitimately names a DIRECTORY while a template path
2396
- * does not.
2397
- */
2398
- function isInside(base, candidate) {
2399
- const rel = relative(base, candidate);
2400
- if (rel === "" || rel === "..") return false;
2401
- if (rel.startsWith(`..${sep}`)) return false;
2402
- return !isAbsolute(rel);
2403
- }
2404
- /**
2405
- * `realpath(3)`, or `undefined` for a path that does not fully resolve.
2406
- *
2407
- * `.native` is load-bearing and must not be simplified to `fs.realpathSync`.
2408
- * The plain form is a JavaScript walker that folds `..` LEXICALLY, so it
2409
- * disagrees with the kernel on a target carrying a `..` after a symlinked
2410
- * component — answering `ENOENT` for a path the kernel resolves, and on a
2411
- * case-insensitive filesystem (APFS by default) it can spin on the same shape.
2412
- * `.native` is libuv's `realpath(3)` and throws the same
2413
- * `ENOENT` / `ELOOP` / `EACCES`.
2414
- */
2415
- function tryRealpath(p) {
2416
- try {
2417
- return realpathSync.native(p);
2418
- } catch {
2419
- return;
2420
- }
2421
- }
2422
- /** `fs.readlinkSync`, or `undefined` when `p` is not a symbolic link. */
2423
- function tryReadlink(p) {
2424
- try {
2425
- return readlinkSync(p);
2426
- } catch {
2427
- return;
2428
- }
2429
- }
2430
- /**
2431
- * Link follows before the walk gives up and reports the path as unresolvable.
2432
- * It does NOT refuse: an exhausted budget answers `undefined`, which leaves
2433
- * the symlink arm silent and the lexical verdict standing. Safe because the OS
2434
- * gives up first (macOS caps at 32, `ELOOP`), so a chain that reaches this cap
2435
- * is one nothing can open anyway.
2436
- */
2437
- const MAX_LINK_HOPS = 40;
2438
- /**
2439
- * Unresolvable path COMPONENTS the climb walks before giving up. Bounds the
2440
- * recursion below, which is one frame per component, so a pathological value
2441
- * cannot raise a `RangeError` from inside {@link tryRealpath}'s own `try` (its
2442
- * `catch` would swallow the crash into a silent `undefined`).
2443
- *
2444
- * KNOW THAT EXHAUSTING THIS FAILS OPEN, and why that is accepted rather than
2445
- * unnoticed. Unlike {@link MAX_LINK_HOPS}, which has the OS's own `ELOOP` cap
2446
- * behind it, nothing else stops a path of 1 001 absent components: the climb
2447
- * gives up, the symlink arm goes silent, and a value that would land outside
2448
- * reads as CONTAINED on the lexical verdict alone. It is not reachable in
2449
- * effect — such a path cannot exist, so `cdkl invoke`'s `existsSync` refuses it
2450
- * and `start-api`'s `docker run` fails to mount it — but that safety lives in
2451
- * the CONSUMERS, not here. A consumer that neither checks existence nor mounts
2452
- * would need its own answer.
2453
- */
2454
- const MAX_PATH_COMPONENTS = 1e3;
2455
- /**
2456
- * Where `target` REALLY points, for a path that may not exist yet.
2457
- *
2458
- * `realpathSync` answers only for a path that fully resolves, and it throws
2459
- * `ENOENT` for a DANGLING symbolic link exactly as it does for an absent file.
2460
- * `cdkl start-api` resolves an asset path without an existence check, so an
2461
- * absent or dangling component must not silence the symlink arm.
2462
- *
2463
- * Each unresolvable component is therefore handled by hand: climb to the
2464
- * deepest ancestor that DOES resolve, then re-apply the remaining components,
2465
- * following any symbolic link with `readlink` and re-resolving its target.
2466
- * `undefined` means the walk could not resolve the path at all.
2467
- *
2468
- * For a path that FULLY RESOLVES the answer is the kernel's and is exact. For
2469
- * one that does not exist yet this is a best-effort MODEL of kernel
2470
- * resolution; the known edge is a `..` INSIDE an unresolvable link's target,
2471
- * folded lexically here while the kernel folds it only after following each
2472
- * preceding component. That edge is benign for THESE callers rather than in
2473
- * general: it needs the target to be absent, and both consumers resolve the
2474
- * path and then open or mount it, so the divergence is between this model and
2475
- * a path nothing can reach. A caller that CREATED a file through such a path
2476
- * would need an `lstat` of its own.
2477
- *
2478
- * `EACCES` is folded into "does not resolve" along with `ENOENT`, so a link
2479
- * under a directory this process cannot traverse reads as contained. Same
2480
- * bound: the consumer's own `existsSync` / mount runs as the same user and
2481
- * fails identically.
2482
- */
2483
- function resolveThroughLinks(target, hops = 0, climbs = 0) {
2484
- const direct = tryRealpath(target);
2485
- if (direct !== void 0) return direct;
2486
- if (hops >= MAX_LINK_HOPS) return void 0;
2487
- if (climbs >= MAX_PATH_COMPONENTS) return void 0;
2488
- const parent = dirname(target);
2489
- if (parent === target) return void 0;
2490
- const realParent = resolveThroughLinks(parent, hops, climbs + 1);
2491
- if (realParent === void 0) return void 0;
2492
- const link = tryReadlink(target);
2493
- if (link === void 0) return join(realParent, basename(target));
2494
- return resolveThroughLinks(resolve(realParent, link), hops + 1, climbs);
2495
- }
2496
- /**
2497
- * Resolve an assembly-supplied `candidate` against `dir` and report whether
2498
- * the result stays inside it.
2499
- *
2500
- * The lexical arm joins exactly the way the call sites used to (`path.join`,
2501
- * NOT `path.resolve`), so the verdict is about the path the caller will
2502
- * actually use. `join`'s handling of an absolute candidate makes this arm
2503
- * strictly more permissive than a `resolve`-based one would be; an absolute
2504
- * value is therefore answered by {@link absoluteAssemblyPathEscape} instead,
2505
- * because this function structurally cannot.
2506
- *
2507
- * The SYMLINK arm exists because the lexical arm alone leaves an equivalent
2508
- * hole: `cdk.out/link -> /etc` plus a candidate of `link/passwd` is lexically
2509
- * contained and still reaches `/etc/passwd`. Both sides go through
2510
- * {@link resolveThroughLinks}, so an assembly directory REACHED through a link
2511
- * (macOS spells `/tmp` as `/private/tmp`) is unaffected.
2512
- *
2513
- * The verdict is about the assembly AS IT SITS ON DISK. It is NOT a defence
2514
- * against a process rewriting that directory concurrently — the caller uses
2515
- * the path after this returns.
2516
- */
2517
- function resolveAssemblyPath(dir, candidate, options) {
2518
- const base = resolve(dir);
2519
- const bound = options?.containWithin === void 0 ? base : resolve(options.containWithin);
2520
- const joined = resolve(join(base, candidate));
2521
- if (!isInside(bound, joined)) return {
2522
- contained: false,
2523
- escape: "lexical",
2524
- path: joined
2525
- };
2526
- const realBound = resolveThroughLinks(bound);
2527
- if (realBound !== void 0) {
2528
- const realTarget = resolveThroughLinks(joined);
2529
- if (realTarget !== void 0 && !isInside(realBound, realTarget)) return {
2530
- contained: false,
2531
- escape: "symlink",
2532
- path: joined,
2533
- realPath: realTarget
2534
- };
2535
- }
2536
- return {
2537
- contained: true,
2538
- path: joined
2539
- };
2540
- }
2541
- /**
2542
- * Whether an ALREADY-ABSOLUTE assembly-supplied path lies outside `bound`.
2543
- *
2544
- * {@link resolveAssemblyPath} cannot answer this, and the reason is structural
2545
- * rather than an oversight: its lexical arm joins with `path.join`, which does
2546
- * NOT honour a leading separator, so an absolute candidate is folded INTO the
2547
- * directory and the verdict would describe a path no caller opens. A site that
2548
- * HONOURS an absolute value needs the verdict about the value itself.
2549
- *
2550
- * The callers are `cdkl invoke`'s and `cdkl start-api`'s
2551
- * `Metadata['aws:asset:path']` resolution. Those honour an absolute path
2552
- * because `cdk synth --no-staging` emits one — CDK writes the asset's absolute
2553
- * SOURCE directory under `aws:cdk:disable-asset-staging`, usually outside the
2554
- * outdir — and they WARN rather than refuse when it leaves the bound, so this
2555
- * returns a verdict rather than throwing. The asset-MANIFEST readers that
2556
- * honour an absolute value take the same verdict: `'honour-warn'` (the
2557
- * start-cloudfront origin) warns when the value is a folder inside the user's
2558
- * project not under a credential or version-control directory, and refuses
2559
- * otherwise, `'honour'` (the soft-reload
2560
- * sources) refuses (#745).
2561
- *
2562
- * It lives HERE so the containment rule has one spelling: it reuses this
2563
- * module's own {@link isInside} and {@link resolveThroughLinks}, symlink arm
2564
- * included, rather than letting a caller re-spell `path.relative` and drift.
2565
- *
2566
- * `bound` itself is NOT an escape, unlike in {@link resolveAssemblyPath},
2567
- * where an empty `path.relative` means "names the directory rather than a file
2568
- * inside it". An asset path legitimately names a DIRECTORY, so a value equal
2569
- * to the bound is inside it and reporting it as outside would be false.
2570
- *
2571
- * THE REAL PATHS DECIDE IN BOTH DIRECTIONS, which is the one place this
2572
- * deliberately does more than {@link resolveAssemblyPath}'s lexical-first
2573
- * ordering. The lexical verdict here is about a SPELLING, and two spellings of
2574
- * the same directory are common rather than exotic: macOS resolves `/tmp` to
2575
- * `/private/tmp` and `/var` to `/private/var`, and a user may symlink `cdk.out`
2576
- * itself. With an outdir of `/tmp/cdk.out` and a `--no-staging` value of
2577
- * `/private/tmp/cdk.out/src`, a lexical-only verdict cries "pointing outside
2578
- * the assembly ... treat this assembly as untrusted" about an asset that is
2579
- * plainly inside it — and this arm exists to make an UNEXPECTED path visible,
2580
- * so a false alarm is the failure that costs it its meaning.
2581
- *
2582
- * Exonerating requires the kernel to answer for BOTH operands: an unresolvable
2583
- * one leaves the lexical verdict standing, so the arm stays loud when it cannot
2584
- * see. (`resolveAssemblyPath` cannot take the same shape: its verdict is about
2585
- * a path built with `path.join`, and letting a real path overrule the lexical
2586
- * `..` there would answer about a location the caller never opens.)
2587
- */
2588
- function absoluteAssemblyPathEscape(bound, absolutePath) {
2589
- const resolvedBound = resolve(bound);
2590
- const target = resolve(absolutePath);
2591
- const lexicallyOutside = !isInside(resolvedBound, target) && target !== resolvedBound;
2592
- const realBound = resolveThroughLinks(resolvedBound);
2593
- const realTarget = resolveThroughLinks(target);
2594
- const reallyOutside = realBound === void 0 || realTarget === void 0 ? void 0 : !isInside(realBound, realTarget) && realTarget !== realBound;
2595
- if (lexicallyOutside) {
2596
- if (reallyOutside === false) return void 0;
2597
- return {
2598
- contained: false,
2599
- escape: "lexical",
2600
- path: target
2601
- };
2602
- }
2603
- if (reallyOutside === true) return {
2604
- contained: false,
2605
- escape: "symlink",
2606
- path: target,
2607
- realPath: realTarget
2608
- };
2609
- }
2610
- /**
2611
- * Whether `candidate` names the SAME directory as `bound` — lexically, or
2612
- * through a symbolic link on either side.
2613
- *
2614
- * An asset source legitimately names a DIRECTORY, so a value that resolves
2615
- * onto the output directory itself is not an escape; but it hands the WHOLE
2616
- * assembly to the sink, which is worth a line. A caller that re-spells this as
2617
- * `resolve(a) === resolve(b)` misses every second spelling of the bound
2618
- * (macOS `/tmp` vs `/private/tmp`, a symlinked `cdk.out`), and
2619
- * {@link absoluteAssemblyPathEscape} exonerates exactly those spellings as
2620
- * inside — so the equality beside it would stay silent for them.
2621
- *
2622
- * Conservative on failure: an unresolvable side answers from the lexical
2623
- * comparison alone, so it can only say "not the same", never wrongly claim
2624
- * identity.
2625
- */
2626
- function namesTheSameDirectory(bound, candidate) {
2627
- const resolvedBound = resolve(bound);
2628
- const target = resolve(candidate);
2629
- if (target === resolvedBound) return true;
2630
- const realBound = resolveThroughLinks(resolvedBound);
2631
- const realTarget = resolveThroughLinks(target);
2632
- return realBound !== void 0 && realTarget !== void 0 && realBound === realTarget;
2633
- }
2634
- /**
2635
- * The escape verdict for an assembly-supplied path WITHOUT throwing, taking
2636
- * whichever arm the value's own shape calls for: {@link absoluteAssemblyPathEscape}
2637
- * for an absolute value, {@link resolveAssemblyPath} for a relative one.
2638
- *
2639
- * For a caller that only WARNS and has many values to judge — the BuildKit
2640
- * passthroughs in `src/assets/buildkit-passthrough-warnings.ts`. `base` is what
2641
- * a relative value resolves against, `bound` what it must stay inside.
2642
- * Returns `undefined` when the value is fine, including when it names `bound`
2643
- * itself ({@link namesTheSameDirectory}).
2644
- */
2645
- function assemblyPathEscape(base, bound, candidate) {
2646
- if (isAbsolute(candidate)) return absoluteAssemblyPathEscape(bound, candidate);
2647
- const resolved = resolveAssemblyPath(base, candidate, { containWithin: bound });
2648
- if (resolved.contained || namesTheSameDirectory(bound, resolved.path)) return void 0;
2649
- return resolved;
2650
- }
2651
- /**
2652
- * The shared tail of a containment refusal: what the value resolved to, what
2653
- * it escaped, and why that means the assembly is not CDK-generated. The call
2654
- * site supplies its own subject ("Lambda 'X' has ... which ") and its own
2655
- * error class. `action` completes "Refusing to ..." and is REQUIRED rather
2656
- * than defaulted: a default is a branch no caller takes, so it can neither be
2657
- * fenced nor be right for the next caller.
2658
- *
2659
- * `provenanceOverride` replaces that whole closing sentence for a caller that
2660
- * does NOT refuse — the BuildKit passthrough warnings, where "Refusing to ..."
2661
- * would be a false statement about a value that is forwarded anyway.
2662
- */
2663
- function renderAssemblyPathEscape(escape, dir, action, provenanceOverride) {
2664
- 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}.`;
2665
- const base = resolve(dir);
2666
- const realBase = resolveThroughLinks(base) ?? base;
2667
- const shownPath = sanitizeServiceExceptionMessage(escape.path);
2668
- const shownBase = sanitizeServiceExceptionMessage(base);
2669
- if (escape.escape === "symlink") {
2670
- if (escape.realPath === realBase) return `resolves to '${shownPath}', a symbolic link to the directory '${shownBase}' itself rather than to a path inside it. ${provenance}`;
2671
- return `resolves to '${shownPath}', which leads through a symbolic link to '${sanitizeServiceExceptionMessage(escape.realPath)}', outside '${shownBase}'. ${provenance}`;
2672
- }
2673
- if (escape.path === base) return `names the directory '${shownBase}' itself rather than a path inside it. ${provenance}`;
2674
- return `resolves to '${shownPath}', outside '${shownBase}'. ${provenance}`;
2675
- }
2676
-
2677
1912
  //#endregion
2678
1913
  //#region src/local/lambda-resolver.ts
2679
1914
  /**
@@ -2999,7 +2234,7 @@ function resolveAssetCodeDirectory(manifestDir, assetPath, wrapError, assetOutdi
2999
2234
  if (isAbsolute(assetPath)) {
3000
2235
  const absolute = resolve(assetPath);
3001
2236
  const escape = absoluteAssemblyPathEscape(assetOutdir, absolute);
3002
- if (escape !== void 0) getLogger().warn(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' has an absolute Metadata['aws:asset:path'] pointing outside the assembly: '${sanitizeServiceExceptionMessage(absolute)}'` + (escape.escape === "symlink" ? ` (through a symbolic link to '${sanitizeServiceExceptionMessage(escape.realPath)}')` : "") + `. ${getEmbedConfig().productName} will read that directory and expose its contents to the container — bind-mounted read-only, or copied when the asset is a .zip or one of several merged layers — where the code in this assembly can read it. This is what cdk synth --no-staging emits, and is expected for it; if you did not synthesize with that flag, treat this assembly as untrusted.`);
2237
+ if (escape !== void 0) getLogger().warn(`Lambda ${displayUntrustedValue(logicalId)} has an absolute Metadata['aws:asset:path'] pointing outside the assembly: ${displayUntrustedValue(absolute)}` + (escape.escape === "symlink" ? ` (through a symbolic link to ${displayUntrustedValue(escape.realPath)})` : "") + `. ${getEmbedConfig().productName} will read that directory and expose its contents to the container — bind-mounted read-only, or copied when the asset is a .zip or one of several merged layers — where the code in this assembly can read it. This is what cdk synth --no-staging emits, and is expected for it; if you did not synthesize with that flag, treat this assembly as untrusted.`);
3003
2238
  warnOutsideNamedDirectory(assetOutdir, absolute);
3004
2239
  return absolute;
3005
2240
  }
@@ -3011,7 +2246,7 @@ function resolveAssetCodeDirectory(manifestDir, assetPath, wrapError, assetOutdi
3011
2246
  if (!resolved.contained) {
3012
2247
  const climbsOut = assetPath === ".." || assetPath.startsWith(`..${sep}`) || assetPath.startsWith("../");
3013
2248
  const stageHint = basename(assetOutdir).startsWith("assembly-") && climbsOut ? " This directory is named like a cdk.Stage sub-assembly, but its parent is not an assembly (no manifest.json), so it was treated as the assembly root. If you meant to point --app at a Stage inside an app's output directory, point it at that output directory." : "";
3014
- throw wrapError(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' has Metadata['aws:asset:path']='${sanitizeServiceExceptionMessage(assetPath)}' which ${renderAssemblyPathEscape(resolved, assetOutdir, "mount it")}${stageHint}`);
2249
+ throw wrapError(`Lambda ${displayUntrustedValue(logicalId)} has Metadata['aws:asset:path']=${displayUntrustedValue(assetPath)} which ${renderAssemblyPathEscape(resolved, assetOutdir, "mount it")}${stageHint}`);
3015
2250
  }
3016
2251
  warnOutsideNamedDirectory(assetOutdir, resolved.path);
3017
2252
  return resolved.path;
@@ -3265,7 +2500,7 @@ function warnOutsideNamedDirectory(assetOutdir, resolved) {
3265
2500
  if (absoluteAssemblyPathEscape(named, resolved) === void 0) return;
3266
2501
  if (warnedOutsideNamed.has(resolved)) return;
3267
2502
  warnedOutsideNamed.add(resolved);
3268
- getLogger().warn(`'${sanitizeServiceExceptionMessage(resolved)}' is outside '${sanitizeServiceExceptionMessage(named)}', the directory --app named. It is inside the assembly root derived from it, so ${getEmbedConfig().productName} is using it.`);
2503
+ getLogger().warn(`${displayUntrustedValue(resolved)} is outside ${displayUntrustedValue(named)}, the directory --app named. It is inside the assembly root derived from it, so ${getEmbedConfig().productName} is using it.`);
3269
2504
  }
3270
2505
  /**
3271
2506
  * Say that the assembly root was DERIVED rather than given.
@@ -3278,7 +2513,7 @@ function warnOutsideNamedDirectory(assetOutdir, resolved) {
3278
2513
  function warnDerivedAssemblyRoot(named, derived) {
3279
2514
  if (warnedDerivedRoots.has(derived)) return;
3280
2515
  warnedDerivedRoots.add(derived);
3281
- getLogger().warn(`'${sanitizeServiceExceptionMessage(named)}' is a cdk.Stage sub-assembly, so ${getEmbedConfig().productName} is treating its parent '${sanitizeServiceExceptionMessage(derived)}' as the assembly root — that is where cdk synth stages a Stage's assets. Everything under that parent is now inside the containment bound, including siblings of the directory you named. If you did not expect the wider directory, point --app at the app's own output directory instead.`);
2516
+ getLogger().warn(`${displayUntrustedValue(named)} is a cdk.Stage sub-assembly, so ${getEmbedConfig().productName} is treating its parent ${displayUntrustedValue(derived)} as the assembly root — that is where cdk synth stages a Stage's assets. Everything under that parent is now inside the containment bound, including siblings of the directory you named. If you did not expect the wider directory, point --app at the app's own output directory instead.`);
3282
2517
  }
3283
2518
  /**
3284
2519
  * Resolve the local directory that corresponds to a function's deployed
@@ -3297,14 +2532,14 @@ function warnDerivedAssemblyRoot(named, derived) {
3297
2532
  */
3298
2533
  function resolveAssetCodePath$1(stack, logicalId, resource, options = {}) {
3299
2534
  const assetPath = resource.Metadata?.["aws:asset:path"];
3300
- if (typeof assetPath !== "string" || assetPath.length === 0) throw new LocalInvokeResolutionError(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} invoke needs this hint to find the local asset directory. Re-synthesize the app (without \`--output <stale-dir>\`) and retry.`);
2535
+ if (typeof assetPath !== "string" || assetPath.length === 0) throw new LocalInvokeResolutionError(`Lambda ${displayUntrustedValue(logicalId)} has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} invoke needs this hint to find the local asset directory. Re-synthesize the app (without \`--output <stale-dir>\`) and retry.`);
3301
2536
  const { manifestDir, assetOutdir } = assetPathDirs(stack);
3302
2537
  const abs = resolveAssetCodeDirectory(manifestDir, assetPath, (message) => new LocalInvokeResolutionError(message), assetOutdir, logicalId);
3303
- if (!existsSync(abs)) throw new LocalInvokeResolutionError(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' asset path '${sanitizeServiceExceptionMessage(abs)}' does not exist. Re-synthesize the app and retry.`);
2538
+ if (!existsSync(abs)) throw new LocalInvokeResolutionError(`Lambda ${displayUntrustedValue(logicalId)} asset path ${displayUntrustedValue(abs)} does not exist. Re-synthesize the app and retry.`);
3304
2539
  const stat = statSync(abs);
3305
2540
  if (stat.isDirectory()) return abs;
3306
2541
  if (options.allowZip && stat.isFile() && abs.toLowerCase().endsWith(".zip")) return abs;
3307
- throw new LocalInvokeResolutionError(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' asset path '${sanitizeServiceExceptionMessage(abs)}' is not a directory` + (options.allowZip ? " or a .zip archive" : "") + ". Re-synthesize the app and retry.");
2542
+ throw new LocalInvokeResolutionError(`Lambda ${displayUntrustedValue(logicalId)} asset path ${displayUntrustedValue(abs)} is not a directory` + (options.allowZip ? " or a .zip archive" : "") + ". Re-synthesize the app and retry.");
3308
2543
  }
3309
2544
  /**
3310
2545
  * Turn a resolved function-code asset path into a directory ready to
@@ -3323,14 +2558,14 @@ function resolveAssetCodePath$1(stack, logicalId, resource, options = {}) {
3323
2558
  * existing tmpdir cleanup.
3324
2559
  */
3325
2560
  function materializeAssetCodeDir(codePath) {
3326
- if (!existsSync(codePath)) throw new LocalInvokeResolutionError(`Lambda asset path '${sanitizeServiceExceptionMessage(codePath)}' does not exist. Re-synthesize the app and retry.`);
2561
+ if (!existsSync(codePath)) throw new LocalInvokeResolutionError(`Lambda asset path ${displayUntrustedValue(codePath)} does not exist. Re-synthesize the app and retry.`);
3327
2562
  if (statSync(codePath).isDirectory()) return { dir: codePath };
3328
2563
  const zipBytes = readFileSync(codePath);
3329
2564
  let files;
3330
2565
  try {
3331
2566
  files = unzipSync(zipBytes);
3332
2567
  } catch (err) {
3333
- throw new LocalInvokeResolutionError(`Lambda asset '${sanitizeServiceExceptionMessage(codePath)}' is a file but could not be read as a ZIP archive: ${err instanceof Error ? err.message : String(err)}. Re-synthesize the app and retry.`);
2568
+ throw new LocalInvokeResolutionError(`Lambda asset ${displayUntrustedValue(codePath)} is a file but could not be read as a ZIP archive: ${err instanceof Error ? err.message : String(err)}. Re-synthesize the app and retry.`);
3334
2569
  }
3335
2570
  const modes = parseZipUnixModes(zipBytes);
3336
2571
  const S_IFMT = 61440;
@@ -6750,7 +5985,7 @@ var CfnLocalStateProvider = class {
6750
5985
  return map.get(exportName);
6751
5986
  },
6752
5987
  async resolveGetStackOutput(producerStack, producerRegion, outputName) {
6753
- logger.warn(`${label}: Fn::GetStackOutput '${producerStack}.${outputName}' (${producerRegion}) has no CloudFormation equivalent and cannot be resolved when reading state from a CloudFormation stack. Use Fn::ImportValue against an exported output instead.`);
5988
+ logger.warn(`${label}: Fn::GetStackOutput ${displayUntrustedValue(producerStack)}.${displayUntrustedValue(outputName)} (${displayUntrustedValue(producerRegion)}) has no CloudFormation equivalent and cannot be resolved when reading state from a CloudFormation stack. Use Fn::ImportValue against an exported output instead.`);
6754
5989
  }
6755
5990
  };
6756
5991
  }
@@ -7665,7 +6900,7 @@ async function resolveImportValueAsync(arg, context) {
7665
6900
  const exportName = inner.value;
7666
6901
  if (!context.crossStackResolver) return {
7667
6902
  kind: "unresolved",
7668
- reason: `Fn::ImportValue '${exportName}': no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`
6903
+ reason: `Fn::ImportValue ${displayUntrustedValue(exportName)}: no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`
7669
6904
  };
7670
6905
  let resolved;
7671
6906
  try {
@@ -7673,12 +6908,12 @@ async function resolveImportValueAsync(arg, context) {
7673
6908
  } catch (err) {
7674
6909
  return {
7675
6910
  kind: "unresolved",
7676
- reason: `Fn::ImportValue '${flattenToOneLine(exportName)}': lookup failed: ${describeAwsFailureForWarn(err, "CrossStackResolver.resolveImport (Fn::ImportValue)")}`
6911
+ reason: `Fn::ImportValue ${displayUntrustedValue(exportName)}: lookup failed: ${describeAwsFailureForWarn(err, "CrossStackResolver.resolveImport (Fn::ImportValue)")}`
7677
6912
  };
7678
6913
  }
7679
6914
  if (resolved === void 0) return {
7680
6915
  kind: "unresolved",
7681
- reason: `Fn::ImportValue '${exportName}': export not found in any deployed stack in this region`
6916
+ reason: `Fn::ImportValue ${displayUntrustedValue(exportName)}: export not found in any deployed stack in this region`
7682
6917
  };
7683
6918
  return {
7684
6919
  kind: "literal",
@@ -7732,15 +6967,15 @@ async function resolveGetStackOutputAsync(arg, context) {
7732
6967
  } else region = context.consumerRegion ?? context.pseudoParameters?.region;
7733
6968
  if (!region) return {
7734
6969
  kind: "unresolved",
7735
- reason: `Fn::GetStackOutput '${stackName}.${outputName}': no Region supplied and consumer region is unknown (set --region, AWS_REGION, or env.region on the CDK stack)`
6970
+ reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)}: no Region supplied and consumer region is unknown (set --region, AWS_REGION, or env.region on the CDK stack)`
7736
6971
  };
7737
6972
  if (args["RoleArn"] !== void 0 && args["RoleArn"] !== null) return {
7738
6973
  kind: "unresolved",
7739
- reason: `Fn::GetStackOutput '${stackName}.${outputName}': RoleArn (cross-account) is not yet supported by the state source — tracked under issue #449`
6974
+ reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)}: RoleArn (cross-account) is not yet supported by the state source — tracked under issue #449`
7740
6975
  };
7741
6976
  if (!context.crossStackResolver) return {
7742
6977
  kind: "unresolved",
7743
- reason: `Fn::GetStackOutput '${stackName}.${outputName}': no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`
6978
+ reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)}: no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`
7744
6979
  };
7745
6980
  let resolved;
7746
6981
  try {
@@ -7748,12 +6983,12 @@ async function resolveGetStackOutputAsync(arg, context) {
7748
6983
  } catch (err) {
7749
6984
  return {
7750
6985
  kind: "unresolved",
7751
- reason: `Fn::GetStackOutput '${flattenToOneLine(stackName)}.${flattenToOneLine(outputName)}' (${flattenToOneLine(region)}): lookup failed: ${describeAwsFailureForWarn(err, "CrossStackResolver.resolveGetStackOutput (Fn::GetStackOutput)")}`
6986
+ reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)} (${displayUntrustedValue(region)}): lookup failed: ${describeAwsFailureForWarn(err, "CrossStackResolver.resolveGetStackOutput (Fn::GetStackOutput)")}`
7752
6987
  };
7753
6988
  }
7754
6989
  if (resolved === void 0) return {
7755
6990
  kind: "unresolved",
7756
- reason: `Fn::GetStackOutput '${stackName}.${outputName}' (${region}): output not found in producer stack state`
6991
+ reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)} (${displayUntrustedValue(region)}): output not found in producer stack state`
7757
6992
  };
7758
6993
  return {
7759
6994
  kind: "literal",
@@ -9065,14 +8300,14 @@ function resolveRuntimeCodeMountPath(runtime) {
9065
8300
  */
9066
8301
  function resolveAssetSourcePath(opts) {
9067
8302
  const { manifestDir, value, assetOutdir, field, subject, action, wrapError } = opts;
9068
- const shownValue = sanitizeServiceExceptionMessage(value);
8303
+ const shownValue = displayUntrustedValue(value);
9069
8304
  if (isAbsolute(value) && opts.absolute !== "fold") {
9070
8305
  const absolute = resolve(value);
9071
8306
  const escape = absoluteAssemblyPathEscape(assetOutdir, absolute);
9072
- if (escape !== void 0 && opts.absolute === "honour") throw wrapError(`${subject} has an absolute ${field}='${shownValue}' which ` + renderAssemblyPathEscape(escape, assetOutdir, action));
8307
+ if (escape !== void 0 && opts.absolute === "honour") throw wrapError(`${subject} has an absolute ${field}=${shownValue} which ` + renderAssemblyPathEscape(escape, assetOutdir, action));
9073
8308
  if (escape !== void 0) {
9074
8309
  const refusal = originScopeRefusal(absolute, assetOutdir);
9075
- if (refusal !== void 0) throw wrapError(`${subject} has an absolute ${field}='${shownValue}' which ${refusal}. An absolute ${field} is accepted only as a cdk synth --no-staging source folder: a directory inside your project (the git work tree holding the output directory, or the current directory when it contains the output directory or is a git work-tree root), not under a credential or version-control directory. Refusing to ${action}.`);
8310
+ if (refusal !== void 0) throw wrapError(`${subject} has an absolute ${field}=${shownValue} which ${refusal}. An absolute ${field} is accepted only as a cdk synth --no-staging source folder: a directory inside your project (the git work tree holding the output directory, or the current directory when it contains the output directory or is a git work-tree root), not under a credential or version-control directory. Refusing to ${action}.`);
9076
8311
  warnAbsoluteOutsideAssembly(opts, absolute, escape);
9077
8312
  return absolute;
9078
8313
  }
@@ -9085,7 +8320,7 @@ function resolveAssetSourcePath(opts) {
9085
8320
  warnWholeAssemblyAsSource(opts, resolved.path);
9086
8321
  return resolved.path;
9087
8322
  }
9088
- throw wrapError(`${subject} has ${field}='${shownValue}' which ` + renderAssemblyPathEscape(resolved, assetOutdir, action));
8323
+ throw wrapError(`${subject} has ${field}=${shownValue} which ` + renderAssemblyPathEscape(resolved, assetOutdir, action));
9089
8324
  }
9090
8325
  /**
9091
8326
  * Why an accepted-absolute root is too broad to be a `--no-staging` source, or
@@ -9139,9 +8374,9 @@ function originScopeRefusal(absolute, assetOutdir) {
9139
8374
  hidden = dotted;
9140
8375
  }
9141
8376
  if (isProjectRoot) return "is your project root itself; name a folder inside it";
9142
- if (hidden !== void 0) return `passes through the credential / version-control directory '${sanitizeServiceExceptionMessage(hidden)}' inside your project`;
9143
- const shown = projects.map((p) => `'${sanitizeServiceExceptionMessage(p)}'`).join(" or ");
9144
- return projects.length === 0 ? "is outside any usable project root (the current directory and the git work tree are too broad to scope it)" : `resolves to '${sanitizeServiceExceptionMessage(root)}', outside your project (${shown})`;
8377
+ if (hidden !== void 0) return `passes through the credential / version-control directory ${displayUntrustedValue(hidden)} inside your project`;
8378
+ const shown = projects.map((p) => displayUntrustedValue(p)).join(" or ");
8379
+ return projects.length === 0 ? "is outside any usable project root (the current directory and the git work tree are too broad to scope it)" : `resolves to ${displayUntrustedValue(root)}, outside your project (${shown})`;
9145
8380
  }
9146
8381
  /**
9147
8382
  * Directory names no `--no-staging` site folder sits under: credential stores
@@ -9235,7 +8470,7 @@ function realPath(p, hops = 0) {
9235
8470
  const warnedAbsoluteOutside = /* @__PURE__ */ new Set();
9236
8471
  function warnAbsoluteOutsideAssembly(opts, absolute, escape) {
9237
8472
  const logger = getLogger().child("assets");
9238
- const line = `${opts.subject} has an absolute ${opts.field} pointing outside the assembly: '${sanitizeServiceExceptionMessage(absolute)}'` + (escape.escape === "symlink" ? ` (through a symbolic link to '${sanitizeServiceExceptionMessage(escape.realPath)}')` : "") + `. ${getEmbedConfig().productName} will ${opts.sink}. ` + (escape.escape === "lexical" ? "This is what cdk synth --no-staging emits, and is expected for it; if you did not synthesize with that flag, treat this assembly as untrusted." : "No CDK synth writes this, so treat this assembly as untrusted unless you made that link yourself.");
8473
+ const line = `${opts.subject} has an absolute ${opts.field} pointing outside the assembly: ` + displayUntrustedValue(absolute) + (escape.escape === "symlink" ? ` (through a symbolic link to ${displayUntrustedValue(escape.realPath)})` : "") + `. ${getEmbedConfig().productName} will ${opts.sink}. ` + (escape.escape === "lexical" ? "This is what cdk synth --no-staging emits, and is expected for it; if you did not synthesize with that flag, treat this assembly as untrusted." : "No CDK synth writes this, so treat this assembly as untrusted unless you made that link yourself.");
9239
8474
  if (warnedAbsoluteOutside.has(line)) {
9240
8475
  logger.debug(line);
9241
8476
  return;
@@ -9252,7 +8487,7 @@ function warnAbsoluteOutsideAssembly(opts, absolute, escape) {
9252
8487
  const warnedWholeAssembly = /* @__PURE__ */ new Set();
9253
8488
  function warnWholeAssemblyAsSource(opts, outdir) {
9254
8489
  const logger = getLogger().child("assets");
9255
- const line = `${opts.subject} has ${opts.field} naming the assembly's output directory ITSELF: '${sanitizeServiceExceptionMessage(outdir)}'. ${getEmbedConfig().productName} will ${opts.sink} — that is the WHOLE assembly, every template and every staged asset, not one asset directory. No CDK synth emits this, so treat this assembly as untrusted unless you wrote that path yourself.`;
8490
+ const line = `${opts.subject} has ${opts.field} naming the assembly's output directory ITSELF: ${displayUntrustedValue(outdir)}. ${getEmbedConfig().productName} will ${opts.sink} — that is the WHOLE assembly, every template and every staged asset, not one asset directory. No CDK synth emits this, so treat this assembly as untrusted unless you wrote that path yourself.`;
9256
8491
  if (warnedWholeAssembly.has(line)) {
9257
8492
  logger.debug(line);
9258
8493
  return;
@@ -9405,7 +8640,7 @@ function warnEscapingBuildKitPaths(source, base, bound) {
9405
8640
  const escape = assemblyPathEscape(base, bound, ref.path);
9406
8641
  if (escape === void 0) continue;
9407
8642
  const verb = ref.write ? "WRITE to" : "read";
9408
- const line = `Docker asset ${ref.field}` + (ref.where === ref.field ? "" : `['${sanitizeServiceExceptionMessage(ref.where)}']`) + ` names a host path outside the assembly, which ` + renderAssemblyPathEscape(escape, bound, "load", `${getEmbedConfig().productName} will ${verb} it during the image build, matching the CDK CLI — a pre-synthesized assembly is trusted input. If you did not produce this assembly, that is a host path it chose and the CloudFormation template does not show.`);
8643
+ const line = `Docker asset ${ref.field}` + (ref.where === ref.field ? "" : `[${displayUntrustedValue(ref.where)}]`) + ` names a host path outside the assembly, which ` + renderAssemblyPathEscape(escape, bound, "load", `${getEmbedConfig().productName} will ${verb} it during the image build, matching the CDK CLI — a pre-synthesized assembly is trusted input. If you did not produce this assembly, that is a host path it chose and the CloudFormation template does not show.`);
9409
8644
  if (warnedPassthroughs.has(line)) {
9410
8645
  logger.debug(line);
9411
8646
  continue;
@@ -9453,9 +8688,10 @@ function warnEscapingBuildKitPaths(source, base, bound) {
9453
8688
  * to `debug`, where the argv already is. Deduping the LINE must never dedupe
9454
8689
  * the WORK: the spawn happens every time regardless.
9455
8690
  *
9456
- * `sanitizeServiceExceptionMessage` for the same reason `assembly-path.ts`
9457
- * uses it: the value is manifest-chosen, it keeps control characters, and an
9458
- * unsanitized one lets the command forge its own log line. It is applied to
8691
+ * `displayUntrustedValue` for the same reason `assembly-path.ts` uses it: the
8692
+ * value is manifest-chosen, it keeps control characters and quotes, and an
8693
+ * unsanitized one lets the command forge its own log line or close a quote
8694
+ * of ours (go-to-k/cdk-local#758). It is applied to
9459
8695
  * the DISPLAYED command only — the dedupe key is built from the raw argv and
9460
8696
  * cwd, so two commands differing only in control characters stay distinct
9461
8697
  * keys.
@@ -9465,12 +8701,12 @@ function warnManifestExecutable(cmd, executable, cwd) {
9465
8701
  const logger = getLogger().child("docker-build");
9466
8702
  const key = JSON.stringify([cwd, executable]);
9467
8703
  if (warnedExecutables.has(key)) {
9468
- logger.debug(`source.executable already announced this process: ${sanitizeServiceExceptionMessage(cmd)} (cwd=${cwd})`);
8704
+ logger.debug(`source.executable already announced this process: ${displayUntrustedValue(cmd)} (cwd=${displayUntrustedValue(cwd)})`);
9469
8705
  return;
9470
8706
  }
9471
8707
  warnedExecutables.add(key);
9472
8708
  const argCount = executable.length - 1;
9473
- logger.warn(`Docker asset source.executable runs a command this asset manifest chose, on this machine: '${sanitizeServiceExceptionMessage(cmd)}'` + (argCount > 0 ? ` (with ${argCount} argument(s); --verbose shows them)` : "") + ". cdk-local runs it, matching the CDK CLI — a pre-synthesized assembly is trusted input. Note that this means running a local command against a pre-synthesized assembly DOES execute code from it, which the CloudFormation template does not show.");
8709
+ logger.warn(`Docker asset source.executable runs a command this asset manifest chose, on this machine: ${displayUntrustedValue(cmd)}` + (argCount > 0 ? ` (with ${argCount} argument(s); --verbose shows them)` : "") + `. ${getEmbedConfig().productName} runs it, matching the CDK CLI — a pre-synthesized assembly is trusted input. Note that this means running a local command against a pre-synthesized assembly DOES execute code from it, which the CloudFormation template does not show.`);
9474
8710
  }
9475
8711
  /**
9476
8712
  * Build a Docker image from a CDK asset source. Returns the local image
@@ -9506,7 +8742,7 @@ async function buildDockerImage(asset, cdkOutDir, options) {
9506
8742
  const [cmd, ...args] = source.executable;
9507
8743
  if (!cmd) throw options.wrapError("asset source.executable[] is empty");
9508
8744
  const cwd = source.directory ? contextDirectory(source.directory, "run this asset's source.executable with that directory as its working directory") : cdkOutDir;
9509
- logger.debug(`Building Docker image via executable: ${sanitizeServiceExceptionMessage(source.executable.join(" "))} (cwd=${sanitizeServiceExceptionMessage(cwd)})`);
8745
+ logger.debug(`Building Docker image via executable: ${displayUntrustedValue(source.executable.join(" "))} (cwd=${displayUntrustedValue(cwd)})`);
9510
8746
  warnManifestExecutable(cmd, source.executable, cwd);
9511
8747
  let result;
9512
8748
  try {
@@ -9516,7 +8752,7 @@ async function buildDockerImage(asset, cdkOutDir, options) {
9516
8752
  throw options.wrapError(e.stderr || e.message || String(err));
9517
8753
  }
9518
8754
  const tag = result.stdout.trim();
9519
- if (!tag) throw options.wrapError(`docker build executable produced no output (expected the local image tag on stdout): ${cmd} ${args.join(" ")}`);
8755
+ if (!tag) throw options.wrapError(`docker build executable produced no output (expected the local image tag on stdout): ${displayUntrustedValue(cmd)}`);
9520
8756
  return tag;
9521
8757
  }
9522
8758
  if (!source.directory) throw options.wrapError(`DockerImageAssetSource must set either 'directory' or 'executable' (got: ${JSON.stringify(source)})`);
@@ -9525,7 +8761,7 @@ async function buildDockerImage(asset, cdkOutDir, options) {
9525
8761
  const contextDir = contextDirectory(source.directory, `send that directory to docker build as the context of an image ${getEmbedConfig().productName} then runs locally`);
9526
8762
  buildArgs.push(".");
9527
8763
  warnEscapingBuildKitPaths(source, contextDir, assetOutdir);
9528
- logger.debug(`${getDockerCmd()} ${buildArgs.join(" ")} (cwd=${contextDir})`);
8764
+ logger.debug(`${getDockerCmd()} ${buildArgs.join(" ")} (cwd=${displayUntrustedValue(contextDir)})`);
9529
8765
  try {
9530
8766
  await runDockerStreaming(buildArgs, {
9531
8767
  cwd: contextDir,
@@ -9840,7 +9076,7 @@ async function buildContainerImage(asset, cdkOutDir, options) {
9840
9076
  const actualTag = await buildDockerImage(asset, cdkOutDir, {
9841
9077
  tag,
9842
9078
  platform,
9843
- wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${sanitizeServiceExceptionMessage(asset.source.directory ?? asset.source.executable?.join(" ") ?? "")}): ${stderr}`),
9079
+ wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${displayUntrustedValue(asset.source.directory ?? asset.source.executable?.[0] ?? "")}): ${stderr}`),
9844
9080
  progressLabel: `Building container image (platform=${platform})`,
9845
9081
  ...options.assetOutdir !== void 0 && { assetOutdir: options.assetOutdir }
9846
9082
  });
@@ -10275,7 +9511,7 @@ var AssetManifestLoader = class {
10275
9511
  */
10276
9512
  async loadManifest(cdkOutputDir, stackName) {
10277
9513
  const resolved = resolveAssemblyPath(cdkOutputDir, `${stackName}.assets.json`);
10278
- if (!resolved.contained) throw new Error(`Refusing to read the asset manifest for stack '${sanitizeServiceExceptionMessage(stackName)}': it ` + renderAssemblyPathEscape(resolved, cdkOutputDir, "read it"));
9514
+ if (!resolved.contained) throw new Error(`Refusing to read the asset manifest for stack ${displayUntrustedValue(stackName)}: it ` + renderAssemblyPathEscape(resolved, cdkOutputDir, "read it"));
10279
9515
  const manifestPath = join$1(cdkOutputDir, `${stackName}.assets.json`);
10280
9516
  try {
10281
9517
  this.logger.debug(`Loading asset manifest from: ${manifestPath}`);
@@ -15053,7 +14289,7 @@ function parseHttpV2IdentitySources(raw) {
15053
14289
  */
15054
14290
  function parseCognitoUserPoolArn(arn, location) {
15055
14291
  const m = /^arn:aws[a-z0-9-]*:cognito-idp:([a-z0-9-]+):[0-9]+:userpool\/(.+)$/.exec(arn);
15056
- if (!m) throw new RouteDiscoveryError(`${location}: malformed Cognito User Pool ARN '${arn}'. Expected 'arn:aws:cognito-idp:<region>:<account>:userpool/<id>'.`);
14292
+ if (!m) throw new RouteDiscoveryError(`${location}: malformed Cognito User Pool ARN ${displayUntrustedValue(arn)}. Expected 'arn:aws:cognito-idp:<region>:<account>:userpool/<id>'.`);
15057
14293
  return {
15058
14294
  region: m[1],
15059
14295
  userPoolId: m[2]
@@ -20117,7 +19353,7 @@ function resolveImageLambda(args) {
20117
19353
  */
20118
19354
  function resolveAssetCodePath(stack, logicalId, resource) {
20119
19355
  const assetPath = resource.Metadata?.["aws:asset:path"];
20120
- if (typeof assetPath !== "string" || assetPath.length === 0) throw new Error(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} start-api needs this hint to find the local asset directory. Re-synthesize the app and retry.`);
19356
+ if (typeof assetPath !== "string" || assetPath.length === 0) throw new Error(`Lambda ${displayUntrustedValue(logicalId)} has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} start-api needs this hint to find the local asset directory. Re-synthesize the app and retry.`);
20121
19357
  const { manifestDir, assetOutdir } = assetPathDirs(stack);
20122
19358
  return resolveAssetCodeDirectory(manifestDir, assetPath, (message) => new Error(message), assetOutdir, logicalId);
20123
19359
  }
@@ -21630,7 +20866,7 @@ async function buildAgentCoreCodeImage(options) {
21630
20866
  ], { progressLabel: `Building agent image (runtime=${options.runtime})` });
21631
20867
  } catch (err) {
21632
20868
  const stderr = err.stderr?.trim();
21633
- throw new LocalInvokeBuildError(`docker build failed for AgentCore code artifact (${flattenToOneLine(options.sourceDir)})${stderr ? `: ${stderr}` : ""}`);
20869
+ throw new LocalInvokeBuildError(`docker build failed for AgentCore code artifact (${displayUntrustedValue(options.sourceDir)})${stderr ? `: ${stderr}` : ""}`);
21634
20870
  } finally {
21635
20871
  await rm(buildDir, {
21636
20872
  recursive: true,
@@ -21675,14 +20911,14 @@ async function warnIfDependenciesNotVendored(sourceDir, runtime, isNode, logger)
21675
20911
  }
21676
20912
  const has = (name) => entries.includes(name);
21677
20913
  if (isNode) {
21678
- if (has("package.json") && !has("node_modules")) logger.warn(`AgentCore code bundle '${flattenToOneLine(sourceDir)}' declares package.json but does not vendor node_modules. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail to resolve them. Vendor dependencies into the bundle (e.g. 'npm install --omit=dev' in the bundle dir) so the deploy artifact is self-contained. cdk-local runs the bundle as-is to match the deployed runtime.`);
20914
+ if (has("package.json") && !has("node_modules")) logger.warn(`AgentCore code bundle ${displayUntrustedValue(sourceDir)} declares package.json but does not vendor node_modules. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail to resolve them. Vendor dependencies into the bundle (e.g. 'npm install --omit=dev' in the bundle dir) so the deploy artifact is self-contained. cdk-local runs the bundle as-is to match the deployed runtime.`);
21679
20915
  return;
21680
20916
  }
21681
20917
  const manifest = await pythonManifestDeclaringDeps(sourceDir, entries);
21682
20918
  const vendored = entries.some((e) => e.endsWith(".dist-info"));
21683
20919
  if (manifest && !vendored) {
21684
20920
  const pyVersion = runtime.replace("PYTHON_", "").replace("_", ".");
21685
- logger.warn(`AgentCore code bundle '${flattenToOneLine(sourceDir)}' declares ${manifest} but does not vendor its dependencies. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail with ModuleNotFoundError. Vendor arm64 wheels into the bundle, e.g.:
20921
+ logger.warn(`AgentCore code bundle ${displayUntrustedValue(sourceDir)} declares ${manifest} but does not vendor its dependencies. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail with ModuleNotFoundError. Vendor arm64 wheels into the bundle, e.g.:
21686
20922
  uv pip install --python-platform aarch64-manylinux2014 --python-version ${pyVersion} --target <bundle-dir> -r requirements.txt\ncdk-local runs the bundle as-is to match the deployed runtime.`);
21687
20923
  }
21688
20924
  }
@@ -23018,10 +22254,10 @@ async function resolveAgentCoreCodeImage(resolved, code, options, architecture,
23018
22254
  if (!asset) throw new CdkLocalError(`AgentCore Runtime '${resolved.logicalId}' code bundle (asset ${code.codeAssetHash}) was not found in the cdk.out asset manifest. ${getEmbedConfig().cliName} invoke-agentcore runs a local from-source build of a fromCodeAsset bundle — re-synthesize the app so the asset is staged in cdk.out and retry. (A fromS3 bundle is downloaded from S3 instead; this runtime has no literal Code.S3.Bucket.)`, "LOCAL_INVOKE_AGENTCORE_CODE_ASSET_NOT_FOUND");
23019
22255
  const sourceDir = loader.getAssetSourcePath(cdkOutDir, asset, {
23020
22256
  assetOutdir: assetPathDirs(resolved.stack).assetOutdir,
23021
- subject: `AgentCore Runtime '${flattenToOneLine(resolved.logicalId)}' code bundle`,
22257
+ subject: `AgentCore Runtime ${displayUntrustedValue(resolved.logicalId)} code bundle`,
23022
22258
  wrapError: (message) => new CdkLocalError(message, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_ESCAPES_ASSEMBLY")
23023
22259
  });
23024
- if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) throw new CdkLocalError(`AgentCore Runtime '${flattenToOneLine(resolved.logicalId)}' code bundle source '${flattenToOneLine(sourceDir)}' does not exist or is not a directory. Re-synthesize the app and retry.`, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_MISSING");
22260
+ if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) throw new CdkLocalError(`AgentCore Runtime ${displayUntrustedValue(resolved.logicalId)} code bundle source ${displayUntrustedValue(sourceDir)} does not exist or is not a directory. Re-synthesize the app and retry.`, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_MISSING");
23025
22261
  return buildAgentCoreCodeImage({
23026
22262
  sourceDir,
23027
22263
  runtime: code.runtime,
@@ -23786,7 +23022,7 @@ async function loadAgentCoreAssetContext(args) {
23786
23022
  if (!asset) return void 0;
23787
23023
  const sourceDir = assetLoader.getAssetSourcePath(watchManifestDir(newCandidate, cdkOutDir), asset, {
23788
23024
  assetOutdir: outputAssetBound(newCandidate, cdkOutDir),
23789
- subject: `AgentCore Runtime '${flattenToOneLine(resolved.logicalId)}' code bundle`,
23025
+ subject: `AgentCore Runtime ${displayUntrustedValue(resolved.logicalId)} code bundle`,
23790
23026
  wrapError: (message) => new CdkLocalError(message, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_ESCAPES_ASSEMBLY")
23791
23027
  });
23792
23028
  return {
@@ -23810,7 +23046,7 @@ async function loadAgentCoreAssetContext(args) {
23810
23046
  assetOutdir: outputAssetBound(newCandidate, cdkOutDir),
23811
23047
  absolute: "honour",
23812
23048
  field: "source.directory",
23813
- subject: `AgentCore Runtime '${flattenToOneLine(resolved.logicalId)}' image asset`,
23049
+ subject: `AgentCore Runtime ${displayUntrustedValue(resolved.logicalId)} image asset`,
23814
23050
  action: "copy from it",
23815
23051
  sink: "copy that directory into the running agent container on a soft reload",
23816
23052
  wrapError: (message) => new CdkLocalError(message, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_ESCAPES_ASSEMBLY")
@@ -24388,7 +23624,7 @@ async function resolveOne(entry, secretsClient, ssmClient) {
24388
23624
  switch (shape.kind) {
24389
23625
  case "secrets-manager": return resolveSecretsManager(entry, shape, secretsClient);
24390
23626
  case "ssm": return resolveSsm(entry, shape, ssmClient);
24391
- case "unknown": throw new EcsSecretsResolutionError(`Container '${entry.containerName}' secret '${entry.name}' references an unsupported ValueFrom shape '${arn}'. Expected Secrets Manager ARN (optionally with :<json-key>::) or SSM Parameter ARN.`);
23627
+ case "unknown": throw new EcsSecretsResolutionError(`Container ${displayUntrustedValue(entry.containerName)} secret ${displayUntrustedValue(entry.name)} references an unsupported ValueFrom shape ${displayUntrustedValue(arn)}. Expected Secrets Manager ARN (optionally with :<json-key>::) or SSM Parameter ARN.`);
24392
23628
  }
24393
23629
  }
24394
23630
  /**
@@ -24853,7 +24089,7 @@ async function prepareOneImage(task, container, options) {
24853
24089
  tag,
24854
24090
  assetOutdir: assetPathDirs(task.stack).assetOutdir,
24855
24091
  ...options.platformOverride !== void 0 && { platform: options.platformOverride },
24856
- wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${sanitizeServiceExceptionMessage(asset.source.directory ?? asset.source.executable?.join(" ") ?? "")}): ${stderr}`),
24092
+ wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${displayUntrustedValue(asset.source.directory ?? asset.source.executable?.[0] ?? "")}): ${stderr}`),
24857
24093
  progressLabel: `Building container image for '${container.name}'`
24858
24094
  });
24859
24095
  if (actualTag !== tag) try {
@@ -24880,7 +24116,7 @@ async function realizeDockerVolumes(volumes, state) {
24880
24116
  const out = /* @__PURE__ */ new Map();
24881
24117
  for (const v of volumes) {
24882
24118
  if (v.kind === "host") {
24883
- if (v.hostPath && !checkVolumeHostPath(v.hostPath)) logger.warn(`Volume '${v.name}': host path '${v.hostPath}' does not exist or is not a directory. Docker will create an anonymous bind mount; create the host path before run-task if you expected to bind-mount it.`);
24119
+ if (v.hostPath && !checkVolumeHostPath(v.hostPath)) logger.warn(`Volume ${displayUntrustedValue(v.name)}: host path ${displayUntrustedValue(v.hostPath)} does not exist or is not a directory. Docker will create an anonymous bind mount; create the host path before run-task if you expected to bind-mount it.`);
24884
24120
  out.set(v.name, v);
24885
24121
  continue;
24886
24122
  }
@@ -26278,7 +25514,7 @@ async function softReloadReplica(args) {
26278
25514
  const defaultDockerInspectWorkdirImpl = async (containerId) => {
26279
25515
  const { execFile } = await import("node:child_process");
26280
25516
  const { promisify } = await import("node:util");
26281
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25517
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26282
25518
  const { stdout } = await promisify(execFile)(getDockerCmd(), [
26283
25519
  "inspect",
26284
25520
  "--format",
@@ -26297,7 +25533,7 @@ let dockerInspectWorkdirImpl = defaultDockerInspectWorkdirImpl;
26297
25533
  const defaultDockerCpImpl = async (src, dst) => {
26298
25534
  const { execFile } = await import("node:child_process");
26299
25535
  const { promisify } = await import("node:util");
26300
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25536
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26301
25537
  await promisify(execFile)(getDockerCmd(), [
26302
25538
  "cp",
26303
25539
  src,
@@ -26312,7 +25548,7 @@ let dockerCpImpl = defaultDockerCpImpl;
26312
25548
  const defaultDockerRestartImpl = async (containerId) => {
26313
25549
  const { execFile } = await import("node:child_process");
26314
25550
  const { promisify } = await import("node:util");
26315
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25551
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26316
25552
  await promisify(execFile)(getDockerCmd(), ["restart", containerId]);
26317
25553
  };
26318
25554
  let dockerRestartImpl = defaultDockerRestartImpl;
@@ -26352,7 +25588,7 @@ async function disconnectOldFromSharedNetwork(oldInstance) {
26352
25588
  const defaultDockerNetworkDisconnectImpl = async (networkName, containerId) => {
26353
25589
  const { execFile } = await import("node:child_process");
26354
25590
  const { promisify } = await import("node:util");
26355
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25591
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26356
25592
  await promisify(execFile)(getDockerCmd(), [
26357
25593
  "network",
26358
25594
  "disconnect",
@@ -26529,7 +25765,7 @@ function pickEssentialContainerId(instance, service) {
26529
25765
  const defaultWaitForExitImpl = async (containerId) => {
26530
25766
  const { execFile } = await import("node:child_process");
26531
25767
  const { promisify } = await import("node:util");
26532
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25768
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26533
25769
  const { stdout } = await promisify(execFile)(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
26534
25770
  const code = parseInt(stdout.trim(), 10);
26535
25771
  return Number.isFinite(code) ? code : -1;
@@ -26549,7 +25785,7 @@ const EXIT_LOG_TAIL_LINES = 50;
26549
25785
  const defaultReadContainerLogsImpl = async (containerId) => {
26550
25786
  const { execFile } = await import("node:child_process");
26551
25787
  const { promisify } = await import("node:util");
26552
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25788
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26553
25789
  const { stdout, stderr } = await promisify(execFile)(getDockerCmd(), [
26554
25790
  "logs",
26555
25791
  "--tail",
@@ -29764,7 +29000,7 @@ async function loadAssetContextForTarget(args) {
29764
29000
  assetOutdir: outputAssetBound(candidate, cdkOutDir),
29765
29001
  absolute: "honour",
29766
29002
  field: "source.directory",
29767
- subject: `Docker image asset '${sanitizeServiceExceptionMessage(newAssetHash)}' of stack '${sanitizeServiceExceptionMessage(candidate.stackName)}'`,
29003
+ subject: `Docker image asset ${displayUntrustedValue(newAssetHash)} of stack ${displayUntrustedValue(candidate.stackName)}`,
29768
29004
  action: "copy from it",
29769
29005
  sink: "copy that directory into the running replicas on a soft reload",
29770
29006
  wrapError: (message) => new Error(message)
@@ -30293,11 +29529,11 @@ async function resolvePlaceholderAccount$1(arn, region, profile) {
30293
29529
  }));
30294
29530
  try {
30295
29531
  const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
30296
- if (!account) throw new LocalStartServiceError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'.`);
29532
+ if (!account) throw new LocalStartServiceError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN ${displayUntrustedValue(arn)}.`);
30297
29533
  return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
30298
29534
  } catch (err) {
30299
29535
  if (err instanceof LocalStartServiceError) throw err;
30300
- throw new LocalStartServiceError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN '${flattenToOneLine(arn)}': ${describeAwsFailureForWarn(err, "STS GetCallerIdentity (task-role placeholder)")}. Pass the ARN explicitly: --assume-task-role <arn>`);
29536
+ throw new LocalStartServiceError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN ${displayUntrustedValue(arn)}: ${describeAwsFailureForWarn(err, "STS GetCallerIdentity (task-role placeholder)")}. Pass the ARN explicitly: --assume-task-role <arn>`);
30301
29537
  } finally {
30302
29538
  sts.destroy();
30303
29539
  }
@@ -30870,13 +30106,13 @@ async function resolvePlaceholderAccount(arn, region, profile) {
30870
30106
  }));
30871
30107
  try {
30872
30108
  const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
30873
- if (!account) throw new PlaceholderAccountError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`);
30109
+ if (!account) throw new PlaceholderAccountError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN ${displayUntrustedValue(arn)}. Pass the ARN explicitly: --assume-task-role <arn>`);
30874
30110
  return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
30875
30111
  } catch (err) {
30876
30112
  if (err instanceof PlaceholderAccountError) throw err;
30877
- const shownArn = flattenToOneLine(arn);
30113
+ const shownArn = displayUntrustedValue(arn);
30878
30114
  const detail = describeAwsFailureForWarn(err, "STS GetCallerIdentity (task-role placeholder)");
30879
- throw new PlaceholderAccountError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN '${shownArn}': ${detail}. Pass the ARN explicitly: --assume-task-role <arn>`);
30115
+ throw new PlaceholderAccountError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN ${shownArn}: ${detail}. Pass the ARN explicitly: --assume-task-role <arn>`);
30880
30116
  } finally {
30881
30117
  sts.destroy();
30882
30118
  }
@@ -32744,7 +31980,7 @@ function resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId)
32744
31980
  assetOutdir,
32745
31981
  absolute: "honour-warn",
32746
31982
  field: "source.path",
32747
- subject: `BucketDeployment source asset for bucket '${sanitizeServiceExceptionMessage(bucketLogicalId)}'`,
31983
+ subject: `BucketDeployment source asset for bucket ${displayUntrustedValue(bucketLogicalId)}`,
32748
31984
  action: "serve it",
32749
31985
  sink: "serve every file under that directory as this bucket's local S3 origin",
32750
31986
  wrapError: (message) => new Error(message)
@@ -33307,7 +32543,7 @@ function realPathInsideRoot(root, file) {
33307
32543
  if (rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel)) return realFile;
33308
32544
  if (!warnedEscapes.has(file)) {
33309
32545
  warnedEscapes.add(file);
33310
- getLogger().warn(`Not serving '${flattenToOneLine(file)}': it is a symbolic link to '${flattenToOneLine(realFile)}', outside the origin directory '${flattenToOneLine(root)}' the asset manifest named. Answering as if the key did not exist.`);
32546
+ getLogger().warn(`Not serving ${displayUntrustedValue(file)}: it is a symbolic link to ${displayUntrustedValue(realFile)}, outside the origin directory ${displayUntrustedValue(root)} the asset manifest named. Answering as if the key did not exist.`);
33311
32547
  }
33312
32548
  return false;
33313
32549
  }
@@ -33346,7 +32582,7 @@ function isExistingFile(p) {
33346
32582
  function warnHiddenKey(dir, key, resolved) {
33347
32583
  if (warnedHiddenKeys.has(resolved)) return;
33348
32584
  warnedHiddenKeys.add(resolved);
33349
- getLogger().warn(`Not serving '${flattenToOneLine(key)}' from '${flattenToOneLine(dir)}': it is a hidden entry in a cdk synth --no-staging source folder (your own tree, not a staged asset). Answering as if the key did not exist.`);
32585
+ getLogger().warn(`Not serving ${displayUntrustedValue(key)} from ${displayUntrustedValue(dir)}: it is a hidden entry in a cdk synth --no-staging source folder (your own tree, not a staged asset). Answering as if the key did not exist.`);
33350
32586
  }
33351
32587
  /** Hidden keys already warned about. */
33352
32588
  const warnedHiddenKeys = /* @__PURE__ */ new Set();
@@ -33824,9 +33060,9 @@ function createS3OriginReader(bucketName, options = {}) {
33824
33060
  };
33825
33061
  if (direct.kind === "denied" && !deniedWarned) {
33826
33062
  deniedWarned = true;
33827
- getLogger().warn(`S3 denied reading '${flattenToOneLine(key)}' from bucket '${flattenToOneLine(bucketName)}'. If this is an OAC-locked / private bucket your credentials cannot read, point the origin at a local directory with --origin <originId>=<dir> (or use credentials with s3:GetObject on the bucket). ${getEmbedConfig().cliName} start-cloudfront reads the origin from real S3.`);
33063
+ getLogger().warn(`S3 denied reading ${displayUntrustedValue(key)} from bucket ${displayUntrustedValue(bucketName)}. If this is an OAC-locked / private bucket your credentials cannot read, point the origin at a local directory with --origin <originId>=<dir> (or use credentials with s3:GetObject on the bucket). ${getEmbedConfig().cliName} start-cloudfront reads the origin from real S3.`);
33828
33064
  }
33829
- if (direct.kind === "error") getLogger().warn(`S3 read of '${flattenToOneLine(key)}' from bucket '${flattenToOneLine(bucketName)}' failed: ${direct.message}`);
33065
+ if (direct.kind === "error") getLogger().warn(`S3 read of ${displayUntrustedValue(key)} from bucket ${displayUntrustedValue(bucketName)} failed: ${direct.message}`);
33830
33066
  }
33831
33067
  for (const candidate of resolveErrorResponseCandidates(input.customErrorResponses)) {
33832
33068
  const page = await fetchObject(candidate.errorKey);
@@ -33835,7 +33071,7 @@ function createS3OriginReader(bucketName, options = {}) {
33835
33071
  headers: { "content-type": contentTypeForKey(candidate.errorKey) },
33836
33072
  body: page.body
33837
33073
  };
33838
- if (page.kind === "denied" || page.kind === "error") getLogger().warn(`S3 could not read custom-error page '${flattenToOneLine(candidate.errorKey)}' from bucket '${flattenToOneLine(bucketName)}' (${page.kind}); falling through.`);
33074
+ if (page.kind === "denied" || page.kind === "error") getLogger().warn(`S3 could not read custom-error page ${displayUntrustedValue(candidate.errorKey)} from bucket ${displayUntrustedValue(bucketName)} (${page.kind}); falling through.`);
33839
33075
  }
33840
33076
  return {
33841
33077
  statusCode: 404,
@@ -41683,5 +40919,5 @@ function addStudioSpecificOptions(cmd) {
41683
40919
  }
41684
40920
 
41685
40921
  //#endregion
41686
- export { applyEdgeResponseResult as $, buildJwksUrlFromIssuer as $n, resolveCfnStackName as $r, buildCloudMapIndex as $t, startAgentCoreHttpServer as A, describeCredentialLoadFailure as Ai, classifySourceChange as An, ConnectionRegistry as Ar, addRunTaskSpecificOptions as At, idFromArn as B, buildStageMap as Bn, resolveRuntimeFileExtension as Br, resolveEcsAssumeRoleOption as Bt, addListSpecificOptions as C, resolveAgentCoreTarget as Ci, waitForAgentCorePing as Cn, tryParseStatus as Cr, parseLbPortOverrides as Ct, createLocalStartAgentCoreCommand as D, tryResolveImageFnJoin as Di, computeCodeImageTag as Dn, probeHostGatewaySupport as Dr, addStartServiceSpecificOptions as Dt, addStartAgentCoreSpecificOptions as E, substituteImagePlaceholders as Ei, buildAgentCoreCodeImage as En, HOST_GATEWAY_MIN_VERSION as Er, resolveAlbFrontDoor as Et, createLocalStartCloudFrontCommand as F, createWatchPredicates as Fn, buildDisconnectEvent as Fr, addImageOverrideOptions as Ft, classifyS3Error as G, filterRoutesByApiIdentifiers as Gn, substituteEnvVarsFromState as Gr, enforceImageOverrideOrphans as Gt, createDeployedKvsDataSource as H, resolveEnvVars$1 as Hn, EcsTaskResolutionError as Hr, runEcsServiceEmulator as Ht, normalizeKvsFileKeys as I, resolveApiTargetSubset as In, buildMessageEvent as Ir, buildEcsImageResolutionContext$1 as It, startCloudFrontServer as J, startApiServer as Jn, createLocalStateProvider as Jr, resolveImageOverrides as Jt, createS3OriginReader as K, groupRoutesByServer as Kn, substituteEnvVarsFromStateAsync as Kr, mergeForService as Kt, parseKvsFileOverrides as L, createAuthorizerCache as Ln, architectureToPlatform as Lr, ecsClusterOption as Lt, startAgentCoreWsBridge as M, resolveProfileCredentials as Mi, createLocalInvokeCommand as Mn, handleConnectionsRequest as Mr, MAX_TASKS_SUBNET_RANGE_CAP as Mt, LocalStartCloudFrontError as N, buildProxyClientConfig as Ni, addStartApiSpecificOptions as Nn, parseConnectionsPath as Nr, addCommonEcsServiceOptions as Nt, buildAgentCoreServeAuthCheck as O, LocalInvokeBuildError as Oi, renderCodeDockerfile as On, resolveHostGatewayExtraHosts as Or, createLocalStartServiceCommand as Ot, addStartCloudFrontSpecificOptions as P, isProxyEnvConfigured as Pi, createLocalStartApiCommand as Pn, buildConnectEvent as Pr, addEcsAssumeRoleOptions as Pt, applyEdgeRequestResult as Q, buildCognitoJwksUrl as Qn, resolveCfnRegion as Qr, listPinnedTargets as Qt, parseOriginOverrides as R, createFileWatcher as Rn, buildContainerImage as Rr, parseMaxTasks as Rt, StudioEventBus as S, pickAgentCoreCandidateStack as Si, waitForAgentCoreHttpReady as Sn, selectIntegrationResponse as Sr, createLocalStartAlbCommand as St, formatTargetListing as T, formatStateRemedy as Ti, SUPPORTED_CODE_RUNTIMES as Tn, HOST_DOCKER_INTERNAL_GATEWAY as Tr, isApplicationLoadBalancer as Tt, resolveDeployedKvsArnByName as U, availableApiIdentifiers as Un, substituteAgainstState as Ur, ImageOverrideError as Ut, resolveKvsModulesForDistribution as V, materializeLayerFromArn as Vn, resolveRuntimeImage as Vr, resolveSharedSidecarCredentials as Vt, resolveDeployedOriginBucket as W, filterRoutesByApiIdentifier as Wn, substituteAgainstStateAsync as Wr, buildImageOverrideTag as Wt, serveFromStaticOrigin as X, resolveServiceIntegrationParameters as Xn, rejectExplicitCfnStackWithMultipleStacks as Xr, describePinnedImageUri as Xt, resolveErrorResponseCandidates as Y, resolveSelectionExpression as Yn, isCfnFlagPresent as Yr, runImageOverrideBuilds as Yt, serveLambdaUrlOrigin as Z, defaultCredentialsLoader as Zn, resolveCfnFallbackRegion as Zr, isLocalCdkAssetImage as Zt, filterStudioTargetGroups as _, AGENTCORE_AGUI_PROTOCOL as _i, parseSseForJsonRpc as _n, applyAuthorizerOverlay as _r, createCloudFrontModule as _t, createLocalStudioCommand as a, countTargets as ai, attachContainerLogStreamer as an, computeRequestIdentityHash as ar, describeS3OriginDomain as at, renderStudioHtml as b, AGENTCORE_RUNTIME_TYPE as bi, AGENTCORE_SESSION_ID_HEADER as bn, evaluateResponseParameters as br, addAlbSpecificOptions as bt, startStudioProxy as c, discoverWebSocketApis as ci, bridgeAgentCoreWs as cn, invokeTokenAuthorizer as cr, pickFunctionUrlLogicalIdFromOrigin as ct, createStudioDispatcher as d, parseSelectionExpressionPath as di, A2A_PATH as dn, buildCorsConfigByApiId as dr, pickTargetFunctionLogicalId as dt, CfnLocalStateProvider as ei, CloudMapRegistry as en, createJwksCache as er, buildEdgeRequestEvent as et, filterStudioCustomResources as f, webSocketApiMatchesIdentifier as fi, a2aInvokeOnce as fn, buildCorsConfigFromCloudFrontChain as fr, resolveCloudFrontDistribution as ft, annotatePinnedEcsTargets as g, AGENTCORE_A2A_PROTOCOL as gi, mcpInvokeOnce as gn, translateLambdaResponse as gr, stripCloudFrontImport as gt, annotateEcsTaskPinnedTargets as h, resolveLambdaArnIntrinsic as hi, MCP_PROTOCOL_VERSION as hn, matchRoute as hr, runViewerResponse as ht, coerceStopRequest as i, resolveSingleTarget as ii, getContainerNetworkIp as in, buildMethodArn as ir, CLOUDFRONT_DISTRIBUTION_TYPE as it, attachAgentCoreWsBridge as j, buildStsClientConfig as ji, addInvokeSpecificOptions as jn, buildMgmtEndpointEnvUrl as jr, createLocalRunTaskCommand as jt, selectServeInboundAuth as k, describeAwsFailureForWarn as ki, toCmdArgv as kn, bufferToBody as kr, serviceStrategy as kt, relayServeRequest as l, discoverWebSocketApisOrThrow as li, invokeAgentCoreWs as ln, attachAuthorizers as lr, pickKvsLogicalIdFromArn as lt, annotateAlbPinnedBackingServices as m, pickRefLogicalId as mi, MCP_PATH as mn, matchPreflight as mr, runViewerRequest as mt, coerceRunRequest as n, resolveSsmParameters as ni, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as nn, verifyJwtAuthorizer as nr, edgeHeadersToHttp as nt, resolveServeBaseUrl as o, listTargets as oi, addInvokeAgentCoreSpecificOptions as on, evaluateCachedLambdaPolicy as or, extractKvsAssociations as ot, isCustomResourceLambdaTarget as p, discoverRoutes as pi, MCP_CONTAINER_PORT as pn, isFunctionUrlOacFronted as pr, compileCloudFrontFunction as pt, matchBehavior as q, readMtlsMaterialsFromDisk as qn, LocalStateSourceError as qr, parseImageOverrideFlags as qt, coerceServeRequest as r, resolveWatchConfig as ri, setShadowReadyTimeoutMs as rn, verifyJwtViaDiscovery as rr, httpHeadersToEdge as rt, createStudioServeManager as s, availableWebSocketApiIdentifiers as si, createLocalInvokeAgentCoreCommand as sn, invokeRequestAuthorizer as sr, isCloudFrontDistribution as st, addStudioSpecificOptions as t, collectSsmParameterRefs as ti, DEFAULT_SHADOW_READY_TIMEOUT_MS as tn, verifyCognitoJwt as tr, buildEdgeResponseEvent as tt, reinvoke as u, filterWebSocketApisByIdentifiers as ui, A2A_CONTAINER_PORT as un, applyCorsResponseHeaders as ur, pickLambdaEdgeFunctionLogicalId as ut, startStudioServer as v, AGENTCORE_HTTP_PROTOCOL as vi, AGENTCORE_SIGV4_SERVICE as vn, buildHttpApiV2Event as vr, createLocalFileKvsDataSource as vt, createLocalListCommand as w, derivePseudoParametersFromRegion as wi, downloadAndExtractS3Bundle as wn, VtlEvaluationError as wr, resolveAlbTarget as wt, createStudioStore as x, AgentCoreResolutionError as xi, invokeAgentCore as xn, pickResponseTemplate as xr, albStrategy as xt, toStudioTargetGroups as y, AGENTCORE_MCP_PROTOCOL as yi, signAgentCoreInvocation as yn, buildRestV1Event as yr, createUnboundCloudFrontModule as yt, resolveCloudFrontTarget as z, attachStageContext as zn, resolveRuntimeCodeMountPath as zr, parseRestartPolicy as zt };
41687
- //# sourceMappingURL=local-studio-BbLfdSy4.js.map
40922
+ export { applyEdgeResponseResult as $, buildJwksUrlFromIssuer as $n, resolveCfnStackName as $r, buildCloudMapIndex as $t, startAgentCoreHttpServer as A, resolveProfileCredentials as Ai, classifySourceChange as An, ConnectionRegistry as Ar, addRunTaskSpecificOptions as At, idFromArn as B, buildStageMap as Bn, resolveRuntimeFileExtension as Br, resolveEcsAssumeRoleOption as Bt, addListSpecificOptions as C, resolveAgentCoreTarget as Ci, waitForAgentCorePing as Cn, tryParseStatus as Cr, parseLbPortOverrides as Ct, createLocalStartAgentCoreCommand as D, tryResolveImageFnJoin as Di, computeCodeImageTag as Dn, probeHostGatewaySupport as Dr, addStartServiceSpecificOptions as Dt, addStartAgentCoreSpecificOptions as E, substituteImagePlaceholders as Ei, buildAgentCoreCodeImage as En, HOST_GATEWAY_MIN_VERSION as Er, resolveAlbFrontDoor as Et, createLocalStartCloudFrontCommand as F, createWatchPredicates as Fn, buildDisconnectEvent as Fr, addImageOverrideOptions as Ft, classifyS3Error as G, filterRoutesByApiIdentifiers as Gn, substituteEnvVarsFromState as Gr, enforceImageOverrideOrphans as Gt, createDeployedKvsDataSource as H, resolveEnvVars$1 as Hn, EcsTaskResolutionError as Hr, runEcsServiceEmulator as Ht, normalizeKvsFileKeys as I, resolveApiTargetSubset as In, buildMessageEvent as Ir, buildEcsImageResolutionContext$1 as It, startCloudFrontServer as J, startApiServer as Jn, createLocalStateProvider as Jr, resolveImageOverrides as Jt, createS3OriginReader as K, groupRoutesByServer as Kn, substituteEnvVarsFromStateAsync as Kr, mergeForService as Kt, parseKvsFileOverrides as L, createAuthorizerCache as Ln, architectureToPlatform as Lr, ecsClusterOption as Lt, startAgentCoreWsBridge as M, isProxyEnvConfigured as Mi, createLocalInvokeCommand as Mn, handleConnectionsRequest as Mr, MAX_TASKS_SUBNET_RANGE_CAP as Mt, LocalStartCloudFrontError as N, addStartApiSpecificOptions as Nn, parseConnectionsPath as Nr, addCommonEcsServiceOptions as Nt, buildAgentCoreServeAuthCheck as O, LocalInvokeBuildError as Oi, renderCodeDockerfile as On, resolveHostGatewayExtraHosts as Or, createLocalStartServiceCommand as Ot, addStartCloudFrontSpecificOptions as P, createLocalStartApiCommand as Pn, buildConnectEvent as Pr, addEcsAssumeRoleOptions as Pt, applyEdgeRequestResult as Q, buildCognitoJwksUrl as Qn, resolveCfnRegion as Qr, listPinnedTargets as Qt, parseOriginOverrides as R, createFileWatcher as Rn, buildContainerImage as Rr, parseMaxTasks as Rt, StudioEventBus as S, pickAgentCoreCandidateStack as Si, waitForAgentCoreHttpReady as Sn, selectIntegrationResponse as Sr, createLocalStartAlbCommand as St, formatTargetListing as T, formatStateRemedy as Ti, SUPPORTED_CODE_RUNTIMES as Tn, HOST_DOCKER_INTERNAL_GATEWAY as Tr, isApplicationLoadBalancer as Tt, resolveDeployedKvsArnByName as U, availableApiIdentifiers as Un, substituteAgainstState as Ur, ImageOverrideError as Ut, resolveKvsModulesForDistribution as V, materializeLayerFromArn as Vn, resolveRuntimeImage as Vr, resolveSharedSidecarCredentials as Vt, resolveDeployedOriginBucket as W, filterRoutesByApiIdentifier as Wn, substituteAgainstStateAsync as Wr, buildImageOverrideTag as Wt, serveFromStaticOrigin as X, resolveServiceIntegrationParameters as Xn, rejectExplicitCfnStackWithMultipleStacks as Xr, describePinnedImageUri as Xt, resolveErrorResponseCandidates as Y, resolveSelectionExpression as Yn, isCfnFlagPresent as Yr, runImageOverrideBuilds as Yt, serveLambdaUrlOrigin as Z, defaultCredentialsLoader as Zn, resolveCfnFallbackRegion as Zr, isLocalCdkAssetImage as Zt, filterStudioTargetGroups as _, AGENTCORE_AGUI_PROTOCOL as _i, parseSseForJsonRpc as _n, applyAuthorizerOverlay as _r, createCloudFrontModule as _t, createLocalStudioCommand as a, countTargets as ai, attachContainerLogStreamer as an, computeRequestIdentityHash as ar, describeS3OriginDomain as at, renderStudioHtml as b, AGENTCORE_RUNTIME_TYPE as bi, AGENTCORE_SESSION_ID_HEADER as bn, evaluateResponseParameters as br, addAlbSpecificOptions as bt, startStudioProxy as c, discoverWebSocketApis as ci, bridgeAgentCoreWs as cn, invokeTokenAuthorizer as cr, pickFunctionUrlLogicalIdFromOrigin as ct, createStudioDispatcher as d, parseSelectionExpressionPath as di, A2A_PATH as dn, buildCorsConfigByApiId as dr, pickTargetFunctionLogicalId as dt, CfnLocalStateProvider as ei, CloudMapRegistry as en, createJwksCache as er, buildEdgeRequestEvent as et, filterStudioCustomResources as f, webSocketApiMatchesIdentifier as fi, a2aInvokeOnce as fn, buildCorsConfigFromCloudFrontChain as fr, resolveCloudFrontDistribution as ft, annotatePinnedEcsTargets as g, AGENTCORE_A2A_PROTOCOL as gi, mcpInvokeOnce as gn, translateLambdaResponse as gr, stripCloudFrontImport as gt, annotateEcsTaskPinnedTargets as h, resolveLambdaArnIntrinsic as hi, MCP_PROTOCOL_VERSION as hn, matchRoute as hr, runViewerResponse as ht, coerceStopRequest as i, resolveSingleTarget as ii, getContainerNetworkIp as in, buildMethodArn as ir, CLOUDFRONT_DISTRIBUTION_TYPE as it, attachAgentCoreWsBridge as j, buildProxyClientConfig as ji, addInvokeSpecificOptions as jn, buildMgmtEndpointEnvUrl as jr, createLocalRunTaskCommand as jt, selectServeInboundAuth as k, buildStsClientConfig as ki, toCmdArgv as kn, bufferToBody as kr, serviceStrategy as kt, relayServeRequest as l, discoverWebSocketApisOrThrow as li, invokeAgentCoreWs as ln, attachAuthorizers as lr, pickKvsLogicalIdFromArn as lt, annotateAlbPinnedBackingServices as m, pickRefLogicalId as mi, MCP_PATH as mn, matchPreflight as mr, runViewerRequest as mt, coerceRunRequest as n, resolveSsmParameters as ni, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as nn, verifyJwtAuthorizer as nr, edgeHeadersToHttp as nt, resolveServeBaseUrl as o, listTargets as oi, addInvokeAgentCoreSpecificOptions as on, evaluateCachedLambdaPolicy as or, extractKvsAssociations as ot, isCustomResourceLambdaTarget as p, discoverRoutes as pi, MCP_CONTAINER_PORT as pn, isFunctionUrlOacFronted as pr, compileCloudFrontFunction as pt, matchBehavior as q, readMtlsMaterialsFromDisk as qn, LocalStateSourceError as qr, parseImageOverrideFlags as qt, coerceServeRequest as r, resolveWatchConfig as ri, setShadowReadyTimeoutMs as rn, verifyJwtViaDiscovery as rr, httpHeadersToEdge as rt, createStudioServeManager as s, availableWebSocketApiIdentifiers as si, createLocalInvokeAgentCoreCommand as sn, invokeRequestAuthorizer as sr, isCloudFrontDistribution as st, addStudioSpecificOptions as t, collectSsmParameterRefs as ti, DEFAULT_SHADOW_READY_TIMEOUT_MS as tn, verifyCognitoJwt as tr, buildEdgeResponseEvent as tt, reinvoke as u, filterWebSocketApisByIdentifiers as ui, A2A_CONTAINER_PORT as un, applyCorsResponseHeaders as ur, pickLambdaEdgeFunctionLogicalId as ut, startStudioServer as v, AGENTCORE_HTTP_PROTOCOL as vi, AGENTCORE_SIGV4_SERVICE as vn, buildHttpApiV2Event as vr, createLocalFileKvsDataSource as vt, createLocalListCommand as w, derivePseudoParametersFromRegion as wi, downloadAndExtractS3Bundle as wn, VtlEvaluationError as wr, resolveAlbTarget as wt, createStudioStore as x, AgentCoreResolutionError as xi, invokeAgentCore as xn, pickResponseTemplate as xr, albStrategy as xt, toStudioTargetGroups as y, AGENTCORE_MCP_PROTOCOL as yi, signAgentCoreInvocation as yn, buildRestV1Event as yr, createUnboundCloudFrontModule as yt, resolveCloudFrontTarget as z, attachStageContext as zn, resolveRuntimeCodeMountPath as zr, parseRestartPolicy as zt };
40923
+ //# sourceMappingURL=local-studio-CV21zLBM.js.map