cdk-local 0.147.16 → 0.147.18

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.
@@ -8102,6 +8102,136 @@ function extractHashFromImageUri(imageUri) {
8102
8102
  return /:([a-f0-9]{8,})$/.exec(imageUri)?.[1];
8103
8103
  }
8104
8104
 
8105
+ //#endregion
8106
+ //#region src/utils/url-authority.ts
8107
+ /**
8108
+ * Compose the authority component of a URL from a host and a port.
8109
+ *
8110
+ * Every local serve prints an endpoint banner, and several of those banners
8111
+ * are MACHINE-PARSED — `cdkl studio` reads them to learn where to point its
8112
+ * capture proxy (`local/studio-serve-manager`'s `readyRe` /
8113
+ * `parsePublishedHostEndpoint`), then resolves the result with `new URL(...)`
8114
+ * before it will forward anything there. So an authority that the WHATWG
8115
+ * parser rejects is not a cosmetic log defect: the serve is refused.
8116
+ *
8117
+ * The one shape that used to be composed wrong is an IPv6 literal. RFC 3986
8118
+ * 3.2.2 requires it to be bracketed inside an authority, so a bare
8119
+ * `${host}:${port}` turns `--container-host ::` into `http://:::8080`, which
8120
+ * is not a URL — while the IPv4 wildcard `0.0.0.0`, the same intention spelled
8121
+ * differently, works. Issue go-to-k/cdk-local#599.
8122
+ *
8123
+ * The detection is mechanical rather than a list of known values: a colon is
8124
+ * forbidden in both a registered name and an IPv4 address, so a host carrying
8125
+ * one is an IPv6 literal — PROVIDED the rest of it could be one, which is a
8126
+ * character-class test (hex digits, `:`, and `.` for the IPv4-mapped form)
8127
+ * rather than an enumeration. The qualifier is load-bearing rather than
8128
+ * pedantry: `buildRedirectLocation` fills its host from an ALB `#{host}`
8129
+ * template or a configured literal, so a CRLF-injection attempt reaches this
8130
+ * function AS-IS — `example.test\r\nx-injected: yes`, with the CR/LF still in
8131
+ * it, because `front-door-server` sanitises the Location only AFTER building
8132
+ * it. Either way it is a colon in something that is not an address and must
8133
+ * pass through untouched rather than gain brackets it never had. (CR and LF
8134
+ * are outside the character class by construction, so the raw and flattened
8135
+ * spellings classify identically — but the raw one is what actually arrives.)
8136
+ * Caught by `front-door-server.test.ts`'s raw-socket injection case.
8137
+ */
8138
+ /**
8139
+ * A host that could be an IPv6 literal: hex digits, `:` separators, and `.`
8140
+ * for the IPv4-mapped form (`::ffff:127.0.0.1`). Deliberately NOT a full
8141
+ * grammar — the job is to separate "an address" from "not an address", and
8142
+ * validating an address that has already been bound to is not this function's
8143
+ * business.
8144
+ */
8145
+ const IPV6_LITERAL_CHARS = /^[0-9A-Fa-f:.]+$/;
8146
+ /**
8147
+ * Render `host` as it must appear inside a URL authority.
8148
+ *
8149
+ * - An IPv6 literal is bracketed, in either case (`FE80::1` -> `[FE80::1]`).
8150
+ * - An already-bracketed literal comes back bracketed exactly once, so the
8151
+ * function is idempotent: `[::1]` never becomes `[[::1]]`. That matters
8152
+ * because a caller often feeds back a `URL.hostname`, which is ALREADY
8153
+ * bracketed.
8154
+ * - A zone id (`fe80::1%en0`) is DROPPED, keeping `[fe80::1]`. No URL parser
8155
+ * accepts one — Node's `new URL` throws on both the raw `%en0` and the
8156
+ * percent-encoded `%25en0` — and a zone id is a host-local interface
8157
+ * selector that carries no meaning for whoever reads the banner. Emitting
8158
+ * an authority nothing can parse is the exact failure this helper exists to
8159
+ * prevent, so the scope is dropped rather than propagated. The brackets are
8160
+ * removed BEFORE that decision, so `[fe80::1%en0]` is stripped too — an
8161
+ * early return on "already bracketed" would have waved the one input that
8162
+ * is both bracketed and unparseable straight through.
8163
+ * - Everything else (IPv4 literal, registered name, the empty string, and a
8164
+ * colon-bearing string that is not an address) is returned untouched.
8165
+ */
8166
+ function formatHostForAuthority(host) {
8167
+ const inner = host.length > 1 && host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
8168
+ if (!inner.includes(":")) return host;
8169
+ const zoneAt = inner.indexOf("%");
8170
+ const literal = zoneAt === -1 ? inner : inner.slice(0, zoneAt);
8171
+ if (!IPV6_LITERAL_CHARS.test(literal)) return host;
8172
+ return `[${literal}]`;
8173
+ }
8174
+ /**
8175
+ * Compose `<host>:<port>` for use inside a URL, bracketing an IPv6 literal.
8176
+ *
8177
+ * An empty `host` is passed through as-is rather than substituted: the helper
8178
+ * composes an authority, it does not invent a host, and a caller that has no
8179
+ * host has a bug of its own. The result (`:8080`) does not parse, which is the
8180
+ * honest rendering of that state.
8181
+ */
8182
+ function formatAuthority(host, port) {
8183
+ return `${formatHostForAuthority(host)}:${port}`;
8184
+ }
8185
+ /**
8186
+ * Read the HOST out of an authority (`<host>` or `<host>:<port>`), returning
8187
+ * it BARE — an IPv6 literal comes back without its brackets, ready to be
8188
+ * handed straight back to {@link formatAuthority}.
8189
+ *
8190
+ * The inverse of {@link formatAuthority}, and it exists for the same reason:
8191
+ * `authority.split(':')[0]` is the obvious thing to write and it is wrong for
8192
+ * exactly one input. A conforming client sends `Host: [::1]:8080` (measured —
8193
+ * see `tests/unit/local/ipv6-host-header.test.ts`), and splitting that on the
8194
+ * first colon yields `'['`, so an ALB `#{host}` substitution built from it
8195
+ * produced `http://[:8080/...`. Issue go-to-k/cdk-local#599.
8196
+ *
8197
+ * # It refuses rather than guesses — on BOTH arms
8198
+ *
8199
+ * This is an attacker-reachable parser: the request `Host` header feeds an
8200
+ * ALB `#{host}` substitution, and whatever comes back is composed into a
8201
+ * redirect `Location`. So a malformed authority yields the empty string
8202
+ * rather than the fragment that happens to be readable, on either branch.
8203
+ *
8204
+ * BRACKETED — read to the closing `]`, and only when the value is
8205
+ * well-formed: there must BE a closing bracket, and what follows it must be
8206
+ * empty or `:<digits>`. Guessing here is the worse of the two directions,
8207
+ * because the guess PARSES: `[evil.example` (no closing bracket) would come
8208
+ * back as `evil.example`, turning a half-named host into a valid `Location`
8209
+ * the client would follow, where the malformed input should have produced
8210
+ * nothing. `[a:b]junk` would silently drop its trailing junk, and `[x:y`
8211
+ * would return a multi-colon value as a host — the very thing the other arm
8212
+ * refuses.
8213
+ *
8214
+ * UNBRACKETED — the text before its single `:`. Two or more colons yields
8215
+ * the empty string: it is not a shape any conforming client sends (Node
8216
+ * brackets — measured in `tests/unit/local/ipv6-host-header.test.ts`), it has
8217
+ * no unambiguous split, and returning the leading fragment would hand a
8218
+ * caller a host that is not the one named. `split(':')[0]` did exactly that —
8219
+ * `fe80::1:8080` came back as `'fe80'`.
8220
+ */
8221
+ function hostFromAuthority(authority) {
8222
+ if (authority.startsWith("[")) {
8223
+ const close = authority.indexOf("]");
8224
+ if (close === -1) return "";
8225
+ const afterBracket = authority.slice(close + 1);
8226
+ if (afterBracket !== "" && !/^:\d+$/.test(afterBracket)) return "";
8227
+ return authority.slice(1, close);
8228
+ }
8229
+ const firstColon = authority.indexOf(":");
8230
+ if (firstColon === -1) return authority;
8231
+ if (authority.includes(":", firstColon + 1)) return "";
8232
+ return authority.slice(0, firstColon);
8233
+ }
8234
+
8105
8235
  //#endregion
8106
8236
  //#region src/local/rie-client.ts
8107
8237
  /**
@@ -8168,7 +8298,7 @@ async function waitForRieReady(host, port, timeoutMs = 5e3) {
8168
8298
  await setTimeout$1(100);
8169
8299
  }
8170
8300
  const tail = lastError instanceof Error ? `: ${lastError.message}` : "";
8171
- throw new Error(`RIE did not become ready on ${host}:${port} within ${timeoutMs}ms${tail}. The container may have exited early — check 'docker logs' output.`);
8301
+ throw new Error(`RIE did not become ready on ${formatAuthority(host, port)} within ${timeoutMs}ms${tail}. The container may have exited early — check 'docker logs' output.`);
8172
8302
  }
8173
8303
  /**
8174
8304
  * Issue a tiny HTTP request to confirm RIE's HTTP listener is up (not
@@ -8180,7 +8310,7 @@ async function httpProbe(host, port, timeoutMs) {
8180
8310
  const controller = new AbortController();
8181
8311
  const timer = setTimeout(() => controller.abort(), timeoutMs);
8182
8312
  try {
8183
- await (await fetch(`http://${host}:${port}/`, {
8313
+ await (await fetch(`http://${formatAuthority(host, port)}/`, {
8184
8314
  method: "POST",
8185
8315
  headers: { "Content-Type": "application/json" },
8186
8316
  body: "{}",
@@ -8222,7 +8352,7 @@ function isTransientNetworkError$3(err) {
8222
8352
  * the timeout in v1, but it's the right ballpark.
8223
8353
  */
8224
8354
  async function invokeRie(host, port, event, timeoutMs) {
8225
- const url = `http://${host}:${port}${INVOKE_PATH}`;
8355
+ const url = `http://${formatAuthority(host, port)}${INVOKE_PATH}`;
8226
8356
  const body = JSON.stringify(event ?? {});
8227
8357
  const controller = new AbortController();
8228
8358
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -8359,7 +8489,7 @@ const STREAM_BODY_MAX_BYTES = 100 * 1024 * 1024;
8359
8489
  * Readable is destroyed with a clear error when the cap trips.
8360
8490
  */
8361
8491
  async function invokeRieStreaming(host, port, event, timeoutMs) {
8362
- const url = `http://${host}:${port}${INVOKE_PATH}`;
8492
+ const url = `http://${formatAuthority(host, port)}${INVOKE_PATH}`;
8363
8493
  const body = JSON.stringify(event ?? {});
8364
8494
  const controller = new AbortController();
8365
8495
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -8747,7 +8877,7 @@ function parseConnectionsPath(url) {
8747
8877
  * Issue #537 item 7.
8748
8878
  */
8749
8879
  function buildMgmtEndpointEnvUrl(host, port, stage) {
8750
- return `http://${host}:${port}/${stage}`;
8880
+ return `http://${formatAuthority(host, port)}/${stage}`;
8751
8881
  }
8752
8882
  /**
8753
8883
  * `decodeURIComponent` throws `URIError` on malformed input
@@ -16352,6 +16482,24 @@ async function writeProfileCredentialsFile(profileName, creds) {
16352
16482
 
16353
16483
  //#endregion
16354
16484
  //#region src/cli/commands/local-start-api.ts
16485
+ /**
16486
+ * The `Server listening on <url> (<label>)` ready banner, terminated with a
16487
+ * newline.
16488
+ *
16489
+ * D8.4 — load-bearing, and the most machine-parsed line in the repo: several
16490
+ * integ fixtures grep the prefix and extract the port from it, and
16491
+ * `cdkl studio` matches it with `SERVE_SPECS.api.readyRe` and then resolves
16492
+ * the captured URL with `new URL(...)` before it will front the serve with a
16493
+ * capture proxy. An authority the WHATWG parser rejects therefore does not
16494
+ * merely read badly — the studio serve is refused (issue #599).
16495
+ *
16496
+ * Exported so that contract has a unit test at the EMITTER. Both banner sites
16497
+ * live inside the long-running boot function, which has no cheap test seam,
16498
+ * so without this the only emitter-side coverage was the source fence.
16499
+ */
16500
+ function formatServerListeningBanner(scheme, host, port, pathSuffix, label) {
16501
+ return `Server listening on ${scheme}://${formatAuthority(host, port)}${pathSuffix} (${label})\n`;
16502
+ }
16355
16503
  async function localStartApiCommand(targets, options, extraStateProviders) {
16356
16504
  const logger = getLogger();
16357
16505
  if (options.verbose) logger.setLevel("debug");
@@ -16660,10 +16808,10 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
16660
16808
  warnUnsupportedRoutes(allRoutes, logger);
16661
16809
  warnSsrfRiskyIntegrations(allRoutes, logger);
16662
16810
  logger.info(`Per-Lambda concurrency: ${perLambdaConcurrency} (override with --per-lambda-concurrency)`);
16663
- for (const { group, server } of servers) process.stdout.write(`Server listening on ${server.scheme}://${server.host}:${server.port} (${group.displayName})\n`);
16811
+ for (const { group, server } of servers) process.stdout.write(formatServerListeningBanner(server.scheme, server.host, server.port, "", group.displayName));
16664
16812
  for (const ws of wsServers) {
16665
16813
  const scheme = ws.server.scheme === "https" ? "wss" : "ws";
16666
- process.stdout.write(`Server listening on ${scheme}://${ws.server.host}:${ws.server.port}${ws.apiPath} (${ws.api.apiLogicalId} (WebSocket API))\n`);
16814
+ process.stdout.write(formatServerListeningBanner(scheme, ws.server.host, ws.server.port, ws.apiPath, `${ws.api.apiLogicalId} (WebSocket API)`));
16667
16815
  }
16668
16816
  process.stdout.write("^C to stop and clean up containers.\n");
16669
16817
  let watcher;
@@ -17781,7 +17929,7 @@ function filterSpecsForGroup(group, allSpecs) {
17781
17929
  */
17782
17930
  function printPerServerRouteTables(servers) {
17783
17931
  for (const { group, server } of servers) {
17784
- process.stdout.write(`\n${group.displayName} (http://${server.host}:${server.port})\n`);
17932
+ process.stdout.write(`\n${group.displayName} (http://${formatAuthority(server.host, server.port)})\n`);
17785
17933
  printRouteTable(group.routes);
17786
17934
  }
17787
17935
  }
@@ -19254,7 +19402,7 @@ async function waitForAgentCorePing(host, port, timeoutMs = 3e4) {
19254
19402
  await setTimeout$1(150);
19255
19403
  }
19256
19404
  const tail = lastDetail ? `: ${lastDetail}` : "";
19257
- throw new Error(`AgentCore agent did not become ready on ${host}:${port} within ${timeoutMs}ms${tail}. The container may have exited early or may not serve GET ${PING_PATH} — check 'docker logs' output.`);
19405
+ throw new Error(`AgentCore agent did not become ready on ${formatAuthority(host, port)} within ${timeoutMs}ms${tail}. The container may have exited early or may not serve GET ${PING_PATH} — check 'docker logs' output.`);
19258
19406
  }
19259
19407
  /**
19260
19408
  * Issue `GET /ping`. Returns the HTTP status on any response, undefined on
@@ -19265,7 +19413,7 @@ async function pingProbe(host, port, timeoutMs) {
19265
19413
  const controller = new AbortController();
19266
19414
  const timer = setTimeout(() => controller.abort(), timeoutMs);
19267
19415
  try {
19268
- const response = await fetch(`http://${host}:${port}${PING_PATH}`, {
19416
+ const response = await fetch(`http://${formatAuthority(host, port)}${PING_PATH}`, {
19269
19417
  method: "GET",
19270
19418
  signal: controller.signal
19271
19419
  });
@@ -19301,7 +19449,7 @@ async function waitForAgentCoreHttpReady(host, port, path, timeoutMs = 3e4) {
19301
19449
  const controller = new AbortController();
19302
19450
  const timer = setTimeout(() => controller.abort(), 1e3);
19303
19451
  try {
19304
- await (await fetch(`http://${host}:${port}${path}`, {
19452
+ await (await fetch(`http://${formatAuthority(host, port)}${path}`, {
19305
19453
  method: "POST",
19306
19454
  headers: { "content-type": "application/json" },
19307
19455
  body: "{}",
@@ -19317,7 +19465,7 @@ async function waitForAgentCoreHttpReady(host, port, path, timeoutMs = 3e4) {
19317
19465
  await setTimeout$1(150);
19318
19466
  }
19319
19467
  const tail = lastDetail ? `: ${lastDetail}` : "";
19320
- throw new Error(`AgentCore agent did not become ready on ${host}:${port} within ${timeoutMs}ms${tail}. The container may have exited early or may not serve POST ${path} — check 'docker logs' output.`);
19468
+ throw new Error(`AgentCore agent did not become ready on ${formatAuthority(host, port)} within ${timeoutMs}ms${tail}. The container may have exited early or may not serve POST ${path} — check 'docker logs' output.`);
19321
19469
  }
19322
19470
  /**
19323
19471
  * `fetch()` failures during container boot manifest as a generic
@@ -19345,7 +19493,7 @@ function isTransientNetworkError$2(err) {
19345
19493
  * a missing sink) is buffered into `raw` and returned verbatim.
19346
19494
  */
19347
19495
  async function invokeAgentCore(host, port, event, options) {
19348
- const url = `http://${host}:${port}${INVOCATIONS_PATH}`;
19496
+ const url = `http://${formatAuthority(host, port)}${INVOCATIONS_PATH}`;
19349
19497
  const body = JSON.stringify(event ?? {});
19350
19498
  const controller = new AbortController();
19351
19499
  const timer = setTimeout(() => controller.abort(), options.timeoutMs);
@@ -19452,7 +19600,7 @@ async function signAgentCoreInvocation(opts) {
19452
19600
  path: opts.path,
19453
19601
  headers: {
19454
19602
  "Content-Type": "application/json",
19455
- Host: `${opts.host}:${opts.port}`,
19603
+ Host: formatAuthority(opts.host, opts.port),
19456
19604
  [AGENTCORE_SESSION_ID_HEADER]: opts.sessionId
19457
19605
  },
19458
19606
  body: opts.body
@@ -19513,7 +19661,7 @@ const PROTOCOL_VERSION_HEADER = "MCP-Protocol-Version";
19513
19661
  */
19514
19662
  async function mcpInvokeOnce(host, port, request, options = {}) {
19515
19663
  const fetchImpl = options.fetchImpl ?? fetch;
19516
- const url = `http://${host}:${port}${MCP_PATH}`;
19664
+ const url = `http://${formatAuthority(host, port)}${MCP_PATH}`;
19517
19665
  const requestTimeoutMs = options.requestTimeoutMs ?? 12e4;
19518
19666
  const sessionId = await initializeWithRetry(fetchImpl, url, options.readyTimeoutMs ?? 3e4);
19519
19667
  await postMcp(fetchImpl, url, {
@@ -19689,7 +19837,7 @@ const A2A_PATH = "/";
19689
19837
  */
19690
19838
  async function a2aInvokeOnce(host, port, request, options = {}) {
19691
19839
  const fetchImpl = options.fetchImpl ?? fetch;
19692
- const url = `http://${host}:${port}${"/"}`;
19840
+ const url = `http://${formatAuthority(host, port)}${"/"}`;
19693
19841
  const requestTimeoutMs = options.requestTimeoutMs ?? 12e4;
19694
19842
  const readyTimeoutMs = options.readyTimeoutMs ?? 3e4;
19695
19843
  const message = await postWithReadyRetry(fetchImpl, url, {
@@ -19801,7 +19949,7 @@ function decodeWsFrame(data) {
19801
19949
  * BridgeAgentCoreWsOptions.onMessage}.
19802
19950
  */
19803
19951
  function bridgeAgentCoreWs(host, port, options) {
19804
- const ws = new (options.webSocketImpl ?? WebSocket)(`ws://${host}:${port}${WS_PATH}`, { headers: {
19952
+ const ws = new (options.webSocketImpl ?? WebSocket)(`ws://${formatAuthority(host, port)}${WS_PATH}`, { headers: {
19805
19953
  [AGENTCORE_SESSION_ID_HEADER]: options.sessionId,
19806
19954
  ...options.authorization && { Authorization: options.authorization }
19807
19955
  } });
@@ -19862,7 +20010,7 @@ function bridgeAgentCoreWs(host, port, options) {
19862
20010
  */
19863
20011
  async function invokeAgentCoreWs(host, port, event, options) {
19864
20012
  const Impl = options.webSocketImpl ?? WebSocket;
19865
- const url = `ws://${host}:${port}${WS_PATH}`;
20013
+ const url = `ws://${formatAuthority(host, port)}${WS_PATH}`;
19866
20014
  const body = JSON.stringify(event ?? {});
19867
20015
  return new Promise((resolve, reject) => {
19868
20016
  const ws = new Impl(url, { headers: {
@@ -22364,7 +22512,7 @@ function buildDockerRunArgs(opts) {
22364
22512
  const hostPort = opts.hostPortOverrides?.[pm.containerPort] ?? declaredHostPort;
22365
22513
  const overridden = hostPort !== declaredHostPort;
22366
22514
  const overrideNote = opts.autoRemappedContainerPorts?.has(pm.containerPort) ?? false ? " (privileged-port auto-remap)" : overridden ? " (--host-port override)" : "";
22367
- getLogger().child("ecs").info(`Container '${container.name}' container port ${pm.containerPort} published on ${containerHost}:${hostPort}${overrideNote}. Reach it at ${containerHost}:${hostPort}.`);
22515
+ getLogger().child("ecs").info(`Container '${container.name}' container port ${pm.containerPort} published on ${formatAuthority(containerHost, hostPort)}${overrideNote}. Reach it at ${formatAuthority(containerHost, hostPort)}.`);
22368
22516
  args.push("-p", `${containerHost}:${hostPort}:${pm.containerPort}/${pm.protocol}`);
22369
22517
  publishedEndpoints.push({
22370
22518
  containerName: container.name,
@@ -24790,7 +24938,7 @@ function buildRedirectLocation(action, req, listenerPort, scheme = "http") {
24790
24938
  const rawHost = req.headers["host"];
24791
24939
  const placeholders = {
24792
24940
  protocol: scheme,
24793
- host: ((Array.isArray(rawHost) ? rawHost[0] : rawHost) ?? "").split(":")[0] ?? "",
24941
+ host: hostFromAuthority((Array.isArray(rawHost) ? rawHost[0] : rawHost) ?? ""),
24794
24942
  port: String(listenerPort),
24795
24943
  path: reqPath.replace(/^\//, ""),
24796
24944
  query: reqQuery
@@ -24801,7 +24949,7 @@ function buildRedirectLocation(action, req, listenerPort, scheme = "http") {
24801
24949
  const port = action.port ? fill(action.port) : placeholders["port"];
24802
24950
  const path = fill(action.path ?? "/#{path}");
24803
24951
  const query = fill(action.query ?? "#{query}");
24804
- return `${protocol}://${protocol === "http" && port === "80" || protocol === "https" && port === "443" || port === "" ? host : `${host}:${port}`}${path.startsWith("/") ? path : `/${path}`}${query ? `?${query}` : ""}`;
24952
+ return `${protocol}://${protocol === "http" && port === "80" || protocol === "https" && port === "443" || port === "" ? formatHostForAuthority(host) : formatAuthority(host, port)}${path.startsWith("/") ? path : `/${path}`}${query ? `?${query}` : ""}`;
24805
24953
  }
24806
24954
  /** Synthesize an ALB-style fixed-response. */
24807
24955
  function writeFixedResponse(res, action) {
@@ -27462,7 +27610,7 @@ async function buildFrontDoor(plan, options, logger, extraStateProviders) {
27462
27610
  } else throw bindErr;
27463
27611
  }
27464
27612
  servers.push(server);
27465
- logger.info(`ALB front-door: ${server.scheme}://${server.host}:${server.port} (listener port ${listener.listenerPort})`);
27613
+ logger.info(`ALB front-door: ${server.scheme}://${formatAuthority(server.host, server.port)} (listener port ${listener.listenerPort})`);
27466
27614
  if (degradedHttps) logger.warn(`listener port ${listener.listenerPort} is HTTPS in the cloud but serving HTTP locally (X-Forwarded-Proto: https preserved). Pass --tls to terminate TLS locally with a self-signed or user-supplied cert.`);
27467
27615
  if (listener.defaultAction) logger.info(` default -> ${describeAction(listener.defaultAction)}`);
27468
27616
  for (const r of [...listener.rules].sort((a, b) => a.priority - b.priority)) logger.info(` ${describeConditions(r)} (priority ${r.priority}) -> ${describeAction(r.action)}`);
@@ -27724,12 +27872,12 @@ function logEndpointsBanner(perTarget, frontDoorServers, logger) {
27724
27872
  for (const ep of endpoints) {
27725
27873
  const scheme = ep.protocol.toLowerCase() === "udp" ? "udp" : "http";
27726
27874
  const override = ep.overridden ? " (--host-port override)" : "";
27727
- lines.push(` ${ep.containerName} container port ${ep.containerPort}/${ep.protocol} -> ${scheme}://${ep.host}:${ep.hostPort}${override}`);
27875
+ lines.push(` ${ep.containerName} container port ${ep.containerPort}/${ep.protocol} -> ${scheme}://${formatAuthority(ep.host, ep.hostPort)}${override}`);
27728
27876
  }
27729
27877
  }
27730
27878
  if (frontDoorServers.length > 0) {
27731
27879
  lines.push(" ALB front-door");
27732
- for (const s of frontDoorServers) lines.push(` ${s.scheme}://${s.host}:${s.port}`);
27880
+ for (const s of frontDoorServers) lines.push(` ${s.scheme}://${formatAuthority(s.host, s.port)}`);
27733
27881
  }
27734
27882
  if (lines.length === 0) return;
27735
27883
  logger.info("Service endpoints:");
@@ -30607,7 +30755,7 @@ async function startCloudFrontServer(options) {
30607
30755
  }, handler) : createServer$1(handler);
30608
30756
  const port = await listen(server, options.host, options.port);
30609
30757
  return {
30610
- url: `${scheme}://${options.host}:${port}`,
30758
+ url: `${scheme}://${formatAuthority(options.host, port)}`,
30611
30759
  port,
30612
30760
  scheme,
30613
30761
  update(distribution) {
@@ -31884,7 +32032,7 @@ function startAgentCoreWsBridge(config) {
31884
32032
  httpServer.on("error", (err) => getLogger().debug(`agentcore-ws bridge server error: ${err.message}`));
31885
32033
  const port = httpServer.address().port;
31886
32034
  resolve({
31887
- url: `ws://${host}:${port}${attached.path}`,
32035
+ url: `ws://${formatAuthority(host, port)}${attached.path}`,
31888
32036
  port,
31889
32037
  close: () => new Promise((res) => {
31890
32038
  attached.close().then(() => httpServer.close(() => res()));
@@ -32057,8 +32205,8 @@ function startAgentCoreHttpServer(config) {
32057
32205
  httpServer.on("error", (err) => getLogger().debug(`agentcore http serve server error: ${err.message}`));
32058
32206
  const port = httpServer.address().port;
32059
32207
  resolve({
32060
- httpUrl: `http://${host}:${port}`,
32061
- ...bridge && { wsUrl: `ws://${host}:${port}${bridge.path}` },
32208
+ httpUrl: `http://${formatAuthority(host, port)}`,
32209
+ ...bridge && { wsUrl: `ws://${formatAuthority(host, port)}${bridge.path}` },
32062
32210
  port,
32063
32211
  setContainerPort: (newPort) => {
32064
32212
  config.containerPort = newPort;
@@ -36256,7 +36404,7 @@ async function startStudioServer(options) {
36256
36404
  const server = createServer$1((req, res) => handleRequest(req, res, options.bus, html, () => targetsJson, options, instanceId));
36257
36405
  const boundPort = await listenWithBump(server, host, options.port, maxBump);
36258
36406
  return {
36259
- url: `http://${host}:${boundPort}`,
36407
+ url: `http://${formatAuthority(host, boundPort)}`,
36260
36408
  port: boundPort,
36261
36409
  close: () => new Promise((resolveClose, reject) => {
36262
36410
  server.close((err) => err ? reject(err) : resolveClose());
@@ -37271,7 +37419,7 @@ function startStudioProxy(config) {
37271
37419
  server.removeListener("error", reject);
37272
37420
  const port = server.address().port;
37273
37421
  resolve({
37274
- url: `http://${host}:${port}`,
37422
+ url: `http://${formatAuthority(host, port)}`,
37275
37423
  port,
37276
37424
  close: () => new Promise((resolveClose, rejectClose) => {
37277
37425
  for (const sock of upgradeSockets) sock.destroy();
@@ -37413,6 +37561,12 @@ const SERVE_SPECS = {
37413
37561
  }
37414
37562
  };
37415
37563
  /**
37564
+ * The `ecs-task-runner` replica publish banner, with its authority captured.
37565
+ * The authority is a dotted-quad IPv4 or a bracketed IPv6 literal, each
37566
+ * followed by `:<port>`.
37567
+ */
37568
+ const PUBLISHED_ENDPOINT_RE = /^Container '[^']*' container port \d+ published on ((?:\d{1,3}(?:\.\d{1,3}){3}|\[[0-9A-Fa-f:.]{2,45}\]):\d+)/;
37569
+ /**
37416
37570
  * Parse an auto-published replica host endpoint from an `ecs` serve child's
37417
37571
  * stdout (issue #392). `start-service` publishes each replica's declared
37418
37572
  * container port on the host — auto-remapping a privileged port (< 1024) to a
@@ -37432,9 +37586,17 @@ const SERVE_SPECS = {
37432
37586
  * The loopback bound is applied by the CALLER (a parsed endpoint is still only
37433
37587
  * adopted when {@link normalizeLocalUpstream} accepts it), so this stays a pure
37434
37588
  * reader of the banner.
37589
+ *
37590
+ * The authority half accepts a BRACKETED IPv6 literal alongside the dotted
37591
+ * quad (issue go-to-k/cdk-local#599). `ecs-task-runner` composes that banner
37592
+ * through `formatAuthority`, so `--container-host ::1` prints
37593
+ * `[::1]:54321` — an emitter that brackets and a reader that only knows IPv4
37594
+ * is the same defect moved one file over. Brackets are REQUIRED here rather
37595
+ * than optional: a bare `::1:54321` has no unambiguous split into host and
37596
+ * port, and it is not what the emitter writes.
37435
37597
  */
37436
37598
  function parsePublishedHostEndpoint(line) {
37437
- const m = /^Container '[^']*' container port \d+ published on (\d{1,3}(?:\.\d{1,3}){3}:\d+)/.exec(line);
37599
+ const m = PUBLISHED_ENDPOINT_RE.exec(line);
37438
37600
  return m ? `http://${m[1]}` : void 0;
37439
37601
  }
37440
37602
  /**
@@ -38836,4 +38998,4 @@ function addStudioSpecificOptions(cmd) {
38836
38998
 
38837
38999
  //#endregion
38838
39000
  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, 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, 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 };
38839
- //# sourceMappingURL=local-studio-BJxBmC75.js.map
39001
+ //# sourceMappingURL=local-studio-BIHN-Erp.js.map