cdk-local 0.149.4 → 0.149.6

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
  /**
@@ -1699,393 +1225,6 @@ function effectiveAssumeRoleArn(logicalId, opt) {
1699
1225
  return opt.perLambda?.[logicalId] ?? opt.globalArn;
1700
1226
  }
1701
1227
 
1702
- //#endregion
1703
- //#region src/utils/assembly-path.ts
1704
- /**
1705
- * `true` when `candidate` names something strictly beneath `base`, both given
1706
- * as already-resolved absolute paths.
1707
- *
1708
- * The `..` test is SEPARATOR-AWARE rather than a bare `startsWith('..')`,
1709
- * which would also reject a legitimate sibling named `..foo`. The empty-string
1710
- * case is `base` itself; callers decide whether that counts as an escape,
1711
- * because an asset path legitimately names a DIRECTORY while a template path
1712
- * does not.
1713
- */
1714
- function isInside(base, candidate) {
1715
- const rel = relative(base, candidate);
1716
- if (rel === "" || rel === "..") return false;
1717
- if (rel.startsWith(`..${sep}`)) return false;
1718
- return !isAbsolute(rel);
1719
- }
1720
- /**
1721
- * `realpath(3)`, or `undefined` for a path that does not fully resolve.
1722
- *
1723
- * `.native` is load-bearing and must not be simplified to `fs.realpathSync`.
1724
- * The plain form is a JavaScript walker that folds `..` LEXICALLY, so it
1725
- * disagrees with the kernel on a target carrying a `..` after a symlinked
1726
- * component — answering `ENOENT` for a path the kernel resolves, and on a
1727
- * case-insensitive filesystem (APFS by default) it can spin on the same shape.
1728
- * `.native` is libuv's `realpath(3)` and throws the same
1729
- * `ENOENT` / `ELOOP` / `EACCES`.
1730
- */
1731
- function tryRealpath(p) {
1732
- try {
1733
- return realpathSync.native(p);
1734
- } catch {
1735
- return;
1736
- }
1737
- }
1738
- /** `fs.readlinkSync`, or `undefined` when `p` is not a symbolic link. */
1739
- function tryReadlink(p) {
1740
- try {
1741
- return readlinkSync(p);
1742
- } catch {
1743
- return;
1744
- }
1745
- }
1746
- /**
1747
- * Link follows before the walk gives up and reports the path as unresolvable.
1748
- * It does NOT refuse: an exhausted budget answers `undefined`, which leaves
1749
- * the symlink arm silent and the lexical verdict standing. Safe because the OS
1750
- * gives up first (macOS caps at 32, `ELOOP`), so a chain that reaches this cap
1751
- * is one nothing can open anyway.
1752
- */
1753
- const MAX_LINK_HOPS = 40;
1754
- /**
1755
- * Unresolvable path COMPONENTS the climb walks before giving up. Bounds the
1756
- * recursion below, which is one frame per component, so a pathological value
1757
- * cannot raise a `RangeError` from inside {@link tryRealpath}'s own `try` (its
1758
- * `catch` would swallow the crash into a silent `undefined`).
1759
- *
1760
- * KNOW THAT EXHAUSTING THIS FAILS OPEN, and why that is accepted rather than
1761
- * unnoticed. Unlike {@link MAX_LINK_HOPS}, which has the OS's own `ELOOP` cap
1762
- * behind it, nothing else stops a path of 1 001 absent components: the climb
1763
- * gives up, the symlink arm goes silent, and a value that would land outside
1764
- * reads as CONTAINED on the lexical verdict alone. It is not reachable in
1765
- * effect — such a path cannot exist, so `cdkl invoke`'s `existsSync` refuses it
1766
- * and `start-api`'s `docker run` fails to mount it — but that safety lives in
1767
- * the CONSUMERS, not here. A consumer that neither checks existence nor mounts
1768
- * would need its own answer.
1769
- */
1770
- const MAX_PATH_COMPONENTS = 1e3;
1771
- /**
1772
- * Where `target` REALLY points, for a path that may not exist yet.
1773
- *
1774
- * `realpathSync` answers only for a path that fully resolves, and it throws
1775
- * `ENOENT` for a DANGLING symbolic link exactly as it does for an absent file.
1776
- * `cdkl start-api` resolves an asset path without an existence check, so an
1777
- * absent or dangling component must not silence the symlink arm.
1778
- *
1779
- * Each unresolvable component is therefore handled by hand: climb to the
1780
- * deepest ancestor that DOES resolve, then re-apply the remaining components,
1781
- * following any symbolic link with `readlink` and re-resolving its target.
1782
- * `undefined` means the walk could not resolve the path at all.
1783
- *
1784
- * For a path that FULLY RESOLVES the answer is the kernel's and is exact. For
1785
- * one that does not exist yet this is a best-effort MODEL of kernel
1786
- * resolution; the known edge is a `..` INSIDE an unresolvable link's target,
1787
- * folded lexically here while the kernel folds it only after following each
1788
- * preceding component. That edge is benign for THESE callers rather than in
1789
- * general: it needs the target to be absent, and both consumers resolve the
1790
- * path and then open or mount it, so the divergence is between this model and
1791
- * a path nothing can reach. A caller that CREATED a file through such a path
1792
- * would need an `lstat` of its own.
1793
- *
1794
- * `EACCES` is folded into "does not resolve" along with `ENOENT`, so a link
1795
- * under a directory this process cannot traverse reads as contained. Same
1796
- * bound: the consumer's own `existsSync` / mount runs as the same user and
1797
- * fails identically.
1798
- */
1799
- function resolveThroughLinks(target, hops = 0, climbs = 0) {
1800
- const direct = tryRealpath(target);
1801
- if (direct !== void 0) return direct;
1802
- if (hops >= MAX_LINK_HOPS) return void 0;
1803
- if (climbs >= MAX_PATH_COMPONENTS) return void 0;
1804
- const parent = dirname(target);
1805
- if (parent === target) return void 0;
1806
- const realParent = resolveThroughLinks(parent, hops, climbs + 1);
1807
- if (realParent === void 0) return void 0;
1808
- const link = tryReadlink(target);
1809
- if (link === void 0) return join(realParent, basename(target));
1810
- return resolveThroughLinks(resolve(realParent, link), hops + 1, climbs);
1811
- }
1812
- /**
1813
- * Resolve an assembly-supplied `candidate` against `dir` and report whether
1814
- * the result stays inside it.
1815
- *
1816
- * The lexical arm joins exactly the way the call sites used to (`path.join`,
1817
- * NOT `path.resolve`), so the verdict is about the path the caller will
1818
- * actually use. `join`'s handling of an absolute candidate makes this arm
1819
- * strictly more permissive than a `resolve`-based one would be; an absolute
1820
- * value is therefore answered by {@link absoluteAssemblyPathEscape} instead,
1821
- * because this function structurally cannot.
1822
- *
1823
- * The SYMLINK arm exists because the lexical arm alone leaves an equivalent
1824
- * hole: `cdk.out/link -> /etc` plus a candidate of `link/passwd` is lexically
1825
- * contained and still reaches `/etc/passwd`. Both sides go through
1826
- * {@link resolveThroughLinks}, so an assembly directory REACHED through a link
1827
- * (macOS spells `/tmp` as `/private/tmp`) is unaffected.
1828
- *
1829
- * The verdict is about the assembly AS IT SITS ON DISK. It is NOT a defence
1830
- * against a process rewriting that directory concurrently — the caller uses
1831
- * the path after this returns.
1832
- */
1833
- function resolveAssemblyPath(dir, candidate, options) {
1834
- const base = resolve(dir);
1835
- const bound = options?.containWithin === void 0 ? base : resolve(options.containWithin);
1836
- const joined = resolve(join(base, candidate));
1837
- if (!isInside(bound, joined)) return {
1838
- contained: false,
1839
- escape: "lexical",
1840
- path: joined
1841
- };
1842
- const realBound = resolveThroughLinks(bound);
1843
- if (realBound !== void 0) {
1844
- const realTarget = resolveThroughLinks(joined);
1845
- if (realTarget !== void 0 && !isInside(realBound, realTarget)) return {
1846
- contained: false,
1847
- escape: "symlink",
1848
- path: joined,
1849
- realPath: realTarget
1850
- };
1851
- }
1852
- return {
1853
- contained: true,
1854
- path: joined
1855
- };
1856
- }
1857
- /**
1858
- * Whether an ALREADY-ABSOLUTE assembly-supplied path lies outside `bound`.
1859
- *
1860
- * {@link resolveAssemblyPath} cannot answer this, and the reason is structural
1861
- * rather than an oversight: its lexical arm joins with `path.join`, which does
1862
- * NOT honour a leading separator, so an absolute candidate is folded INTO the
1863
- * directory and the verdict would describe a path no caller opens. A site that
1864
- * HONOURS an absolute value needs the verdict about the value itself.
1865
- *
1866
- * The callers are `cdkl invoke`'s and `cdkl start-api`'s
1867
- * `Metadata['aws:asset:path']` resolution. Those honour an absolute path
1868
- * because `cdk synth --no-staging` emits one — CDK writes the asset's absolute
1869
- * SOURCE directory under `aws:cdk:disable-asset-staging`, usually outside the
1870
- * outdir — and they WARN rather than refuse when it leaves the bound, so this
1871
- * returns a verdict rather than throwing. The asset-MANIFEST readers that
1872
- * honour an absolute value take the same verdict: `'honour-warn'` (the
1873
- * start-cloudfront origin) warns when the value is a folder inside the user's
1874
- * project not under a credential or version-control directory, and refuses
1875
- * otherwise, `'honour'` (the soft-reload
1876
- * sources) refuses (#745).
1877
- *
1878
- * It lives HERE so the containment rule has one spelling: it reuses this
1879
- * module's own {@link isInside} and {@link resolveThroughLinks}, symlink arm
1880
- * included, rather than letting a caller re-spell `path.relative` and drift.
1881
- *
1882
- * `bound` itself is NOT an escape, unlike in {@link resolveAssemblyPath},
1883
- * where an empty `path.relative` means "names the directory rather than a file
1884
- * inside it". An asset path legitimately names a DIRECTORY, so a value equal
1885
- * to the bound is inside it and reporting it as outside would be false.
1886
- *
1887
- * THE REAL PATHS DECIDE IN BOTH DIRECTIONS, which is the one place this
1888
- * deliberately does more than {@link resolveAssemblyPath}'s lexical-first
1889
- * ordering. The lexical verdict here is about a SPELLING, and two spellings of
1890
- * the same directory are common rather than exotic: macOS resolves `/tmp` to
1891
- * `/private/tmp` and `/var` to `/private/var`, and a user may symlink `cdk.out`
1892
- * itself. With an outdir of `/tmp/cdk.out` and a `--no-staging` value of
1893
- * `/private/tmp/cdk.out/src`, a lexical-only verdict cries "pointing outside
1894
- * the assembly ... treat this assembly as untrusted" about an asset that is
1895
- * plainly inside it — and this arm exists to make an UNEXPECTED path visible,
1896
- * so a false alarm is the failure that costs it its meaning.
1897
- *
1898
- * Exonerating requires the kernel to answer for BOTH operands: an unresolvable
1899
- * one leaves the lexical verdict standing, so the arm stays loud when it cannot
1900
- * see. (`resolveAssemblyPath` cannot take the same shape: its verdict is about
1901
- * a path built with `path.join`, and letting a real path overrule the lexical
1902
- * `..` there would answer about a location the caller never opens.)
1903
- */
1904
- function absoluteAssemblyPathEscape(bound, absolutePath) {
1905
- const resolvedBound = resolve(bound);
1906
- const target = resolve(absolutePath);
1907
- const lexicallyOutside = !isInside(resolvedBound, target) && target !== resolvedBound;
1908
- const realBound = resolveThroughLinks(resolvedBound);
1909
- const realTarget = resolveThroughLinks(target);
1910
- const reallyOutside = realBound === void 0 || realTarget === void 0 ? void 0 : !isInside(realBound, realTarget) && realTarget !== realBound;
1911
- if (lexicallyOutside) {
1912
- if (reallyOutside === false) return void 0;
1913
- return {
1914
- contained: false,
1915
- escape: "lexical",
1916
- path: target
1917
- };
1918
- }
1919
- if (reallyOutside === true) return {
1920
- contained: false,
1921
- escape: "symlink",
1922
- path: target,
1923
- realPath: realTarget
1924
- };
1925
- }
1926
- /**
1927
- * Whether `candidate` names the SAME directory as `bound` — lexically, or
1928
- * through a symbolic link on either side.
1929
- *
1930
- * An asset source legitimately names a DIRECTORY, so a value that resolves
1931
- * onto the output directory itself is not an escape; but it hands the WHOLE
1932
- * assembly to the sink, which is worth a line. A caller that re-spells this as
1933
- * `resolve(a) === resolve(b)` misses every second spelling of the bound
1934
- * (macOS `/tmp` vs `/private/tmp`, a symlinked `cdk.out`), and
1935
- * {@link absoluteAssemblyPathEscape} exonerates exactly those spellings as
1936
- * inside — so the equality beside it would stay silent for them.
1937
- *
1938
- * Conservative on failure: an unresolvable side answers from the lexical
1939
- * comparison alone, so it can only say "not the same", never wrongly claim
1940
- * identity.
1941
- */
1942
- function namesTheSameDirectory(bound, candidate) {
1943
- const resolvedBound = resolve(bound);
1944
- const target = resolve(candidate);
1945
- if (target === resolvedBound) return true;
1946
- const realBound = resolveThroughLinks(resolvedBound);
1947
- const realTarget = resolveThroughLinks(target);
1948
- return realBound !== void 0 && realTarget !== void 0 && realBound === realTarget;
1949
- }
1950
- /**
1951
- * The escape verdict for an assembly-supplied path WITHOUT throwing, taking
1952
- * whichever arm the value's own shape calls for: {@link absoluteAssemblyPathEscape}
1953
- * for an absolute value, {@link resolveAssemblyPath} for a relative one.
1954
- *
1955
- * For a caller that only WARNS and has many values to judge — the BuildKit
1956
- * passthroughs in `src/assets/buildkit-passthrough-warnings.ts`. `base` is what
1957
- * a relative value resolves against, `bound` what it must stay inside.
1958
- * Returns `undefined` when the value is fine, including when it names `bound`
1959
- * itself ({@link namesTheSameDirectory}).
1960
- */
1961
- function assemblyPathEscape(base, bound, candidate) {
1962
- if (isAbsolute(candidate)) return absoluteAssemblyPathEscape(bound, candidate);
1963
- const resolved = resolveAssemblyPath(base, candidate, { containWithin: bound });
1964
- if (resolved.contained || namesTheSameDirectory(bound, resolved.path)) return void 0;
1965
- return resolved;
1966
- }
1967
- /**
1968
- * The shared tail of a containment refusal: what the value resolved to, what
1969
- * it escaped, and why that means the assembly is not CDK-generated. The call
1970
- * site supplies its own subject ("Lambda 'X' has ... which ") and its own
1971
- * error class. `action` completes "Refusing to ..." and is REQUIRED rather
1972
- * than defaulted: a default is a branch no caller takes, so it can neither be
1973
- * fenced nor be right for the next caller.
1974
- *
1975
- * `provenanceOverride` replaces that whole closing sentence for a caller that
1976
- * does NOT refuse — the BuildKit passthrough warnings, where "Refusing to ..."
1977
- * would be a false statement about a value that is forwarded anyway.
1978
- */
1979
- function renderAssemblyPathEscape(escape, dir, action, provenanceOverride) {
1980
- 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}.`;
1981
- const base = resolve(dir);
1982
- const realBase = resolveThroughLinks(base) ?? base;
1983
- const shownPath = displayUntrustedValue(escape.path);
1984
- const shownBase = displayUntrustedValue(base);
1985
- if (escape.escape === "symlink") {
1986
- if (escape.realPath === realBase) return `resolves to ${shownPath}, a symbolic link to the directory ${shownBase} itself rather than to a path inside it. ${provenance}`;
1987
- return `resolves to ${shownPath}, which leads through a symbolic link to ${displayUntrustedValue(escape.realPath)}, outside ${shownBase}. ${provenance}`;
1988
- }
1989
- if (escape.path === base) return `names the directory ${shownBase} itself rather than a path inside it. ${provenance}`;
1990
- return `resolves to ${shownPath}, outside ${shownBase}. ${provenance}`;
1991
- }
1992
- /**
1993
- * A letter or digit that neither draws as a blank nor reads as a quote.
1994
- * `\p{L}` alone admits both, so two classes are carved out: the
1995
- * default-ignorables, which hold letters that draw as a blank (the Hangul
1996
- * fillers U+3164 and U+FFA0), and the quote-shaped letters — the Spacing
1997
- * Modifier Letters block (U+02BA reads as `"`, U+02BC as `'`) plus the ones
1998
- * outside it (U+0374, U+0559, U+07F4-U+07F5, U+A78B-U+A78C, and the halfwidth
1999
- * sound marks U+FF9E-U+FF9F). Not all of `\p{Lm}`: U+30FC is in it, and it is
2000
- * an ordinary character of a Japanese directory name.
2001
- */
2002
- const VISIBLE_LETTER = new RegExp(String.raw`^(?![\p{Default_Ignorable_Code_Point}\u02b0-\u02ff\u0374\u0559\u07f4\u07f5\ua78b\ua78c\uff9e\uff9f])[\p{L}\p{N}]$`, "u");
2003
- /**
2004
- * A combining mark, which counts only DIRECTLY after a visible letter (or a
2005
- * mark that itself counted): on a space or on punctuation it draws on its own,
2006
- * and U+030B / U+030E there look like a quote.
2007
- */
2008
- const COMBINING_MARK = /^\p{M}$/u;
2009
- /**
2010
- * A default-ignorable code point, which draws as NOTHING. Tested before the
2011
- * mark rule, because some are combining marks (U+034F, U+17B4, U+180B, the
2012
- * variation selectors U+FE00-U+FE0F and U+E0100-U+E01EF): after a letter they
2013
- * would otherwise pass as bare, and `/home/me/.ss\u034fh` would print exactly
2014
- * like `.ssh` while naming a different path. The host's classifier
2015
- * (go-to-k/cdkd#3509) does not carve these out; this one does.
2016
- */
2017
- const DEFAULT_IGNORABLE = /^\p{Default_Ignorable_Code_Point}$/u;
2018
- /**
2019
- * The ASCII a bare value may carry besides letters and digits. Everything else
2020
- * — any whitespace, any quote, any other symbol — takes the boundary, which
2021
- * costs a legitimate path nothing but a pair of quotes.
2022
- */
2023
- const BARE_PUNCTUATION = /^[/\\._~+@:=-]$/;
2024
- /**
2025
- * Classify each code point: `bare` (allowed in a bare value), `shown` (shown
2026
- * as itself inside the boundary), or `escaped`. An ALLOWLIST, because a
2027
- * denylist has to enumerate every character that draws as a blank or reads as
2028
- * a quote.
2029
- *
2030
- * Each test is ONE character and this walks the value — never `^(...)+$` over
2031
- * the whole of it: alternatives that overlap under a quantifier backtrack
2032
- * exponentially on a long value that fails near its end.
2033
- */
2034
- function classify(chars) {
2035
- let afterLetter = false;
2036
- return chars.map((ch) => {
2037
- if (DEFAULT_IGNORABLE.test(ch)) {
2038
- afterLetter = false;
2039
- return "escaped";
2040
- }
2041
- if (COMBINING_MARK.test(ch)) return afterLetter ? "bare" : "escaped";
2042
- afterLetter = VISIBLE_LETTER.test(ch);
2043
- if (afterLetter || BARE_PUNCTUATION.test(ch)) return "bare";
2044
- return ch >= " " && ch <= "~" ? "shown" : "escaped";
2045
- });
2046
- }
2047
- /**
2048
- * Render an untrusted value — a filesystem path, identifier or command an
2049
- * assembly chose, or a key a request supplied — into cdk-local's own prose
2050
- * (go-to-k/cdk-local#758; the host's `displayAssemblyPath`, go-to-k/cdkd#3509).
2051
- * The caller writes NO quotes around the result.
2052
- *
2053
- * `sanitizeServiceExceptionMessage` alone is not enough inside quotes of ours:
2054
- * it flattens control characters and passes `'`, so a value carrying one
2055
- * closed the quote and wrote a clause of its own into the message. This keeps
2056
- * a plain value bare and puts any other one inside a JSON string literal.
2057
- * Inside it, `"` and `\` are escaped as JSON escapes them, and so is every
2058
- * character that is neither printable ASCII nor a visible letter — a curly or
2059
- * fullwidth quote that could pass for the boundary's own closing `"`, and a
2060
- * blank that could pass for a space. The result stays valid JSON: `JSON.parse`
2061
- * returns the sanitized value.
2062
- *
2063
- * A legitimate value with a space or any symbol outside `/ \ . _ ~ + @ : = -`
2064
- * (`/Users/me/My Project/cdk.out`) renders quoted; a non-ASCII letter is
2065
- * shown as itself, and a non-letter non-ASCII character (an emoji, `©`) as
2066
- * its `\u` escape. A value the sanitizer ALTERED (a control character
2067
- * flattened, an over-long value truncated) did not arrive plain, so it always
2068
- * takes the boundary. The empty string renders as `""`.
2069
- *
2070
- * The sanitizer also CAPS the value at 512 code points, so a longer path
2071
- * prints truncated (inside the boundary, with its true length named). That
2072
- * is deliberate: an unbounded assembly-chosen value would otherwise make the
2073
- * log line as long as the attacker likes, and a real path is far shorter.
2074
- */
2075
- function displayUntrustedValue(value) {
2076
- const clean = sanitizeServiceExceptionMessage(value);
2077
- const chars = Array.from(clean);
2078
- const kinds = classify(chars);
2079
- if (clean === value && chars.length > 0 && kinds.every((k) => k === "bare")) return clean;
2080
- let body = "";
2081
- chars.forEach((ch, i) => {
2082
- if (ch === "\"" || ch === "\\") body += `\\${ch}`;
2083
- else if (kinds[i] !== "escaped") body += ch;
2084
- else for (let u = 0; u < ch.length; u++) body += `\\u${ch.charCodeAt(u).toString(16).padStart(4, "0")}`;
2085
- });
2086
- return `"${body}"`;
2087
- }
2088
-
2089
1228
  //#endregion
2090
1229
  //#region src/utils/error-handler.ts
2091
1230
  /**
@@ -8039,6 +7178,166 @@ function applyDeployedEnvFallback(resolvedEnv, unresolved, deployedEnv) {
8039
7178
  };
8040
7179
  }
8041
7180
 
7181
+ //#endregion
7182
+ //#region src/local/ecr-uri.ts
7183
+ /**
7184
+ * Every ECR private-registry host form, read off the AWS-published `ecr`
7185
+ * endpoint list (https://docs.aws.amazon.com/general/latest/gr/ecr.html):
7186
+ *
7187
+ * - `<acct>.dkr.ecr.<region>.<partition suffix>` — the plain IPv4 endpoint.
7188
+ * - `<acct>.dkr.ecr-fips.<region>.<partition suffix>` — FIPS IPv4.
7189
+ * - `<acct>.dkr-ecr.<region>.on.aws` — dual-stack (IPv4 + IPv6).
7190
+ * - `<acct>.dkr-ecr-fips.<region>.on.aws` — dual-stack FIPS.
7191
+ *
7192
+ * Every form except the plain one is scoped to the `aws` and `aws-us-gov`
7193
+ * partitions, the only ones that endpoint list shows it in: both FIPS forms are
7194
+ * listed for `us-east-1` / `us-east-2` / `us-west-1` / `us-west-2` /
7195
+ * `us-gov-east-1` / `us-gov-west-1` only, and `on.aws` is the dual-stack DNS of
7196
+ * those two partitions (the others serve dual-stack under their own suffixes,
7197
+ * which are not listed here). A FIPS or dual-stack host in any other partition
7198
+ * is therefore not recognized and is pulled as a public image, as before issue
7199
+ * #760. The scope is by PARTITION, not by region: a commercial region AWS adds
7200
+ * later is accepted, and a login to an unserved region's FIPS host only fails,
7201
+ * because the suffix is still AWS-owned.
7202
+ */
7203
+ const FIPS_AND_DUAL_STACK_PARTITIONS = ["aws", "aws-us-gov"];
7204
+ const ECR_REGISTRY_HOST_FORMS = [
7205
+ { labels: "dkr.ecr" },
7206
+ {
7207
+ labels: "dkr.ecr-fips",
7208
+ partitions: FIPS_AND_DUAL_STACK_PARTITIONS
7209
+ },
7210
+ {
7211
+ labels: "dkr-ecr",
7212
+ fixedUrlSuffix: "on.aws",
7213
+ partitions: FIPS_AND_DUAL_STACK_PARTITIONS
7214
+ },
7215
+ {
7216
+ labels: "dkr-ecr-fips",
7217
+ fixedUrlSuffix: "on.aws",
7218
+ partitions: FIPS_AND_DUAL_STACK_PARTITIONS
7219
+ }
7220
+ ];
7221
+ const ECR_HOST_FORM_BY_LABELS = new Map(ECR_REGISTRY_HOST_FORMS.map((f) => [f.labels, f]));
7222
+ function escapeRegExp(value) {
7223
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7224
+ }
7225
+ /**
7226
+ * `<acct>.<labels>.<region>.<suffix>/` — ANCHORED at the start, so nothing
7227
+ * (userinfo, a scheme, another host) can precede the account id, and ending at
7228
+ * the first `/`, so the captured suffix is the rest of the host.
7229
+ *
7230
+ * The labels alternation is built from {@link ECR_REGISTRY_HOST_FORMS},
7231
+ * ESCAPED (an unescaped `dkr.ecr` would match `dkrxecr`) and longest first, so
7232
+ * `dkr.ecr-fips` is tried before its own `dkr.ecr` prefix. The `i` flag makes
7233
+ * the labels case-insensitive, as DNS is; it is non-unicode, so it does NOT
7234
+ * treat U+212A (Kelvin sign) as `k`, and `\d` is ASCII-only.
7235
+ */
7236
+ const ECR_URI_HOST_REGEX = new RegExp(`^(\\d{12})\\.(${[...ECR_REGISTRY_HOST_FORMS].sort((a, b) => b.labels.length - a.labels.length).map((f) => escapeRegExp(f.labels)).join("|")})\\.([^./]+)\\.([^/]+)/`, "i");
7237
+ /**
7238
+ * A region segment is ASCII alphanumerics plus `-`. Tested on the RAW capture,
7239
+ * BEFORE any case fold: `String.prototype.toLowerCase` folds U+212A onto an
7240
+ * ASCII `k`, so testing a folded value would admit `us-eKst-1` (Kelvin sign) as
7241
+ * the region `us-ekst-1` — a region the host does not name, which then seeds
7242
+ * the ECR client and the login endpoint. Today {@link REGION_SHAPE} also
7243
+ * refuses that input, because the fold is ASCII-only and the shape is ASCII
7244
+ * only, so this guard is DEFENSE IN DEPTH: it keeps the region safe if the fold
7245
+ * is ever widened to full Unicode folding.
7246
+ */
7247
+ const CANONICAL_REGION_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
7248
+ /**
7249
+ * The SHAPE of an AWS region id, tested on the FOLDED region after the charset
7250
+ * guard above. One alternative per partition, transcribed from the
7251
+ * `regionRegex` of each partition in the AWS SDK / botocore `partitions.json`
7252
+ * (`\w+` written as `[a-z0-9]+`):
7253
+ *
7254
+ * - aws: `(us|eu|ap|sa|ca|me|af|il|mx)-X-N`
7255
+ * - aws-cn: `cn-X-N`; aws-us-gov: `us-gov-X-N`
7256
+ * - aws-iso / -b / -e / -f: `us-iso-X-N`, `us-isob-X-N`, `eu-isoe-X-N`,
7257
+ * `us-isof-X-N`
7258
+ * - aws-eusc: `eusc-<cc>-X-N` — broader than upstream's `eusc-(de)-`, matching
7259
+ * `PARTITION_TABLE`'s `eusc-` prefix so a future country is not refused here
7260
+ * while the partition lookup accepts it.
7261
+ *
7262
+ * It refuses a label that is not a region — `s3`, `lambda-url`,
7263
+ * `s3-external-1`, `fips-us-east-1` — so `<acct>.dkr.ecr.s3.amazonaws.com` or
7264
+ * `<acct>.dkr-ecr.lambda-url.on.aws` never becomes a login target. Those hosts
7265
+ * are AWS-owned, but they are not registries, and only TLS would stop a token
7266
+ * sent there (an `insecure-registries` daemon setting turns that off).
7267
+ */
7268
+ const REGION_SHAPE = /^(?:(?:us|eu|ap|sa|ca|me|af|il|mx)-[a-z0-9]+-[0-9]+|cn-[a-z0-9]+-[0-9]+|us-gov-[a-z0-9]+-[0-9]+|us-iso-[a-z0-9]+-[0-9]+|us-isob-[a-z0-9]+-[0-9]+|eu-isoe-[a-z0-9]+-[0-9]+|us-isof-[a-z0-9]+-[0-9]+|eusc-[a-z]+-[a-z0-9]+-[0-9]+)$/;
7269
+ /**
7270
+ * A URL suffix is a dotted name of ASCII alphanumerics and `-`, tested on the
7271
+ * raw capture like the region. Today it is DEFENSE IN DEPTH, not the load-
7272
+ * bearing check: the fold below is ASCII-only, so `cloud.adc-e.uK` (Kelvin
7273
+ * sign) never becomes the `aws-iso-e` suffix, and the exact-suffix comparison
7274
+ * already refuses a port (`:`), userinfo (`@`) or any look-alike. It keeps
7275
+ * the suffix safe if that fold is ever widened to full Unicode folding.
7276
+ */
7277
+ const CANONICAL_URL_SUFFIX = /^[A-Za-z0-9][A-Za-z0-9.-]*$/;
7278
+ /**
7279
+ * Lower-case ONLY `A`-`Z`. DNS case-insensitivity is ASCII-only, and a full
7280
+ * `toLowerCase` would fold U+212A onto `k` and hand the charset guards above an
7281
+ * already-ASCII string, defeating them.
7282
+ */
7283
+ function foldAsciiUpperCase(value) {
7284
+ return value.replace(/[A-Z]+/g, (run) => run.toLowerCase());
7285
+ }
7286
+ /** Docker's rule for "component 1 is a registry host, not a repository segment". */
7287
+ function isRegistryHostComponent(component) {
7288
+ return component.includes(".") || component.includes(":") || component === "localhost";
7289
+ }
7290
+ /**
7291
+ * Lower-case the REGISTRY HOST of an image reference (ASCII letters only) and
7292
+ * leave the repository path and tag byte-identical.
7293
+ *
7294
+ * Docker's credential store is keyed on the hostname VERBATIM, so a login to
7295
+ * one spelling and a pull from another sends no credentials; every
7296
+ * docker-facing use of an ECR reference goes through this one spelling. The
7297
+ * path is not folded: docker requires it lower-case already, and rewriting it
7298
+ * would name a different image. A first component that is not a registry host
7299
+ * (`MyOrg/MyRepo:tag` is a Docker Hub path) is left alone for the same reason.
7300
+ */
7301
+ function canonicalizeImageUriHost(imageUri) {
7302
+ const slash = imageUri.indexOf("/");
7303
+ if (slash < 0) return imageUri;
7304
+ const host = imageUri.slice(0, slash);
7305
+ if (!isRegistryHostComponent(host)) return imageUri;
7306
+ return foldAsciiUpperCase(host) + imageUri.slice(slash);
7307
+ }
7308
+ /**
7309
+ * The ECR registry an image reference names, or `undefined` when its host is
7310
+ * not one of {@link ECR_REGISTRY_HOST_FORMS} with the suffix that form carries
7311
+ * for the region it names.
7312
+ *
7313
+ * The suffix check is what keeps a `docker login` from being pointed at a host
7314
+ * cdk-local does not own: `<acct>.dkr.ecr.<region>.example.com`, a look-alike
7315
+ * `amazonaws.com.evil.example`, a port or userinfo after the suffix, and a
7316
+ * form/suffix mispairing (`dkr-ecr` + `amazonaws.com`, `dkr.ecr` + `on.aws`,
7317
+ * an ISO region + `amazonaws.com`) are all refused.
7318
+ */
7319
+ function parseEcrRegistryHost(imageUri) {
7320
+ const m = ECR_URI_HOST_REGEX.exec(imageUri);
7321
+ if (!m) return void 0;
7322
+ const [, accountId, rawLabels, rawRegion, rawSuffix] = m;
7323
+ if (!CANONICAL_REGION_SEGMENT.test(rawRegion)) return void 0;
7324
+ if (!CANONICAL_URL_SUFFIX.test(rawSuffix)) return void 0;
7325
+ const form = ECR_HOST_FORM_BY_LABELS.get(foldAsciiUpperCase(rawLabels));
7326
+ if (!form) return void 0;
7327
+ const region = foldAsciiUpperCase(rawRegion);
7328
+ if (!REGION_SHAPE.test(region)) return void 0;
7329
+ const suffix = foldAsciiUpperCase(rawSuffix);
7330
+ const partition = derivePartitionAndUrlSuffix(region);
7331
+ if (form.partitions && !form.partitions.includes(partition.partition)) return void 0;
7332
+ if (suffix !== (form.fixedUrlSuffix ?? partition.urlSuffix)) return void 0;
7333
+ return {
7334
+ accountId,
7335
+ region,
7336
+ registryHost: `${accountId}.${form.labels}.${region}.${suffix}`,
7337
+ form
7338
+ };
7339
+ }
7340
+
8042
7341
  //#endregion
8043
7342
  //#region src/local/ecs-task-resolver.ts
8044
7343
  var EcsTaskResolutionError = class EcsTaskResolutionError extends Error {
@@ -8553,9 +7852,10 @@ function buildSubstitutionContextFromImageContext(context) {
8553
7852
  * Parse the `Image` field of an ECS container definition.
8554
7853
  *
8555
7854
  * Three shapes:
8556
- * - `<account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` — same-account
8557
- * same-region ECR. Cross-account/region is hard-errored (matches
8558
- * `cdkl invoke`'s ECR-pull semantics).
7855
+ * - `<account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` — ECR, in any
7856
+ * host form `ECR_REGISTRY_HOST_FORMS` lists (FIPS / dual-stack too) and any
7857
+ * partition. Pulled through `pullEcrImage` (cross-account / cross-region
7858
+ * below).
8559
7859
  * - `Fn::Sub` / `Fn::Join` / `Ref` referencing a `Code.fromAsset`-style
8560
7860
  * CDK asset image. Surfaces `kind: 'cdk-asset'` with the optional
8561
7861
  * asset hash so the runner can route through `docker-build.ts`.
@@ -8602,12 +7902,12 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
8602
7902
  if (substituted.includes("AWS::")) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${substituted}). ${getEmbedConfig().productName} could not resolve them: confirm AWS credentials are configured so STS GetCallerIdentity succeeds, and that --region / AWS_REGION names the target region. Workaround: build the image locally (ContainerImage.fromAsset) or pin a public image.`);
8603
7903
  throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image with unresolved \${...} placeholders (${substituted}). ${getEmbedConfig().cliName} only resolves AWS pseudo parameters and same-stack AWS::ECR::Repository refs.`);
8604
7904
  }
8605
- const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(substituted);
8606
- if (ecrMatch) return {
7905
+ const ecrHost = parseEcrRegistryHost(substituted);
7906
+ if (ecrHost) return {
8607
7907
  kind: "ecr",
8608
7908
  uri: substituted,
8609
- account: ecrMatch[1],
8610
- region: ecrMatch[2]
7909
+ account: ecrHost.accountId,
7910
+ region: ecrHost.region
8611
7911
  };
8612
7912
  return {
8613
7913
  kind: "public",
@@ -8643,12 +7943,12 @@ function classifyResolvedImage(uri) {
8643
7943
  if (hashMatch) out.assetHash = hashMatch[1];
8644
7944
  return out;
8645
7945
  }
8646
- const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(uri);
8647
- if (ecrMatch) return {
7946
+ const ecrHost = parseEcrRegistryHost(uri);
7947
+ if (ecrHost) return {
8648
7948
  kind: "ecr",
8649
7949
  uri,
8650
- account: ecrMatch[1],
8651
- region: ecrMatch[2]
7950
+ account: ecrHost.accountId,
7951
+ region: ecrHost.region
8652
7952
  };
8653
7953
  return {
8654
7954
  kind: "public",
@@ -9603,7 +8903,7 @@ async function buildDockerImage(asset, cdkOutDir, options) {
9603
8903
  const [cmd, ...args] = source.executable;
9604
8904
  if (!cmd) throw options.wrapError("asset source.executable[] is empty");
9605
8905
  const cwd = source.directory ? contextDirectory(source.directory, "run this asset's source.executable with that directory as its working directory") : cdkOutDir;
9606
- logger.debug(`Building Docker image via executable: ${sanitizeServiceExceptionMessage(source.executable.join(" "))} (cwd=${displayUntrustedValue(cwd)})`);
8906
+ logger.debug(`Building Docker image via executable: ${displayUntrustedValue(source.executable.join(" "))} (cwd=${displayUntrustedValue(cwd)})`);
9607
8907
  warnManifestExecutable(cmd, source.executable, cwd);
9608
8908
  let result;
9609
8909
  try {
@@ -9613,7 +8913,7 @@ async function buildDockerImage(asset, cdkOutDir, options) {
9613
8913
  throw options.wrapError(e.stderr || e.message || String(err));
9614
8914
  }
9615
8915
  const tag = result.stdout.trim();
9616
- if (!tag) throw options.wrapError(`docker build executable produced no output (expected the local image tag on stdout): ${cmd} ${args.join(" ")}`);
8916
+ if (!tag) throw options.wrapError(`docker build executable produced no output (expected the local image tag on stdout): ${displayUntrustedValue(cmd)}`);
9617
8917
  return tag;
9618
8918
  }
9619
8919
  if (!source.directory) throw options.wrapError(`DockerImageAssetSource must set either 'directory' or 'executable' (got: ${JSON.stringify(source)})`);
@@ -9622,7 +8922,7 @@ async function buildDockerImage(asset, cdkOutDir, options) {
9622
8922
  const contextDir = contextDirectory(source.directory, `send that directory to docker build as the context of an image ${getEmbedConfig().productName} then runs locally`);
9623
8923
  buildArgs.push(".");
9624
8924
  warnEscapingBuildKitPaths(source, contextDir, assetOutdir);
9625
- logger.debug(`${getDockerCmd()} ${buildArgs.join(" ")} (cwd=${contextDir})`);
8925
+ logger.debug(`${getDockerCmd()} ${buildArgs.join(" ")} (cwd=${displayUntrustedValue(contextDir)})`);
9626
8926
  try {
9627
8927
  await runDockerStreaming(buildArgs, {
9628
8928
  cwd: contextDir,
@@ -9666,49 +8966,27 @@ function buildDockerBuildCommand(source, tag, platformOverride) {
9666
8966
  //#endregion
9667
8967
  //#region src/local/ecr-puller.ts
9668
8968
  /**
9669
- * ECR pull fallback for `cdkl invoke` / `cdkl start-api` /
9670
- * `cdkl run-task`. When the image URI resolves to an ECR repo but
9671
- * doesn't match any cdk.out asset (typical when invoking a stack
9672
- * deployed elsewhere or sharing a centralized registry), cdk-local
9673
- * authenticates against the target registry and runs `docker pull`.
9674
- *
9675
- * **Cross-account / cross-region** (#455):
9676
- * - Same-account, same-region: fast path. No STS hop. The default
9677
- * credential chain is used directly for `ecr:GetAuthorizationToken`.
9678
- * - `ecrRoleArn` is provided: `sts:AssumeRole` is issued via the
9679
- * default credential chain to obtain temporary credentials for the
9680
- * target account. The resulting credentials authenticate the ECR
9681
- * client (regardless of region — the ECR client is built for the
9682
- * URI's region, which can differ from the caller's profile region).
9683
- * - Cross-account, NO `ecrRoleArn`: cdk-local falls through to the
9684
- * default credential chain. This works when the caller has been
9685
- * granted cross-account `ecr:GetAuthorizationToken` +
9686
- * `ecr:BatchGetImage` permissions on the target repository via an
9687
- * IAM policy; otherwise AWS rejects the call with `AccessDenied`
9688
- * and the user is pointed at `--ecr-role-arn`.
9689
- *
9690
- * The `--no-pull` semantics (C3 in the design doc):
9691
- * - When NOT set: `ecrLogin` + `docker pull <uri>`.
9692
- * - When set: skip `docker pull`. If the image isn't in the local
9693
- * cache, the subsequent `docker run` will fail; we surface a clearer
9694
- * "image not in local cache" error here so the user knows to drop
9695
- * `--no-pull` or pre-pull manually.
9696
- */
9697
- /** Regex matching the `<acct>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` shape. */
9698
- const ECR_URI_REGEX = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\/([^:]+):(.+)$/;
9699
- /**
9700
8969
  * Parse an ECR image URI. Returns `undefined` for non-ECR URIs (typically:
9701
8970
  * Docker Hub, public.ecr.aws, gcr.io, ...) — those are user-managed
9702
8971
  * images we don't try to authenticate against.
8972
+ *
8973
+ * Recognizes every host form in `ECR_REGISTRY_HOST_FORMS` (plain, FIPS,
8974
+ * dual-stack, dual-stack FIPS) in every partition, each with the suffix that
8975
+ * form carries for its region (issue #760) — see `parseEcrRegistryHost`.
9703
8976
  */
9704
8977
  function parseEcrUri(imageUri) {
9705
- const m = ECR_URI_REGEX.exec(imageUri);
8978
+ const host = parseEcrRegistryHost(imageUri);
8979
+ if (!host) return void 0;
8980
+ const canonicalUri = canonicalizeImageUriHost(imageUri);
8981
+ const m = /^([^:]+):(.+)$/.exec(canonicalUri.slice(canonicalUri.indexOf("/") + 1));
9706
8982
  if (!m) return void 0;
9707
8983
  return {
9708
- accountId: m[1],
9709
- region: m[2],
9710
- repository: m[3],
9711
- tag: m[4]
8984
+ accountId: host.accountId,
8985
+ region: host.region,
8986
+ repository: m[1],
8987
+ tag: m[2],
8988
+ canonicalUri,
8989
+ registryHost: host.registryHost
9712
8990
  };
9713
8991
  }
9714
8992
  /**
@@ -9748,17 +9026,20 @@ function isCredentialFresh(creds) {
9748
9026
  *
9749
9027
  * Auto-detects cross-account from `STS:GetCallerIdentity` and assumes
9750
9028
  * the supplied role when set. Returns the image URI the caller should
9751
- * pass to `docker run` (same as the input — no rewriting).
9029
+ * pass to `docker run`: the input with its registry host ASCII-lower-cased
9030
+ * (`ParsedEcrUri.canonicalUri`), so the run names the same spelling the pull
9031
+ * and the login used; the repository path and tag are unchanged.
9752
9032
  */
9753
9033
  async function pullEcrImage(imageUri, options) {
9754
9034
  const logger = getLogger().child("ecr-puller");
9755
9035
  const parsed = parseEcrUri(imageUri);
9756
9036
  if (!parsed) throw new LocalInvokeBuildError(`Image URI '${imageUri}' is not an ECR URI. ${getEmbedConfig().cliName} invoke v1 only authenticates against ECR for the deployed-image fallback path.`);
9037
+ const canonicalUri = parsed.canonicalUri;
9757
9038
  const callerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"];
9758
9039
  if (options.skipPull) {
9759
- logger.info(`Skipping ECR pull (--no-pull). Verifying ${imageUri} is in local cache...`);
9760
- await verifyImageInLocalCache(imageUri);
9761
- return imageUri;
9040
+ logger.info(`Skipping ECR pull (--no-pull). Verifying ${canonicalUri} is in local cache...`);
9041
+ await verifyImageInLocalCache(canonicalUri);
9042
+ return canonicalUri;
9762
9043
  }
9763
9044
  const callerIdentityKey = `${options.profile ?? "_noprofile"}|${callerRegion ?? "_unset"}`;
9764
9045
  let callerAccount = CALLER_IDENTITY_CACHE.get(callerIdentityKey);
@@ -9801,17 +9082,21 @@ async function pullEcrImage(imageUri, options) {
9801
9082
  ...assumed ? { credentials: assumed } : options.profile ? { profile: options.profile } : {}
9802
9083
  });
9803
9084
  try {
9804
- await ecrLogin(ecr, parsed.accountId, parsed.region);
9085
+ await ecrLogin(ecr, {
9086
+ accountId: parsed.accountId,
9087
+ region: parsed.region,
9088
+ registryHost: parsed.registryHost
9089
+ });
9805
9090
  } finally {
9806
9091
  ecr.destroy();
9807
9092
  }
9808
- logger.info(`Pulling ${imageUri}...`);
9093
+ logger.info(`Pulling ${canonicalUri}...`);
9809
9094
  try {
9810
- await runDockerForeground(["pull", imageUri]);
9095
+ await runDockerForeground(["pull", canonicalUri]);
9811
9096
  } catch (err) {
9812
- throw new LocalInvokeBuildError(`docker pull ${imageUri} failed: ${err.message}`);
9097
+ throw new LocalInvokeBuildError(`docker pull ${canonicalUri} failed: ${err.message}`);
9813
9098
  }
9814
- return imageUri;
9099
+ return canonicalUri;
9815
9100
  }
9816
9101
  /**
9817
9102
  * Assume the supplied role via the SDK default credential chain and
@@ -9852,16 +9137,29 @@ async function assumeRoleForEcr(roleArn, callerRegion, profile, logger) {
9852
9137
  * Authenticate the local docker daemon against the target ECR registry.
9853
9138
  * Self-contained in this module so the local-invoke path stays
9854
9139
  * independent of any full asset-publish path's larger surface area.
9855
- */
9856
- async function ecrLogin(client, accountId, region) {
9857
- getLogger().child("ecr-puller").debug(`ECR login (account=${accountId}, region=${region})`);
9140
+ *
9141
+ * The login endpoint is ALWAYS the host the PULL targets (issue #760):
9142
+ * docker's credential store is keyed on the hostname verbatim, so a login to
9143
+ * any other host leaves the pull with no credentials (`no basic auth
9144
+ * credentials`). `GetAuthorizationToken`'s `proxyEndpoint` is therefore NOT
9145
+ * used: it names the CALLER's default registry on the plain host, which is the
9146
+ * wrong host for a FIPS / dual-stack pull and the wrong ACCOUNT for a
9147
+ * cross-account pull made on the caller's own credentials (a repository policy
9148
+ * grant, no `--ecr-role-arn`). The token is principal-scoped, not host-scoped:
9149
+ * measured on real ECR, one token authenticates the plain, FIPS and dual-stack
9150
+ * hosts alike. The password reaches docker only over `--password-stdin`, never
9151
+ * argv.
9152
+ */
9153
+ async function ecrLogin(client, target) {
9154
+ const { accountId, region, registryHost } = target;
9155
+ getLogger().child("ecr-puller").debug(`ECR login (account=${accountId}, region=${region}, host=${registryHost})`);
9858
9156
  const authData = (await client.send(new GetAuthorizationTokenCommand({})).catch((err) => {
9859
9157
  throw new LocalInvokeBuildError(`ECR GetAuthorizationToken failed (account=${accountId}, region=${region}): ${describeAwsFailureForWarn(err, "ECR GetAuthorizationToken")}. Verify the credentials can call ecr:GetAuthorizationToken.`);
9860
9158
  })).authorizationData?.[0];
9861
9159
  if (!authData?.authorizationToken) throw new LocalInvokeBuildError("Failed to get ECR authorization token");
9862
9160
  const [username, password] = Buffer.from(authData.authorizationToken, "base64").toString().split(":");
9863
9161
  if (!username || password === void 0) throw new LocalInvokeBuildError("ECR authorization token has unexpected shape (missing username/password)");
9864
- const endpoint = authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.amazonaws.com`;
9162
+ const endpoint = `https://${registryHost}`;
9865
9163
  try {
9866
9164
  await runDockerStreaming([
9867
9165
  "login",
@@ -9937,7 +9235,7 @@ async function buildContainerImage(asset, cdkOutDir, options) {
9937
9235
  const actualTag = await buildDockerImage(asset, cdkOutDir, {
9938
9236
  tag,
9939
9237
  platform,
9940
- wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${sanitizeServiceExceptionMessage(asset.source.directory ?? asset.source.executable?.join(" ") ?? "")}): ${stderr}`),
9238
+ wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${displayUntrustedValue(asset.source.directory ?? asset.source.executable?.[0] ?? "")}): ${stderr}`),
9941
9239
  progressLabel: `Building container image (platform=${platform})`,
9942
9240
  ...options.assetOutdir !== void 0 && { assetOutdir: options.assetOutdir }
9943
9241
  });
@@ -24950,7 +24248,7 @@ async function prepareOneImage(task, container, options) {
24950
24248
  tag,
24951
24249
  assetOutdir: assetPathDirs(task.stack).assetOutdir,
24952
24250
  ...options.platformOverride !== void 0 && { platform: options.platformOverride },
24953
- wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${sanitizeServiceExceptionMessage(asset.source.directory ?? asset.source.executable?.join(" ") ?? "")}): ${stderr}`),
24251
+ wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${displayUntrustedValue(asset.source.directory ?? asset.source.executable?.[0] ?? "")}): ${stderr}`),
24954
24252
  progressLabel: `Building container image for '${container.name}'`
24955
24253
  });
24956
24254
  if (actualTag !== tag) try {
@@ -26375,7 +25673,7 @@ async function softReloadReplica(args) {
26375
25673
  const defaultDockerInspectWorkdirImpl = async (containerId) => {
26376
25674
  const { execFile } = await import("node:child_process");
26377
25675
  const { promisify } = await import("node:util");
26378
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25676
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26379
25677
  const { stdout } = await promisify(execFile)(getDockerCmd(), [
26380
25678
  "inspect",
26381
25679
  "--format",
@@ -26394,7 +25692,7 @@ let dockerInspectWorkdirImpl = defaultDockerInspectWorkdirImpl;
26394
25692
  const defaultDockerCpImpl = async (src, dst) => {
26395
25693
  const { execFile } = await import("node:child_process");
26396
25694
  const { promisify } = await import("node:util");
26397
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25695
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26398
25696
  await promisify(execFile)(getDockerCmd(), [
26399
25697
  "cp",
26400
25698
  src,
@@ -26409,7 +25707,7 @@ let dockerCpImpl = defaultDockerCpImpl;
26409
25707
  const defaultDockerRestartImpl = async (containerId) => {
26410
25708
  const { execFile } = await import("node:child_process");
26411
25709
  const { promisify } = await import("node:util");
26412
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25710
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26413
25711
  await promisify(execFile)(getDockerCmd(), ["restart", containerId]);
26414
25712
  };
26415
25713
  let dockerRestartImpl = defaultDockerRestartImpl;
@@ -26449,7 +25747,7 @@ async function disconnectOldFromSharedNetwork(oldInstance) {
26449
25747
  const defaultDockerNetworkDisconnectImpl = async (networkName, containerId) => {
26450
25748
  const { execFile } = await import("node:child_process");
26451
25749
  const { promisify } = await import("node:util");
26452
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25750
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26453
25751
  await promisify(execFile)(getDockerCmd(), [
26454
25752
  "network",
26455
25753
  "disconnect",
@@ -26626,7 +25924,7 @@ function pickEssentialContainerId(instance, service) {
26626
25924
  const defaultWaitForExitImpl = async (containerId) => {
26627
25925
  const { execFile } = await import("node:child_process");
26628
25926
  const { promisify } = await import("node:util");
26629
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25927
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26630
25928
  const { stdout } = await promisify(execFile)(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
26631
25929
  const code = parseInt(stdout.trim(), 10);
26632
25930
  return Number.isFinite(code) ? code : -1;
@@ -26646,7 +25944,7 @@ const EXIT_LOG_TAIL_LINES = 50;
26646
25944
  const defaultReadContainerLogsImpl = async (containerId) => {
26647
25945
  const { execFile } = await import("node:child_process");
26648
25946
  const { promisify } = await import("node:util");
26649
- const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
25947
+ const { getDockerCmd } = await import("./docker-cmd-B5yU__zC.js").then((n) => n.t);
26650
25948
  const { stdout, stderr } = await promisify(execFile)(getDockerCmd(), [
26651
25949
  "logs",
26652
25950
  "--tail",
@@ -41780,5 +41078,5 @@ function addStudioSpecificOptions(cmd) {
41780
41078
  }
41781
41079
 
41782
41080
  //#endregion
41783
- 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 };
41784
- //# sourceMappingURL=local-studio-Cdq8klwg.js.map
41081
+ 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 };
41082
+ //# sourceMappingURL=local-studio-CLk7iSNq.js.map