cdk-local 0.148.3 → 0.149.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +1 -1
- package/dist/{local-studio-BVxRUfQV.js → local-studio-CP1PAUS4.js} +920 -59
- package/dist/local-studio-CP1PAUS4.js.map +1 -0
- package/dist/{local-studio-9bZLFQqv.d.ts → local-studio-D-j3TZWX.d.ts} +2 -1
- package/dist/local-studio-D-j3TZWX.d.ts.map +1 -0
- package/package.json +6 -10
- package/dist/local-studio-9bZLFQqv.d.ts.map +0 -1
- package/dist/local-studio-BVxRUfQV.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as runDockerStreaming, c as resolveConfiguredLogLevel, d as setEmbedConfig, i as runDockerForeground, l as getEmbedConfig, n as formatDockerLoginError, o as spawnStreaming, r as getDockerCmd, s as getLogger } from "./docker-cmd-DLX1TqVw.js";
|
|
2
|
-
import { chmodSync, cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
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";
|
|
5
|
-
import path, { dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
5
|
+
import path, { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { Command, Option } from "commander";
|
|
7
7
|
import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
8
8
|
import { Agent, createServer, request } from "node:http";
|
|
@@ -203,6 +203,12 @@ function hostFromAuthority(authority) {
|
|
|
203
203
|
* so `proxyAwareFetch()` is the second seam: same `NO_PROXY` decision, same
|
|
204
204
|
* "empty when unconfigured" contract, fenced by
|
|
205
205
|
* `tests/unit/utils/aws-proxy-fetch-audit.test.ts`.
|
|
206
|
+
*
|
|
207
|
+
* Both seams route through ONE routing decision,
|
|
208
|
+
* {@link resolveProxyForTarget} (issue go-to-k/cdk-local#663) -- which is
|
|
209
|
+
* also where a proxy scheme these agents cannot speak is turned into a
|
|
210
|
+
* warned DIRECT connection rather than into an HTTP agent pointed at, for
|
|
211
|
+
* example, a SOCKS port.
|
|
206
212
|
*/
|
|
207
213
|
/**
|
|
208
214
|
* The SDK applies these defaults only when it builds its OWN agents from a
|
|
@@ -227,6 +233,129 @@ function isProxyEnvConfigured() {
|
|
|
227
233
|
return Boolean(env["https_proxy"] || env["HTTPS_PROXY"] || env["http_proxy"] || env["HTTP_PROXY"] || env["all_proxy"] || env["ALL_PROXY"]);
|
|
228
234
|
}
|
|
229
235
|
/**
|
|
236
|
+
* Proxy SCHEMES already warned about, so a decision taken per REQUEST does
|
|
237
|
+
* not print a line per AWS call. Keyed by scheme rather than by URL because
|
|
238
|
+
* the message names only the scheme — two SOCKS proxies would otherwise
|
|
239
|
+
* print the same sentence twice.
|
|
240
|
+
*/
|
|
241
|
+
const warnedProxySchemes = /* @__PURE__ */ new Set();
|
|
242
|
+
/**
|
|
243
|
+
* The scheme of a proxy URL, lowercased and without its `://`.
|
|
244
|
+
*
|
|
245
|
+
* `getProxyForUrl` prepends the REQUEST's scheme to any value that does not
|
|
246
|
+
* already contain `://`, so the URL it returns normally carries one — even
|
|
247
|
+
* `HTTPS_PROXY=proxy.internal:3128`, which becomes `https://proxy.internal:3128`
|
|
248
|
+
* and therefore makes the connection TO THE PROXY ITSELF TLS. (Both spellings
|
|
249
|
+
* still tunnel with `CONNECT`; what changes is whether that tunnel is wrapped
|
|
250
|
+
* in TLS to the proxy. Read from `https-proxy-agent`: an `https:` proxy is
|
|
251
|
+
* reached with `tls.connect` and the `CONNECT` line is written over it.) A
|
|
252
|
+
* value containing `://` somewhere OTHER than the start is returned verbatim
|
|
253
|
+
* and has no leading scheme; pathological, but reachable, so it gets a NAME
|
|
254
|
+
* rather than an empty string that would read as a missing message.
|
|
255
|
+
*
|
|
256
|
+
* The `:\/\/` is load-bearing and NOT decoration. Matching a bare `token:`
|
|
257
|
+
* would name whatever precedes the first colon, and in a verbatim value that
|
|
258
|
+
* is routinely the PROXY USERNAME. Measured:
|
|
259
|
+
* `HTTPS_PROXY=corp-user:s3cr3t@http://proxy.corp:3128` contains `://`, so it
|
|
260
|
+
* comes back untouched — and a `^([a-z][a-z0-9+.-]*):` reading of it yields
|
|
261
|
+
* `corp-user`, which {@link warnUnspeakableProxy} would then print at default
|
|
262
|
+
* level into a log panel `cdkl studio` serves over HTTP. Requiring `://`
|
|
263
|
+
* sends that shape to `(unrecognized)`.
|
|
264
|
+
*
|
|
265
|
+
* It does NOT make "a username can never be printed" true, and the bound is
|
|
266
|
+
* stated rather than left to be rediscovered: `alice://s3cr3t@proxy:3128` is
|
|
267
|
+
* a syntactically valid URL whose scheme IS `alice`, so `alice` is named.
|
|
268
|
+
* That is the URL grammar answering correctly — nothing here can know the
|
|
269
|
+
* author meant it as a username — and it is not a working proxy
|
|
270
|
+
* configuration in any case. Pinned by a case so a future reader finds a
|
|
271
|
+
* decision instead of an oversight.
|
|
272
|
+
*
|
|
273
|
+
* The length bound is the other half: without it the scheme run is
|
|
274
|
+
* unbounded, so a proxy value of 100 kB of `a` followed by `://` becomes a
|
|
275
|
+
* 100 kB default-level warn. 32 is far past every real scheme, and it never
|
|
276
|
+
* affects ROUTING — {@link isSpeakableProxy} is a separate unbounded test, so
|
|
277
|
+
* a 40-character scheme is unspeakable either way.
|
|
278
|
+
*/
|
|
279
|
+
function proxySchemeOf(proxyUrl) {
|
|
280
|
+
const match = /^([a-z][a-z0-9+.-]{0,31}):\/\//i.exec(proxyUrl);
|
|
281
|
+
return match ? match[1].toLowerCase() : "(unrecognized)";
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Whether `proxyUrl` names a proxy these agents can actually SPEAK to.
|
|
285
|
+
*
|
|
286
|
+
* `getProxyForUrl` honours `ALL_PROXY`, and `ALL_PROXY=socks5://...` is an
|
|
287
|
+
* ordinary spelling — but `http-proxy-agent` / `https-proxy-agent` speak HTTP
|
|
288
|
+
* `CONNECT`, not SOCKS. Measured: `new HttpsProxyAgent('socks5://127.0.0.1:1080')`
|
|
289
|
+
* constructs happily and reports `proxy.protocol === 'socks5:'`, then talks
|
|
290
|
+
* HTTP at a SOCKS port, so every request through it fails.
|
|
291
|
+
*/
|
|
292
|
+
function isSpeakableProxy(proxyUrl) {
|
|
293
|
+
return /^https?:\/\//i.test(proxyUrl);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Say ONCE per scheme that a proxy variable is being ignored.
|
|
297
|
+
*
|
|
298
|
+
* The URL is withheld and only the SCHEME is named: a proxy URL routinely
|
|
299
|
+
* carries `user:password@`, and this line is emitted at DEFAULT level
|
|
300
|
+
* somewhere a third party reads it — `cdkl studio` mirrors a serve child's
|
|
301
|
+
* output into the log panel it serves over HTTP. Same rule `parseHttpUrl`'s
|
|
302
|
+
* rejection follows, for the same reason.
|
|
303
|
+
*/
|
|
304
|
+
function warnUnspeakableProxy(proxyUrl) {
|
|
305
|
+
const scheme = proxySchemeOf(proxyUrl);
|
|
306
|
+
if (warnedProxySchemes.has(scheme)) return;
|
|
307
|
+
try {
|
|
308
|
+
getLogger().child("aws-proxy").warn(`Unsupported proxy scheme "${scheme}" — cdk-local speaks HTTP CONNECT proxies only, so this request went DIRECT instead of through the proxy. Set HTTPS_PROXY / HTTP_PROXY / ALL_PROXY to an http:// or https:// proxy URL. (The proxy URL is withheld here: it can carry credentials.)`);
|
|
309
|
+
} catch {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
warnedProxySchemes.add(scheme);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* The proxy to route `targetHref` through, or `''` for a DIRECT connection.
|
|
316
|
+
*
|
|
317
|
+
* ONE site owns the question, so the SDK-client half
|
|
318
|
+
* ({@link EnvRoutingProxyAgent}) and the fetch half ({@link proxyAwareFetch})
|
|
319
|
+
* cannot answer it differently for the same environment — they did until
|
|
320
|
+
* issue go-to-k/cdk-local#663: PR 656 taught the fetch half to fall back on
|
|
321
|
+
* an unspeakable proxy, while the agent PR 646 built kept constructing an
|
|
322
|
+
* HTTP agent pointed at a SOCKS port.
|
|
323
|
+
*
|
|
324
|
+
* Falling back to DIRECT rather than REFUSING is the deliberate call, and it
|
|
325
|
+
* is now the same call on both halves:
|
|
326
|
+
*
|
|
327
|
+
* - Before either seam existed every one of these requests went direct, so a
|
|
328
|
+
* developer who exports `ALL_PROXY=socks5://...` for an unrelated tunnel
|
|
329
|
+
* AND has working direct egress had cdk-local working; PR 646 broke that.
|
|
330
|
+
* Refusing keeps them broken with no configuration to reach — cdk-local
|
|
331
|
+
* speaks no SOCKS, so the only remedy would be unsetting the variable.
|
|
332
|
+
* - On the JWKS / OIDC discovery path a FAILED read does not deny: the
|
|
333
|
+
* verifier caches pass-through and accepts every bearer token for the
|
|
334
|
+
* failure TTL (`docs/cli-reference.md`, "JWKS / OIDC discovery
|
|
335
|
+
* unreachable"). Attempting and failing there is strictly worse than going
|
|
336
|
+
* direct.
|
|
337
|
+
* - The one argument for refusing was that a fallback is SILENT, and
|
|
338
|
+
* {@link warnUnspeakableProxy} removes it: a user whose direct egress is
|
|
339
|
+
* blocked still gets a line naming the cause, instead of an `ETIMEDOUT`
|
|
340
|
+
* that points at nothing.
|
|
341
|
+
*
|
|
342
|
+
* An unparsable proxy value whose scheme IS `http(s):` is a DIFFERENT
|
|
343
|
+
* question and still throws from the agent constructor, as before
|
|
344
|
+
* (`HTTPS_PROXY=http://[`): a typo has no working setup behind it, so going
|
|
345
|
+
* direct would hide it rather than restore anything. A value whose scheme is
|
|
346
|
+
* NEITHER — garbage that merely contains `://` — is refused here first and
|
|
347
|
+
* gets the warn instead of the throw. That is the same trade either way: the
|
|
348
|
+
* cause stays attached to the request, which is the property the throw
|
|
349
|
+
* existed for.
|
|
350
|
+
*/
|
|
351
|
+
function resolveProxyForTarget(targetHref) {
|
|
352
|
+
const proxyUrl = getProxyForUrl(targetHref).trim();
|
|
353
|
+
if (proxyUrl === "") return "";
|
|
354
|
+
if (isSpeakableProxy(proxyUrl)) return proxyUrl;
|
|
355
|
+
warnUnspeakableProxy(proxyUrl);
|
|
356
|
+
return "";
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
230
359
|
* An `agent-base` Agent that decides PER REQUEST whether to tunnel through
|
|
231
360
|
* the configured proxy or connect directly. `NodeHttpHandler` picks its agent
|
|
232
361
|
* by protocol alone, and `https-proxy-agent` does not consult `NO_PROXY` —
|
|
@@ -254,7 +383,7 @@ var EnvRoutingProxyAgent = class extends Agent$2 {
|
|
|
254
383
|
const secure = options.secureEndpoint;
|
|
255
384
|
const host = options.host ?? "localhost";
|
|
256
385
|
const port = options.port || (secure ? 443 : 80);
|
|
257
|
-
const proxyUrl =
|
|
386
|
+
const proxyUrl = resolveProxyForTarget(`${secure ? "https" : "http"}://${formatAuthority(host, port)}`);
|
|
258
387
|
if (!proxyUrl) return secure ? this.directHttpsAgent : this.directHttpAgent;
|
|
259
388
|
const key = `${secure ? "https" : "http"}|${proxyUrl}`;
|
|
260
389
|
let agent = this.proxyAgents.get(key);
|
|
@@ -397,30 +526,6 @@ function isRedirectStatus(status) {
|
|
|
397
526
|
* plausibly does reach those, so exempting them would be a guess rather
|
|
398
527
|
* than an impossibility. `NO_PROXY` remains the control for that case.
|
|
399
528
|
*/
|
|
400
|
-
/**
|
|
401
|
-
* Whether `proxyUrl` names a proxy these agents can actually SPEAK to.
|
|
402
|
-
*
|
|
403
|
-
* `getProxyForUrl` honours `ALL_PROXY`, and `ALL_PROXY=socks5://...` is an
|
|
404
|
-
* ordinary spelling — but `http-proxy-agent` / `https-proxy-agent` speak HTTP
|
|
405
|
-
* `CONNECT`, not SOCKS. Measured: `new HttpsProxyAgent('socks5://127.0.0.1:1080')`
|
|
406
|
-
* constructs happily and then talks HTTP at a SOCKS port, so every request
|
|
407
|
-
* through it fails.
|
|
408
|
-
*
|
|
409
|
-
* Falling back to a DIRECT request is the right answer rather than a
|
|
410
|
-
* best-effort attempt, for a reason specific to this seam: before issue #647
|
|
411
|
-
* these reads went direct through undici and WORKED for a SOCKS user, and an
|
|
412
|
-
* unreachable JWKS does not deny requests — it caches `passThrough` and
|
|
413
|
-
* accepts every token for the failure TTL. Trying and failing would turn a
|
|
414
|
-
* working setup into a silent auth downgrade, which is the outcome the
|
|
415
|
-
* loopback rule below exists to prevent.
|
|
416
|
-
*
|
|
417
|
-
* The SDK-client half of this — `EnvRoutingProxyAgent`, which PR 646 built
|
|
418
|
-
* with the same blind spot, and whose failures are at least loud — is tracked
|
|
419
|
-
* in https://github.com/go-to-k/cdk-local/issues/663.
|
|
420
|
-
*/
|
|
421
|
-
function isSpeakableProxy(proxyUrl) {
|
|
422
|
-
return /^https?:\/\//i.test(proxyUrl);
|
|
423
|
-
}
|
|
424
529
|
function isLoopbackHost(hostname) {
|
|
425
530
|
const host = hostname.trim().replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
|
|
426
531
|
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
@@ -605,7 +710,7 @@ async function proxyAwareFetch(url, opts = {}) {
|
|
|
605
710
|
if (!isProxyEnvConfigured()) return globalThis.fetch(url);
|
|
606
711
|
let target = parseHttpUrl(url);
|
|
607
712
|
for (let hop = 0;; hop++) {
|
|
608
|
-
if (isLoopbackHost(target.hostname) ||
|
|
713
|
+
if (isLoopbackHost(target.hostname) || resolveProxyForTarget(target.href) === "") return globalThis.fetch(target.href);
|
|
609
714
|
const agent = new EnvRoutingProxyAgent();
|
|
610
715
|
let raw;
|
|
611
716
|
try {
|
|
@@ -2278,6 +2383,247 @@ function stringifyValue(value) {
|
|
|
2278
2383
|
}
|
|
2279
2384
|
}
|
|
2280
2385
|
|
|
2386
|
+
//#endregion
|
|
2387
|
+
//#region src/utils/assembly-path.ts
|
|
2388
|
+
/**
|
|
2389
|
+
* `true` when `candidate` names something strictly beneath `base`, both given
|
|
2390
|
+
* as already-resolved absolute paths.
|
|
2391
|
+
*
|
|
2392
|
+
* The `..` test is SEPARATOR-AWARE rather than a bare `startsWith('..')`,
|
|
2393
|
+
* which would also reject a legitimate sibling named `..foo`. The empty-string
|
|
2394
|
+
* case is `base` itself; callers decide whether that counts as an escape,
|
|
2395
|
+
* because an asset path legitimately names a DIRECTORY while a template path
|
|
2396
|
+
* does not.
|
|
2397
|
+
*/
|
|
2398
|
+
function isInside(base, candidate) {
|
|
2399
|
+
const rel = relative(base, candidate);
|
|
2400
|
+
if (rel === "" || rel === "..") return false;
|
|
2401
|
+
if (rel.startsWith(`..${sep}`)) return false;
|
|
2402
|
+
return !isAbsolute(rel);
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* `realpath(3)`, or `undefined` for a path that does not fully resolve.
|
|
2406
|
+
*
|
|
2407
|
+
* `.native` is load-bearing and must not be simplified to `fs.realpathSync`.
|
|
2408
|
+
* The plain form is a JavaScript walker that folds `..` LEXICALLY, so it
|
|
2409
|
+
* disagrees with the kernel on a target carrying a `..` after a symlinked
|
|
2410
|
+
* component — answering `ENOENT` for a path the kernel resolves, and on a
|
|
2411
|
+
* case-insensitive filesystem (APFS by default) it can spin on the same shape.
|
|
2412
|
+
* `.native` is libuv's `realpath(3)` and throws the same
|
|
2413
|
+
* `ENOENT` / `ELOOP` / `EACCES`.
|
|
2414
|
+
*/
|
|
2415
|
+
function tryRealpath(p) {
|
|
2416
|
+
try {
|
|
2417
|
+
return realpathSync.native(p);
|
|
2418
|
+
} catch {
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
/** `fs.readlinkSync`, or `undefined` when `p` is not a symbolic link. */
|
|
2423
|
+
function tryReadlink(p) {
|
|
2424
|
+
try {
|
|
2425
|
+
return readlinkSync(p);
|
|
2426
|
+
} catch {
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Link follows before the walk gives up and reports the path as unresolvable.
|
|
2432
|
+
* It does NOT refuse: an exhausted budget answers `undefined`, which leaves
|
|
2433
|
+
* the symlink arm silent and the lexical verdict standing. Safe because the OS
|
|
2434
|
+
* gives up first (macOS caps at 32, `ELOOP`), so a chain that reaches this cap
|
|
2435
|
+
* is one nothing can open anyway.
|
|
2436
|
+
*/
|
|
2437
|
+
const MAX_LINK_HOPS = 40;
|
|
2438
|
+
/**
|
|
2439
|
+
* Unresolvable path COMPONENTS the climb walks before giving up. Bounds the
|
|
2440
|
+
* recursion below, which is one frame per component, so a pathological value
|
|
2441
|
+
* cannot raise a `RangeError` from inside {@link tryRealpath}'s own `try` (its
|
|
2442
|
+
* `catch` would swallow the crash into a silent `undefined`).
|
|
2443
|
+
*
|
|
2444
|
+
* KNOW THAT EXHAUSTING THIS FAILS OPEN, and why that is accepted rather than
|
|
2445
|
+
* unnoticed. Unlike {@link MAX_LINK_HOPS}, which has the OS's own `ELOOP` cap
|
|
2446
|
+
* behind it, nothing else stops a path of 1 001 absent components: the climb
|
|
2447
|
+
* gives up, the symlink arm goes silent, and a value that would land outside
|
|
2448
|
+
* reads as CONTAINED on the lexical verdict alone. It is not reachable in
|
|
2449
|
+
* effect — such a path cannot exist, so `cdkl invoke`'s `existsSync` refuses it
|
|
2450
|
+
* and `start-api`'s `docker run` fails to mount it — but that safety lives in
|
|
2451
|
+
* the CONSUMERS, not here. A consumer that neither checks existence nor mounts
|
|
2452
|
+
* would need its own answer.
|
|
2453
|
+
*/
|
|
2454
|
+
const MAX_PATH_COMPONENTS = 1e3;
|
|
2455
|
+
/**
|
|
2456
|
+
* Where `target` REALLY points, for a path that may not exist yet.
|
|
2457
|
+
*
|
|
2458
|
+
* `realpathSync` answers only for a path that fully resolves, and it throws
|
|
2459
|
+
* `ENOENT` for a DANGLING symbolic link exactly as it does for an absent file.
|
|
2460
|
+
* `cdkl start-api` resolves an asset path without an existence check, so an
|
|
2461
|
+
* absent or dangling component must not silence the symlink arm.
|
|
2462
|
+
*
|
|
2463
|
+
* Each unresolvable component is therefore handled by hand: climb to the
|
|
2464
|
+
* deepest ancestor that DOES resolve, then re-apply the remaining components,
|
|
2465
|
+
* following any symbolic link with `readlink` and re-resolving its target.
|
|
2466
|
+
* `undefined` means the walk could not resolve the path at all.
|
|
2467
|
+
*
|
|
2468
|
+
* For a path that FULLY RESOLVES the answer is the kernel's and is exact. For
|
|
2469
|
+
* one that does not exist yet this is a best-effort MODEL of kernel
|
|
2470
|
+
* resolution; the known edge is a `..` INSIDE an unresolvable link's target,
|
|
2471
|
+
* folded lexically here while the kernel folds it only after following each
|
|
2472
|
+
* preceding component. That edge is benign for THESE callers rather than in
|
|
2473
|
+
* general: it needs the target to be absent, and both consumers resolve the
|
|
2474
|
+
* path and then open or mount it, so the divergence is between this model and
|
|
2475
|
+
* a path nothing can reach. A caller that CREATED a file through such a path
|
|
2476
|
+
* would need an `lstat` of its own.
|
|
2477
|
+
*
|
|
2478
|
+
* `EACCES` is folded into "does not resolve" along with `ENOENT`, so a link
|
|
2479
|
+
* under a directory this process cannot traverse reads as contained. Same
|
|
2480
|
+
* bound: the consumer's own `existsSync` / mount runs as the same user and
|
|
2481
|
+
* fails identically.
|
|
2482
|
+
*/
|
|
2483
|
+
function resolveThroughLinks(target, hops = 0, climbs = 0) {
|
|
2484
|
+
const direct = tryRealpath(target);
|
|
2485
|
+
if (direct !== void 0) return direct;
|
|
2486
|
+
if (hops >= MAX_LINK_HOPS) return void 0;
|
|
2487
|
+
if (climbs >= MAX_PATH_COMPONENTS) return void 0;
|
|
2488
|
+
const parent = dirname(target);
|
|
2489
|
+
if (parent === target) return void 0;
|
|
2490
|
+
const realParent = resolveThroughLinks(parent, hops, climbs + 1);
|
|
2491
|
+
if (realParent === void 0) return void 0;
|
|
2492
|
+
const link = tryReadlink(target);
|
|
2493
|
+
if (link === void 0) return join(realParent, basename(target));
|
|
2494
|
+
return resolveThroughLinks(resolve(realParent, link), hops + 1, climbs);
|
|
2495
|
+
}
|
|
2496
|
+
/**
|
|
2497
|
+
* Resolve an assembly-supplied `candidate` against `dir` and report whether
|
|
2498
|
+
* the result stays inside it.
|
|
2499
|
+
*
|
|
2500
|
+
* The lexical arm joins exactly the way the call sites used to (`path.join`,
|
|
2501
|
+
* NOT `path.resolve`), so the verdict is about the path the caller will
|
|
2502
|
+
* actually use. `join`'s handling of an absolute candidate makes this arm
|
|
2503
|
+
* strictly more permissive than a `resolve`-based one would be; an absolute
|
|
2504
|
+
* value is therefore answered by {@link absoluteAssemblyPathEscape} instead,
|
|
2505
|
+
* because this function structurally cannot.
|
|
2506
|
+
*
|
|
2507
|
+
* The SYMLINK arm exists because the lexical arm alone leaves an equivalent
|
|
2508
|
+
* hole: `cdk.out/link -> /etc` plus a candidate of `link/passwd` is lexically
|
|
2509
|
+
* contained and still reaches `/etc/passwd`. Both sides go through
|
|
2510
|
+
* {@link resolveThroughLinks}, so an assembly directory REACHED through a link
|
|
2511
|
+
* (macOS spells `/tmp` as `/private/tmp`) is unaffected.
|
|
2512
|
+
*
|
|
2513
|
+
* The verdict is about the assembly AS IT SITS ON DISK. It is NOT a defence
|
|
2514
|
+
* against a process rewriting that directory concurrently — the caller uses
|
|
2515
|
+
* the path after this returns.
|
|
2516
|
+
*/
|
|
2517
|
+
function resolveAssemblyPath(dir, candidate, options) {
|
|
2518
|
+
const base = resolve(dir);
|
|
2519
|
+
const bound = options?.containWithin === void 0 ? base : resolve(options.containWithin);
|
|
2520
|
+
const joined = resolve(join(base, candidate));
|
|
2521
|
+
if (!isInside(bound, joined)) return {
|
|
2522
|
+
contained: false,
|
|
2523
|
+
escape: "lexical",
|
|
2524
|
+
path: joined
|
|
2525
|
+
};
|
|
2526
|
+
const realBound = resolveThroughLinks(bound);
|
|
2527
|
+
if (realBound !== void 0) {
|
|
2528
|
+
const realTarget = resolveThroughLinks(joined);
|
|
2529
|
+
if (realTarget !== void 0 && !isInside(realBound, realTarget)) return {
|
|
2530
|
+
contained: false,
|
|
2531
|
+
escape: "symlink",
|
|
2532
|
+
path: joined,
|
|
2533
|
+
realPath: realTarget
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
return {
|
|
2537
|
+
contained: true,
|
|
2538
|
+
path: joined
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Whether an ALREADY-ABSOLUTE assembly-supplied path lies outside `bound`.
|
|
2543
|
+
*
|
|
2544
|
+
* {@link resolveAssemblyPath} cannot answer this, and the reason is structural
|
|
2545
|
+
* rather than an oversight: its lexical arm joins with `path.join`, which does
|
|
2546
|
+
* NOT honour a leading separator, so an absolute candidate is folded INTO the
|
|
2547
|
+
* directory and the verdict would describe a path no caller opens. A site that
|
|
2548
|
+
* HONOURS an absolute value needs the verdict about the value itself.
|
|
2549
|
+
*
|
|
2550
|
+
* The callers are `cdkl invoke`'s and `cdkl start-api`'s
|
|
2551
|
+
* `Metadata['aws:asset:path']` resolution. Those honour an absolute path
|
|
2552
|
+
* because `cdk synth --no-staging` emits one — CDK writes the asset's absolute
|
|
2553
|
+
* SOURCE directory under `aws:cdk:disable-asset-staging`, usually outside the
|
|
2554
|
+
* outdir — and they WARN rather than refuse when it leaves the bound, so this
|
|
2555
|
+
* returns a verdict rather than throwing.
|
|
2556
|
+
*
|
|
2557
|
+
* It lives HERE so the containment rule has one spelling: it reuses this
|
|
2558
|
+
* module's own {@link isInside} and {@link resolveThroughLinks}, symlink arm
|
|
2559
|
+
* included, rather than letting a caller re-spell `path.relative` and drift.
|
|
2560
|
+
*
|
|
2561
|
+
* `bound` itself is NOT an escape, unlike in {@link resolveAssemblyPath},
|
|
2562
|
+
* where an empty `path.relative` means "names the directory rather than a file
|
|
2563
|
+
* inside it". An asset path legitimately names a DIRECTORY, so a value equal
|
|
2564
|
+
* to the bound is inside it and reporting it as outside would be false.
|
|
2565
|
+
*
|
|
2566
|
+
* THE REAL PATHS DECIDE IN BOTH DIRECTIONS, which is the one place this
|
|
2567
|
+
* deliberately does more than {@link resolveAssemblyPath}'s lexical-first
|
|
2568
|
+
* ordering. The lexical verdict here is about a SPELLING, and two spellings of
|
|
2569
|
+
* the same directory are common rather than exotic: macOS resolves `/tmp` to
|
|
2570
|
+
* `/private/tmp` and `/var` to `/private/var`, and a user may symlink `cdk.out`
|
|
2571
|
+
* itself. With an outdir of `/tmp/cdk.out` and a `--no-staging` value of
|
|
2572
|
+
* `/private/tmp/cdk.out/src`, a lexical-only verdict cries "pointing outside
|
|
2573
|
+
* the assembly ... treat this assembly as untrusted" about an asset that is
|
|
2574
|
+
* plainly inside it — and this arm exists to make an UNEXPECTED path visible,
|
|
2575
|
+
* so a false alarm is the failure that costs it its meaning.
|
|
2576
|
+
*
|
|
2577
|
+
* Exonerating requires the kernel to answer for BOTH operands: an unresolvable
|
|
2578
|
+
* one leaves the lexical verdict standing, so the arm stays loud when it cannot
|
|
2579
|
+
* see. (`resolveAssemblyPath` cannot take the same shape: its verdict is about
|
|
2580
|
+
* a path built with `path.join`, and letting a real path overrule the lexical
|
|
2581
|
+
* `..` there would answer about a location the caller never opens.)
|
|
2582
|
+
*/
|
|
2583
|
+
function absoluteAssemblyPathEscape(bound, absolutePath) {
|
|
2584
|
+
const resolvedBound = resolve(bound);
|
|
2585
|
+
const target = resolve(absolutePath);
|
|
2586
|
+
const lexicallyOutside = !isInside(resolvedBound, target) && target !== resolvedBound;
|
|
2587
|
+
const realBound = resolveThroughLinks(resolvedBound);
|
|
2588
|
+
const realTarget = resolveThroughLinks(target);
|
|
2589
|
+
const reallyOutside = realBound === void 0 || realTarget === void 0 ? void 0 : !isInside(realBound, realTarget) && realTarget !== realBound;
|
|
2590
|
+
if (lexicallyOutside) {
|
|
2591
|
+
if (reallyOutside === false) return void 0;
|
|
2592
|
+
return {
|
|
2593
|
+
contained: false,
|
|
2594
|
+
escape: "lexical",
|
|
2595
|
+
path: target
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
if (reallyOutside === true) return {
|
|
2599
|
+
contained: false,
|
|
2600
|
+
escape: "symlink",
|
|
2601
|
+
path: target,
|
|
2602
|
+
realPath: realTarget
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
/**
|
|
2606
|
+
* The shared tail of a containment refusal: what the value resolved to, what
|
|
2607
|
+
* it escaped, and why that means the assembly is not CDK-generated. The call
|
|
2608
|
+
* site supplies its own subject ("Lambda 'X' has ... which ") and its own
|
|
2609
|
+
* error class. `action` completes "Refusing to ..." and is REQUIRED rather
|
|
2610
|
+
* than defaulted: a default is a branch no caller takes, so it can neither be
|
|
2611
|
+
* fenced nor be right for the next caller.
|
|
2612
|
+
*/
|
|
2613
|
+
function renderAssemblyPathEscape(escape, dir, action) {
|
|
2614
|
+
const provenance = `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}.`;
|
|
2615
|
+
const base = resolve(dir);
|
|
2616
|
+
const realBase = resolveThroughLinks(base) ?? base;
|
|
2617
|
+
const shownPath = sanitizeServiceExceptionMessage(escape.path);
|
|
2618
|
+
const shownBase = sanitizeServiceExceptionMessage(base);
|
|
2619
|
+
if (escape.escape === "symlink") {
|
|
2620
|
+
if (escape.realPath === realBase) return `resolves to '${shownPath}', a symbolic link to the directory '${shownBase}' itself rather than to a path inside it. ${provenance}`;
|
|
2621
|
+
return `resolves to '${shownPath}', which leads through a symbolic link to '${sanitizeServiceExceptionMessage(escape.realPath)}', outside '${shownBase}'. ${provenance}`;
|
|
2622
|
+
}
|
|
2623
|
+
if (escape.path === base) return `names the directory '${shownBase}' itself rather than a path inside it. ${provenance}`;
|
|
2624
|
+
return `resolves to '${shownPath}', outside '${shownBase}'. ${provenance}`;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2281
2627
|
//#endregion
|
|
2282
2628
|
//#region src/local/lambda-resolver.ts
|
|
2283
2629
|
/**
|
|
@@ -2542,6 +2888,325 @@ function extractImageLambdaProperties(args) {
|
|
|
2542
2888
|
};
|
|
2543
2889
|
}
|
|
2544
2890
|
/**
|
|
2891
|
+
* Where a Lambda's `Metadata['aws:asset:path']` really lives on this host
|
|
2892
|
+
* (go-to-k/cdkd#3534, applying the decision taken in go-to-k/cdkd#3494).
|
|
2893
|
+
*
|
|
2894
|
+
* THE one spelling shared by `cdkl invoke`'s resolver and `cdkl start-api`'s,
|
|
2895
|
+
* which each carried their own copy of `isAbsolute(p) ? p : resolve(dir, p)`.
|
|
2896
|
+
* A second hand-written copy is how a guard on one twin becomes a guard on
|
|
2897
|
+
* neither. It throws through the CALLER's `wrapError` so each site keeps its
|
|
2898
|
+
* own error class and names its own command.
|
|
2899
|
+
*
|
|
2900
|
+
* TWO VALUE SHAPES, ANSWERED DIFFERENTLY, and the asymmetry is the decision
|
|
2901
|
+
* rather than an accident:
|
|
2902
|
+
*
|
|
2903
|
+
* - RELATIVE, escaping. REFUSED. `path.resolve` folds `..` exactly as
|
|
2904
|
+
* `path.join` does, so `../../../home/<user>/.aws` leaves the assembly, and
|
|
2905
|
+
* nothing a real `cdk synth` emits has that shape.
|
|
2906
|
+
* - ABSOLUTE. ACCEPTED, with a WARNING naming the path when it leaves the
|
|
2907
|
+
* asset outdir, and SILENCE when it does not.
|
|
2908
|
+
*
|
|
2909
|
+
* **Why absolute is accepted, when the security axis argued for refusing it.**
|
|
2910
|
+
* `cdk synth --no-staging` (context flag `aws:cdk:disable-asset-staging`) makes
|
|
2911
|
+
* upstream `AssetStaging.relativeStagedPath` return the staged path verbatim
|
|
2912
|
+
* instead of relativising it, so `aws:asset:path` is the asset's absolute
|
|
2913
|
+
* SOURCE directory, normally outside the outdir. Refusing it therefore rejects
|
|
2914
|
+
* the output of a documented CDK CLI flag, and the user's view is simply that
|
|
2915
|
+
* cdk-local will not read what `cdk` just wrote. Record both halves of the
|
|
2916
|
+
* trade, because a later reader must not "restore" the refusal as an
|
|
2917
|
+
* oversight:
|
|
2918
|
+
*
|
|
2919
|
+
* - The security cost is genuine. This value is BIND-MOUNTED read-only at
|
|
2920
|
+
* `/var/task` (or `/opt` for a layer) into a container running handler code
|
|
2921
|
+
* the SAME assembly supplied, and the run may carry the caller's
|
|
2922
|
+
* credentials, so an absolute path a hostile assembly chose reaches the host
|
|
2923
|
+
* filesystem. Nothing here can distinguish a `--no-staging` value from a
|
|
2924
|
+
* planted one: both are an absolute directory the assembly named.
|
|
2925
|
+
* - What bounds it: this is a LOCAL developer command run against an assembly
|
|
2926
|
+
* the user pointed at, and the warning names the directory so an unexpected
|
|
2927
|
+
* one is visible rather than silent. **Do not count the read-only mount as
|
|
2928
|
+
* the bound.** It stops writes to the tree, not the capability: a read-only
|
|
2929
|
+
* bind of a directory holding a unix socket (`/var/run`, `/run`) still
|
|
2930
|
+
* permits `connect(2)` on, say, `docker.sock`. The warning is the
|
|
2931
|
+
* mitigation.
|
|
2932
|
+
*
|
|
2933
|
+
* The `..` containment is NOT relaxed with it, but be precise about what it
|
|
2934
|
+
* buys. Against an ADVERSARY it stops nothing: they write the ABSOLUTE
|
|
2935
|
+
* spelling and reach the same place with a warning instead of a refusal. What
|
|
2936
|
+
* it still catches is an ACCIDENTAL or legacy `..`, and it costs nothing,
|
|
2937
|
+
* which is why the arm stays. There is no containment boundary here any more;
|
|
2938
|
+
* the warning is the whole signal.
|
|
2939
|
+
*
|
|
2940
|
+
* CONTAIN WITHIN `assetOutdir`, NOT the manifest's directory. A Lambda inside
|
|
2941
|
+
* a `cdk.Stage` legitimately carries `../asset.<hash>`, because `cdk synth`
|
|
2942
|
+
* stages a Stage's assets into the APP's outdir while the Stage's manifest
|
|
2943
|
+
* sits in `cdk.out/assembly-<Stage>/`. Binding to the manifest directory
|
|
2944
|
+
* refuses every Stage asset as "hand-modified".
|
|
2945
|
+
*
|
|
2946
|
+
* Exported for unit testing and for `local-start-api.ts`'s copy of the caller.
|
|
2947
|
+
*/
|
|
2948
|
+
function resolveAssetCodeDirectory(manifestDir, assetPath, wrapError, assetOutdir, logicalId) {
|
|
2949
|
+
if (isAbsolute(assetPath)) {
|
|
2950
|
+
const absolute = resolve(assetPath);
|
|
2951
|
+
const escape = absoluteAssemblyPathEscape(assetOutdir, absolute);
|
|
2952
|
+
if (escape !== void 0) getLogger().warn(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' has an absolute Metadata['aws:asset:path'] pointing outside the assembly: '${sanitizeServiceExceptionMessage(absolute)}'` + (escape.escape === "symlink" ? ` (through a symbolic link to '${sanitizeServiceExceptionMessage(escape.realPath)}')` : "") + `. ${getEmbedConfig().productName} will read that directory and expose its contents to the container — bind-mounted read-only, or copied when the asset is a .zip or one of several merged layers — where the code in this assembly can read it. This is what cdk synth --no-staging emits, and is expected for it; if you did not synthesize with that flag, treat this assembly as untrusted.`);
|
|
2953
|
+
warnOutsideNamedDirectory(assetOutdir, absolute);
|
|
2954
|
+
return absolute;
|
|
2955
|
+
}
|
|
2956
|
+
const resolved = resolveAssemblyPath(manifestDir, assetPath, { containWithin: assetOutdir });
|
|
2957
|
+
if (!resolved.contained && resolved.escape === "lexical" && resolved.path === resolve(assetOutdir)) {
|
|
2958
|
+
warnOutsideNamedDirectory(assetOutdir, resolved.path);
|
|
2959
|
+
return resolved.path;
|
|
2960
|
+
}
|
|
2961
|
+
if (!resolved.contained) {
|
|
2962
|
+
const climbsOut = assetPath === ".." || assetPath.startsWith(`..${sep}`) || assetPath.startsWith("../");
|
|
2963
|
+
const stageHint = basename(assetOutdir).startsWith("assembly-") && climbsOut ? " This directory is named like a cdk.Stage sub-assembly, but its parent is not an assembly (no manifest.json), so it was treated as the assembly root. If you meant to point --app at a Stage inside an app's output directory, point it at that output directory." : "";
|
|
2964
|
+
throw wrapError(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' has Metadata['aws:asset:path']='${sanitizeServiceExceptionMessage(assetPath)}' which ${renderAssemblyPathEscape(resolved, assetOutdir, "mount it")}${stageHint}`);
|
|
2965
|
+
}
|
|
2966
|
+
warnOutsideNamedDirectory(assetOutdir, resolved.path);
|
|
2967
|
+
return resolved.path;
|
|
2968
|
+
}
|
|
2969
|
+
/**
|
|
2970
|
+
* The two directories an asset path is judged against — THE one spelling both
|
|
2971
|
+
* resolvers derive them with, for the same reason
|
|
2972
|
+
* {@link resolveAssetCodeDirectory} is one function: two sites disagreeing
|
|
2973
|
+
* about the BOUND is a defect no refusal test can see.
|
|
2974
|
+
*
|
|
2975
|
+
* Asset paths are relative to the manifest's own directory: the stack's
|
|
2976
|
+
* `assetManifestPath` is `<cdk.out>/<stack>.assets.json`, so stripping the
|
|
2977
|
+
* filename gives the base.
|
|
2978
|
+
*
|
|
2979
|
+
* `assetOutdir` is the app's outdir and is the CONTAINMENT bound. An ABSENT
|
|
2980
|
+
* one falls back to the base, which is correct for a top-level stack and
|
|
2981
|
+
* NARROWS for a Stage — it never opens past the base, so a hand-built
|
|
2982
|
+
* `StackInfo` carrying neither field is refused rather than admitted.
|
|
2983
|
+
* `AssemblyReader` always sets it.
|
|
2984
|
+
*
|
|
2985
|
+
* The two fallbacks are asymmetric, and the base's prefers `assetOutdir` over
|
|
2986
|
+
* `process.cwd()` deliberately: `AssemblyReader` always sets `assetOutdir` but
|
|
2987
|
+
* may leave `assetManifestPath` undefined, and taking `process.cwd()` there
|
|
2988
|
+
* produces a base DISJOINT from the bound — nothing under the cwd is inside
|
|
2989
|
+
* `cdk.out` — so every asset path is refused with a message blaming the
|
|
2990
|
+
* assembly. Fail-closed, so never a hole, but a wrong diagnosis.
|
|
2991
|
+
* `process.cwd()` survives only for a `StackInfo` carrying NEITHER field,
|
|
2992
|
+
* where base and bound coincide again.
|
|
2993
|
+
*
|
|
2994
|
+
* A PRESENT `assetOutdir` is never replaced BY THE BASE. It may be replaced by
|
|
2995
|
+
* its own ANCESTOR, and only in one shape: {@link assemblyRootOf} climbs when
|
|
2996
|
+
* `--app` names a `cdk.Stage` sub-assembly. That is a real widening, decided
|
|
2997
|
+
* partly by a file inside the tree being examined, and every instance of it is
|
|
2998
|
+
* warned — the reasoning is on `assemblyRootOf` and must be read with this
|
|
2999
|
+
* paragraph, not instead of it. **Do not restate the stronger claim here.** An
|
|
3000
|
+
* earlier revision of this block said the bound is used "AS GIVEN" after the
|
|
3001
|
+
* climb had already made that false, which is the same failure this PR
|
|
3002
|
+
* repaired in `StackInfo.assetOutdir`'s own doc one commit earlier.
|
|
3003
|
+
*
|
|
3004
|
+
* Note also that this helper is no longer pure: the climb reads the filesystem
|
|
3005
|
+
* and can emit a log line.
|
|
3006
|
+
*
|
|
3007
|
+
* The base/bound asymmetry is a trust boundary rather than a style choice:
|
|
3008
|
+
* `assetOutdir` comes from the user's own `--app` / `--output`, while
|
|
3009
|
+
* `manifestDir` is derived from `AssetManifestArtifact.file`, which cx-api
|
|
3010
|
+
* resolves out of the assembly's OWN `manifest.json` — so the base is
|
|
3011
|
+
* assembly-CONTROLLED and the bound is not. An earlier revision dropped a
|
|
3012
|
+
* present bound whenever it was not an ancestor of the base, meaning to
|
|
3013
|
+
* improve the diagnosis for a host that supplied a nonsense bound; measured,
|
|
3014
|
+
* it let a planted `"file": "../../../../x.assets.json"` push `manifestDir` to
|
|
3015
|
+
* `/` and carry the bound with it, so a plain relative `etc/passwd` resolved
|
|
3016
|
+
* CONTAINED. A disjoint host-supplied bound refusing everything is a wrong
|
|
3017
|
+
* DIAGNOSIS; a widened bound is the vulnerability this module exists to stop.
|
|
3018
|
+
*
|
|
3019
|
+
* State the property precisely, because the obvious stronger version is FALSE:
|
|
3020
|
+
* it is NOT that "the base left the bound, so everything resolved against it is
|
|
3021
|
+
* outside" — a candidate can climb back in (`app/cdk.out/asset.9f1` from a base
|
|
3022
|
+
* of `/Users/dev`). What holds is that THE BOUND IS ENFORCED INDEPENDENTLY OF
|
|
3023
|
+
* THE BASE, so a planted `file` can steer where a relative value resolves FROM
|
|
3024
|
+
* and can still only reach inside the BOUND — the user's own outdir, or the
|
|
3025
|
+
* root `assemblyRootOf` derived from it, which is the one case where a planted
|
|
3026
|
+
* tree can influence the bound and is warned for exactly that reason. A later reader leaning on the stronger sentence would think a
|
|
3027
|
+
* separate base check is redundant.
|
|
3028
|
+
*
|
|
3029
|
+
* An EMPTY string is treated as ABSENT rather than as a bound, because
|
|
3030
|
+
* `path.resolve('')` is the cwd — a directory the host never named. It takes
|
|
3031
|
+
* the same fallback as `undefined`.
|
|
3032
|
+
*
|
|
3033
|
+
* KNOW WHAT THE ABSENT CASE BUYS, which is NOT "a narrower bound": the
|
|
3034
|
+
* fallback is `manifestDir`, and that is assembly-derived, so a `StackInfo`
|
|
3035
|
+
* carrying no `assetOutdir` gets NO containment rather than a tighter one — a
|
|
3036
|
+
* planted `file` moves base and bound together. It is unreachable through
|
|
3037
|
+
* `AssemblyReader`, which always sets `assetOutdir` from `cloudAssembly.directory`,
|
|
3038
|
+
* and it is what cdkd does; a library host that builds `StackInfo` by hand and
|
|
3039
|
+
* wants the guard must supply the field.
|
|
3040
|
+
*
|
|
3041
|
+
* Exported for `local-start-api.ts`'s copy of the caller, and for unit testing.
|
|
3042
|
+
*/
|
|
3043
|
+
function assetPathDirs(stack) {
|
|
3044
|
+
const bound = stack.assetOutdir === "" ? void 0 : stack.assetOutdir;
|
|
3045
|
+
const manifestDir = stack.assetManifestPath ? dirname(stack.assetManifestPath) : bound ?? process.cwd();
|
|
3046
|
+
return {
|
|
3047
|
+
manifestDir,
|
|
3048
|
+
assetOutdir: bound === void 0 ? manifestDir : assemblyRootOf(bound)
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
/**
|
|
3052
|
+
* The real assembly ROOT for a `--app` that names a `cdk.Stage`
|
|
3053
|
+
* SUB-assembly.
|
|
3054
|
+
*
|
|
3055
|
+
* When a user points `--app` at `cdk.out/assembly-MyStage`, the assembly they
|
|
3056
|
+
* are working with is rooted at `cdk.out` — `cdk synth` writes the Stage's
|
|
3057
|
+
* manifest into the sub-directory and stages its ASSETS one level above, so
|
|
3058
|
+
* CDK's own `../asset.<hash>` is a within-assembly reference. Bounding at the
|
|
3059
|
+
* named directory made every one of those look like an escape, and the
|
|
3060
|
+
* refusal had to append a paragraph explaining that the layout "is not a
|
|
3061
|
+
* tamper" — a guard that has to talk you out of its own verdict is computing
|
|
3062
|
+
* the wrong thing.
|
|
3063
|
+
*
|
|
3064
|
+
* **It is a CONVENIENCE, not a safety property, and an earlier revision of
|
|
3065
|
+
* this comment claimed the opposite.** That revision argued the climb is safe
|
|
3066
|
+
* because it runs on a USER-supplied value. That is true of the STRING and
|
|
3067
|
+
* false of the DECISION: the predicate is a directory NAME and the presence of
|
|
3068
|
+
* a FILE, both inside the tree being examined — which under this module's own
|
|
3069
|
+
* threat model is the attacker's. An archive unpacking as `manifest.json` +
|
|
3070
|
+
* `assembly-X/` into a user's home, run as `--app ~/assembly-X`, moved the
|
|
3071
|
+
* bound to `~`, after which `../.aws` resolved CONTAINED and was mounted with
|
|
3072
|
+
* no refusal and no warning. It had been refused before the climb existed.
|
|
3073
|
+
*
|
|
3074
|
+
* So the climb is bounded by two things that are not arguments:
|
|
3075
|
+
*
|
|
3076
|
+
* 1. **It WARNS every time it fires** (see {@link warnDerivedAssemblyRoot}),
|
|
3077
|
+
* naming the directory the user passed and the one derived from it. The
|
|
3078
|
+
* module's doctrine for an absolute path applies here unchanged — a
|
|
3079
|
+
* directory the user did not name must be visible rather than silent — and
|
|
3080
|
+
* it costs one line on the legitimate Stage path.
|
|
3081
|
+
* 2. **The parent must DECLARE this child**, not merely sit above it: its
|
|
3082
|
+
* `manifest.json` must parse and carry a `cdk:cloud-assembly` artifact
|
|
3083
|
+
* whose `properties.directoryName` is this directory's basename, which is
|
|
3084
|
+
* cx-api's own invariant. This removes the accidental collision entirely
|
|
3085
|
+
* (`manifest.json` is not a CDK-exclusive filename) and makes the hostile
|
|
3086
|
+
* case require a purpose-built manifest. It does NOT make the climb safe
|
|
3087
|
+
* against someone who ships the whole tree — they can write that manifest
|
|
3088
|
+
* too. Point 1 is what covers that, which is why it is first.
|
|
3089
|
+
*
|
|
3090
|
+
* `manifestDir` never climbs: it stays `dirname(assetManifestPath)`, the
|
|
3091
|
+
* assembly-derived value, and is still judged against whatever this returns.
|
|
3092
|
+
*
|
|
3093
|
+
* Returns the caller's own spelling UNCHANGED when no climb happens, because
|
|
3094
|
+
* the bound is used as given elsewhere and normalising it would make a
|
|
3095
|
+
* relative `--output cdk.out` come back absolute for every ordinary app.
|
|
3096
|
+
*/
|
|
3097
|
+
function assemblyRootOf(outdir) {
|
|
3098
|
+
const cached = derivedRootCache.get(outdir);
|
|
3099
|
+
if (cached !== void 0) return cached;
|
|
3100
|
+
const root = climbToAssemblyRoot(outdir);
|
|
3101
|
+
derivedRootCache.set(outdir, root);
|
|
3102
|
+
return root;
|
|
3103
|
+
}
|
|
3104
|
+
/** The climb itself; {@link assemblyRootOf} holds the reasoning and memoizes. */
|
|
3105
|
+
function climbToAssemblyRoot(outdir) {
|
|
3106
|
+
let dir = resolve(outdir);
|
|
3107
|
+
let climbed = false;
|
|
3108
|
+
for (;;) {
|
|
3109
|
+
if (!basename(dir).startsWith("assembly-")) break;
|
|
3110
|
+
const parent = dirname(dir);
|
|
3111
|
+
if (parent === dir || !parentDeclaresNestedAssembly(parent, basename(dir))) break;
|
|
3112
|
+
dir = parent;
|
|
3113
|
+
climbed = true;
|
|
3114
|
+
}
|
|
3115
|
+
if (!climbed) return outdir;
|
|
3116
|
+
derivedRootOrigins.set(dir, outdir);
|
|
3117
|
+
warnDerivedAssemblyRoot(outdir, dir);
|
|
3118
|
+
return dir;
|
|
3119
|
+
}
|
|
3120
|
+
/**
|
|
3121
|
+
* Whether `parent`'s own `manifest.json` declares `child` as a nested
|
|
3122
|
+
* assembly, which is what cx-api writes for a `cdk.Stage`.
|
|
3123
|
+
*
|
|
3124
|
+
* Tolerant by construction: an unreadable or unparseable manifest, or one with
|
|
3125
|
+
* no matching artifact, answers `false` and the climb stops — the conservative
|
|
3126
|
+
* direction, since not climbing only restores the previous refusal.
|
|
3127
|
+
*/
|
|
3128
|
+
function parentDeclaresNestedAssembly(parent, child) {
|
|
3129
|
+
try {
|
|
3130
|
+
const raw = readFileSync(join(parent, "manifest.json"), "utf-8");
|
|
3131
|
+
const artifacts = JSON.parse(raw).artifacts;
|
|
3132
|
+
if (artifacts === null || typeof artifacts !== "object") return false;
|
|
3133
|
+
return Object.values(artifacts).some((a) => {
|
|
3134
|
+
const art = a;
|
|
3135
|
+
return art?.type === "cdk:cloud-assembly" && art.properties?.directoryName === child;
|
|
3136
|
+
});
|
|
3137
|
+
} catch {
|
|
3138
|
+
return false;
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
/**
|
|
3142
|
+
* Per-process state for the climb. `assetPathDirs` runs once per Lambda AND
|
|
3143
|
+
* once per layer, so without the cache a real `cdk.out/manifest.json` —
|
|
3144
|
+
* routinely multi-MB — is re-read and re-parsed for every one of them, and
|
|
3145
|
+
* `--watch` repeats that per firing. It is also the amplification bound on an
|
|
3146
|
+
* attacker-sized manifest.
|
|
3147
|
+
*
|
|
3148
|
+
* **A cached root can outlive its own evidence, and only in the already-warned
|
|
3149
|
+
* direction.** If a `--watch` re-synth rewrites the parent so it no longer
|
|
3150
|
+
* declares the child, the cache keeps answering the WIDE root while a fresh
|
|
3151
|
+
* derivation would decline and narrow. That grants nothing new: the root
|
|
3152
|
+
* warning fired for that root when it was first derived, and re-deriving can
|
|
3153
|
+
* only narrow. The reverse — cached narrow, a declaring manifest appears
|
|
3154
|
+
* later — stays narrow, which is fail-closed. So the cache cannot introduce an
|
|
3155
|
+
* UNWARNED widening in either direction.
|
|
3156
|
+
*
|
|
3157
|
+
* Keyed by the raw outdir STRING and never evicted. Sound because the same
|
|
3158
|
+
* string names the same directory: a second assembly reusing an outdir is the
|
|
3159
|
+
* same tree, and a changed tree under it is case A above. Growth is one entry
|
|
3160
|
+
* per distinct `--app`, and a climb only happens under `readFromDirectory`
|
|
3161
|
+
* where that argument is constant for the process.
|
|
3162
|
+
*/
|
|
3163
|
+
const derivedRootCache = /* @__PURE__ */ new Map();
|
|
3164
|
+
/**
|
|
3165
|
+
* Derived root -> the directory the user actually named. LAST WRITER WINS, and
|
|
3166
|
+
* the consequence is always a WRONG NAME, never a missing warning: two sibling
|
|
3167
|
+
* Stages climbing to one root make the per-path line name the other sibling,
|
|
3168
|
+
* and a long-lived host that processes assembly A (climbed to `/x/cdk.out`)
|
|
3169
|
+
* and then assembly B whose `--app` IS `/x/cdk.out` names A's Stage directory
|
|
3170
|
+
* throughout run B. The root and the offending path stay correct in both.
|
|
3171
|
+
* Both are a wrong NAME in a line that still fires with the right root and the
|
|
3172
|
+
* right path, which is why neither buys an eviction policy.
|
|
3173
|
+
*/
|
|
3174
|
+
const derivedRootOrigins = /* @__PURE__ */ new Map();
|
|
3175
|
+
const warnedDerivedRoots = /* @__PURE__ */ new Set();
|
|
3176
|
+
const warnedOutsideNamed = /* @__PURE__ */ new Set();
|
|
3177
|
+
/**
|
|
3178
|
+
* Warn that an accepted path leaves the directory the user NAMED, when the
|
|
3179
|
+
* bound was widened by a climb.
|
|
3180
|
+
*
|
|
3181
|
+
* **This is what makes the climb's signal as loud as the absolute arm's.**
|
|
3182
|
+
* Without it the two diverge in the attacker's favour: an absolute path warns
|
|
3183
|
+
* PER LAMBDA naming the exact directory, while a climb warned once at startup
|
|
3184
|
+
* about the root and then mounted each individual path in silence — so
|
|
3185
|
+
* whoever gets the climb converts a per-mount warning into a one-line notice.
|
|
3186
|
+
* Now both arms say something per path that leaves what the user asked for.
|
|
3187
|
+
*/
|
|
3188
|
+
function warnOutsideNamedDirectory(assetOutdir, resolved) {
|
|
3189
|
+
const named = derivedRootOrigins.get(assetOutdir);
|
|
3190
|
+
if (named === void 0) return;
|
|
3191
|
+
if (absoluteAssemblyPathEscape(named, resolved) === void 0) return;
|
|
3192
|
+
if (warnedOutsideNamed.has(resolved)) return;
|
|
3193
|
+
warnedOutsideNamed.add(resolved);
|
|
3194
|
+
getLogger().warn(`'${sanitizeServiceExceptionMessage(resolved)}' is outside '${sanitizeServiceExceptionMessage(named)}', the directory --app named. It is inside the assembly root derived from it, so ${getEmbedConfig().productName} is using it.`);
|
|
3195
|
+
}
|
|
3196
|
+
/**
|
|
3197
|
+
* Say that the assembly root was DERIVED rather than given.
|
|
3198
|
+
*
|
|
3199
|
+
* The user named one directory and the containment bound is another, wider
|
|
3200
|
+
* one. On the legitimate Stage path that is exactly what they wanted and the
|
|
3201
|
+
* line is informative; on a hostile tree it is the only signal that a sibling
|
|
3202
|
+
* of the directory they named is now inside the bound.
|
|
3203
|
+
*/
|
|
3204
|
+
function warnDerivedAssemblyRoot(named, derived) {
|
|
3205
|
+
if (warnedDerivedRoots.has(derived)) return;
|
|
3206
|
+
warnedDerivedRoots.add(derived);
|
|
3207
|
+
getLogger().warn(`'${sanitizeServiceExceptionMessage(named)}' is a cdk.Stage sub-assembly, so ${getEmbedConfig().productName} is treating its parent '${sanitizeServiceExceptionMessage(derived)}' as the assembly root — that is where cdk synth stages a Stage's assets. Everything under that parent is now inside the containment bound, including siblings of the directory you named. If you did not expect the wider directory, point --app at the app's own output directory instead.`);
|
|
3208
|
+
}
|
|
3209
|
+
/**
|
|
2545
3210
|
* Resolve the local directory that corresponds to a function's deployed
|
|
2546
3211
|
* asset, using the CDK-blessed `Metadata['aws:asset:path']` hint (D2). The
|
|
2547
3212
|
* value is a directory path relative to `cdk.out` (e.g. `asset.abc123def`)
|
|
@@ -2551,18 +3216,21 @@ function extractImageLambdaProperties(args) {
|
|
|
2551
3216
|
* Falls back to a clear error when the metadata is missing OR the resolved
|
|
2552
3217
|
* directory does not exist (CDK should always emit it for asset-backed
|
|
2553
3218
|
* Lambdas; absence usually means the user pre-synthesized with a different
|
|
2554
|
-
* cdk.out and pointed `--output` at a stale one).
|
|
3219
|
+
* cdk.out and pointed `--output` at a stale one). Through
|
|
3220
|
+
* {@link resolveAssetCodeDirectory} it REFUSES an escaping RELATIVE value and
|
|
3221
|
+
* WARNS on an ABSOLUTE one that leaves the asset outdir — see that function
|
|
3222
|
+
* for why the two differ.
|
|
2555
3223
|
*/
|
|
2556
3224
|
function resolveAssetCodePath$1(stack, logicalId, resource, options = {}) {
|
|
2557
3225
|
const assetPath = resource.Metadata?.["aws:asset:path"];
|
|
2558
|
-
if (typeof assetPath !== "string" || assetPath.length === 0) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} invoke needs this hint to find the local asset directory. Re-synthesize the app (without \`--output <stale-dir>\`) and retry.`);
|
|
2559
|
-
const
|
|
2560
|
-
const abs =
|
|
2561
|
-
if (!existsSync(abs)) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' asset path '${abs}' does not exist. Re-synthesize the app and retry.`);
|
|
3226
|
+
if (typeof assetPath !== "string" || assetPath.length === 0) throw new LocalInvokeResolutionError(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} invoke needs this hint to find the local asset directory. Re-synthesize the app (without \`--output <stale-dir>\`) and retry.`);
|
|
3227
|
+
const { manifestDir, assetOutdir } = assetPathDirs(stack);
|
|
3228
|
+
const abs = resolveAssetCodeDirectory(manifestDir, assetPath, (message) => new LocalInvokeResolutionError(message), assetOutdir, logicalId);
|
|
3229
|
+
if (!existsSync(abs)) throw new LocalInvokeResolutionError(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' asset path '${sanitizeServiceExceptionMessage(abs)}' does not exist. Re-synthesize the app and retry.`);
|
|
2562
3230
|
const stat = statSync(abs);
|
|
2563
3231
|
if (stat.isDirectory()) return abs;
|
|
2564
3232
|
if (options.allowZip && stat.isFile() && abs.toLowerCase().endsWith(".zip")) return abs;
|
|
2565
|
-
throw new LocalInvokeResolutionError(`Lambda '${logicalId}' asset path '${abs}' is not a directory` + (options.allowZip ? " or a .zip archive" : "") + ". Re-synthesize the app and retry.");
|
|
3233
|
+
throw new LocalInvokeResolutionError(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' asset path '${sanitizeServiceExceptionMessage(abs)}' is not a directory` + (options.allowZip ? " or a .zip archive" : "") + ". Re-synthesize the app and retry.");
|
|
2566
3234
|
}
|
|
2567
3235
|
/**
|
|
2568
3236
|
* Turn a resolved function-code asset path into a directory ready to
|
|
@@ -2581,14 +3249,14 @@ function resolveAssetCodePath$1(stack, logicalId, resource, options = {}) {
|
|
|
2581
3249
|
* existing tmpdir cleanup.
|
|
2582
3250
|
*/
|
|
2583
3251
|
function materializeAssetCodeDir(codePath) {
|
|
2584
|
-
if (!existsSync(codePath)) throw new LocalInvokeResolutionError(`Lambda asset path '${codePath}' does not exist. Re-synthesize the app and retry.`);
|
|
3252
|
+
if (!existsSync(codePath)) throw new LocalInvokeResolutionError(`Lambda asset path '${sanitizeServiceExceptionMessage(codePath)}' does not exist. Re-synthesize the app and retry.`);
|
|
2585
3253
|
if (statSync(codePath).isDirectory()) return { dir: codePath };
|
|
2586
3254
|
const zipBytes = readFileSync(codePath);
|
|
2587
3255
|
let files;
|
|
2588
3256
|
try {
|
|
2589
3257
|
files = unzipSync(zipBytes);
|
|
2590
3258
|
} catch (err) {
|
|
2591
|
-
throw new LocalInvokeResolutionError(`Lambda asset '${codePath}' is a file but could not be read as a ZIP archive: ${err instanceof Error ? err.message : String(err)}. Re-synthesize the app and retry.`);
|
|
3259
|
+
throw new LocalInvokeResolutionError(`Lambda asset '${sanitizeServiceExceptionMessage(codePath)}' is a file but could not be read as a ZIP archive: ${err instanceof Error ? err.message : String(err)}. Re-synthesize the app and retry.`);
|
|
2592
3260
|
}
|
|
2593
3261
|
const modes = parseZipUnixModes(zipBytes);
|
|
2594
3262
|
const S_IFMT = 61440;
|
|
@@ -2675,7 +3343,7 @@ function resolveSafeZipEntryPath(root, entry) {
|
|
|
2675
3343
|
* **Order is preserved**: `Properties.Layers` is iterated left-to-right
|
|
2676
3344
|
* and the resulting `ResolvedLambdaLayer[]` carries the same order. The
|
|
2677
3345
|
* caller (`local-invoke.ts`'s `materializeLambdaLayers` and
|
|
2678
|
-
* `local-start-api.ts`'s server-boot pre-merge)
|
|
3346
|
+
* `local-start-api.ts`'s server-boot pre-merge) merges every
|
|
2679
3347
|
* entry into one host tmpdir in template order to honor AWS's
|
|
2680
3348
|
* "last-layer-wins" file-collision semantics — Docker rejects multiple
|
|
2681
3349
|
* bind mounts at the same target so cdk-local cannot rely on overlay
|
|
@@ -5286,7 +5954,7 @@ var AssemblyReader = class {
|
|
|
5286
5954
|
});
|
|
5287
5955
|
const cached = await toolkit.synth(source);
|
|
5288
5956
|
try {
|
|
5289
|
-
return cached.cloudAssembly
|
|
5957
|
+
return collectStacks(cached.cloudAssembly);
|
|
5290
5958
|
} finally {
|
|
5291
5959
|
await cached.dispose();
|
|
5292
5960
|
}
|
|
@@ -5316,19 +5984,54 @@ var AssemblyReader = class {
|
|
|
5316
5984
|
const source = await toolkit.fromAssemblyDirectory(assemblyDir, { failOnMissingContext: false });
|
|
5317
5985
|
const cached = await toolkit.synth(source);
|
|
5318
5986
|
try {
|
|
5319
|
-
return cached.cloudAssembly
|
|
5987
|
+
return collectStacks(cached.cloudAssembly);
|
|
5320
5988
|
} finally {
|
|
5321
5989
|
await cached.dispose();
|
|
5322
5990
|
}
|
|
5323
5991
|
}
|
|
5324
5992
|
};
|
|
5325
|
-
|
|
5993
|
+
/**
|
|
5994
|
+
* Every stack this assembly enumerates, each bounded by the assembly ROOT.
|
|
5995
|
+
*
|
|
5996
|
+
* NOT `stacksRecursively`, and the omission is a decision rather than an
|
|
5997
|
+
* oversight. `CloudAssembly.stacks` lists only this assembly's own artifacts,
|
|
5998
|
+
* and a `cdk.Stage` is a `NestedCloudAssemblyArtifact`, so a Stage's stacks are
|
|
5999
|
+
* not enumerated at all — a pre-existing gap tracked as
|
|
6000
|
+
* [#746](https://github.com/go-to-k/cdk-local/issues/746). Recursing here would
|
|
6001
|
+
* close it and is tempting because it also makes `assetOutdir` differ from the
|
|
6002
|
+
* manifest directory, which is the shape this module's bound exists for. It is
|
|
6003
|
+
* NOT done here because the recursion changes STACK ENUMERATION for every
|
|
6004
|
+
* command: eleven call sites read `stacks.length === 1` to offer the
|
|
6005
|
+
* "single-stack apps may omit the stack prefix" convenience, `matchStacks`
|
|
6006
|
+
* dedupes by `stackName`, and a Stage's clones carry identical logical IDs. That
|
|
6007
|
+
* is a feature with its own design and its own tests, not a side effect of a
|
|
6008
|
+
* containment fix.
|
|
6009
|
+
*
|
|
6010
|
+
* The consequence to know: a Stage's stacks are reachable only by pointing
|
|
6011
|
+
* `--app` at `cdk.out/assembly-<Stage>/`, which makes that directory the
|
|
6012
|
+
* assembly root, so the Stage's own assets — staged into the APP's outdir by
|
|
6013
|
+
* `cdk synth` — fall outside it and are refused. `resolveAssetCodeDirectory`'s
|
|
6014
|
+
* refusal says so in as many words rather than leaving the user with the
|
|
6015
|
+
* generic hand-modified-assembly diagnosis.
|
|
6016
|
+
*/
|
|
6017
|
+
function collectStacks(assembly) {
|
|
6018
|
+
return assembly.stacks.map((stack) => mapStackArtifact(stack, assembly.directory));
|
|
6019
|
+
}
|
|
6020
|
+
/**
|
|
6021
|
+
* `assetOutdir` is threaded in from the ROOT assembly rather than read off the
|
|
6022
|
+
* artifact's own `assembly.directory`: for a stack under a `cdk.Stage` the
|
|
6023
|
+
* latter is `cdk.out/assembly-<Stage>/`, which is the manifest directory, not
|
|
6024
|
+
* the directory the Stage's assets were staged into. See
|
|
6025
|
+
* `StackInfo.assetOutdir`.
|
|
6026
|
+
*/
|
|
6027
|
+
function mapStackArtifact(stack, assetOutdir) {
|
|
5326
6028
|
const info = {
|
|
5327
6029
|
stackName: stack.stackName,
|
|
5328
6030
|
displayName: stack.displayName ?? stack.id,
|
|
5329
6031
|
artifactId: stack.id,
|
|
5330
6032
|
template: stack.template,
|
|
5331
|
-
dependencyNames: stack.dependencies.map((d) => d.id)
|
|
6033
|
+
dependencyNames: stack.dependencies.map((d) => d.id),
|
|
6034
|
+
assetOutdir
|
|
5332
6035
|
};
|
|
5333
6036
|
if (stack.environment.region) info.region = stack.environment.region;
|
|
5334
6037
|
if (stack.environment.account) info.account = stack.environment.account;
|
|
@@ -17149,6 +17852,110 @@ function looksLikeAccessDenied(err) {
|
|
|
17149
17852
|
return name === "AccessDeniedException" || code === "AccessDeniedException" || /access denied/i.test(message) || /not authorized/i.test(message);
|
|
17150
17853
|
}
|
|
17151
17854
|
|
|
17855
|
+
//#endregion
|
|
17856
|
+
//#region src/local/layer-tree-copy.ts
|
|
17857
|
+
/**
|
|
17858
|
+
* Merge one Lambda Layer's asset tree into the directory that will be
|
|
17859
|
+
* bind-mounted at `/opt`, with AWS's "last layer wins" semantic and with
|
|
17860
|
+
* symlinks kept VERBATIM (issue #727).
|
|
17861
|
+
*
|
|
17862
|
+
* Why this is a hand-written walk and not `cpSync`. The merge used to be
|
|
17863
|
+
* `cpSync(src, dest, { recursive: true, force: true })`, and every
|
|
17864
|
+
* `cpSync`-shaped repair of it was measured wrong in some direction:
|
|
17865
|
+
*
|
|
17866
|
+
* - Without `verbatimSymlinks: true` (the default), `cpSync` rewrites a
|
|
17867
|
+
* RELATIVE link target to the ABSOLUTE path of the source on the host —
|
|
17868
|
+
* `bin/rel-link -> real.sh` arrives as `-> <cdk.out>/asset.<hash>/bin/
|
|
17869
|
+
* real.sh`, which is dangling inside the container, where only `dest`
|
|
17870
|
+
* is mounted. Measured on Node 22.12 / 24.21.
|
|
17871
|
+
* - WITH `verbatimSymlinks: true`, Node's C++ `cpSync` (every release
|
|
17872
|
+
* after 22.12) throws `EEXIST` when a later layer carries a symlink at a
|
|
17873
|
+
* path an earlier layer already placed one — `nodejs/node_modules/.bin/
|
|
17874
|
+
* <tool>` in two layers built from the same dependency is the ordinary
|
|
17875
|
+
* case. Measured by the go-to-k/cdkd#3118 review (the cdkd twin) on 22.23 / 24.15 /
|
|
17876
|
+
* 24.21; the JS implementation on 22.12 does not throw, which is why
|
|
17877
|
+
* the floor alone did not show it.
|
|
17878
|
+
* - A `cpSync` + `readdirSync({ recursive: true })` pair was worse:
|
|
17879
|
+
* the recursive readdir DESCENDS INTO directory symlinks on every Node
|
|
17880
|
+
* after 22.12 (not on 22.12), so a cyclic link (`sub/up -> ..`, or a
|
|
17881
|
+
* pnpm-style `node_modules`) hung the merge, and an absolute link to a
|
|
17882
|
+
* host directory made the walk delete and write INSIDE that host
|
|
17883
|
+
* directory. And `cpSync`'s `force` writes THROUGH a destination that
|
|
17884
|
+
* is already a directory symlink, so a later layer's real directory
|
|
17885
|
+
* landed in the earlier layer's link target — on the host, when the
|
|
17886
|
+
* link was absolute. Same review, same versions.
|
|
17887
|
+
*
|
|
17888
|
+
* So the walk is explicit and recurses only on a real directory (`d_type`,
|
|
17889
|
+
* via `Dirent.isDirectory()`, which reports a directory symlink as a
|
|
17890
|
+
* symlink on 22.12 and 24.21 alike). Per entry, "last layer wins" is applied
|
|
17891
|
+
* the same way for every kind: whatever sits at the destination path that is
|
|
17892
|
+
* not of the same kind is removed first — a link, a file, or a whole
|
|
17893
|
+
* directory an earlier layer placed — then the entry lands. A symlink is
|
|
17894
|
+
* recreated with the SOURCE's own target string and never resolved, so an
|
|
17895
|
+
* absolute link stays absolute and a dangling one stays dangling, exactly as
|
|
17896
|
+
* AWS extracts the layer ZIP into `/opt`; nothing is ever written through a
|
|
17897
|
+
* link. A file is copied with `cpSync` (single file, `force`), which is what
|
|
17898
|
+
* preserves the mode bits, so a layer's `bin/<script>` keeps its `+x` — the
|
|
17899
|
+
* property the `local-invoke-layers` fixture executes through.
|
|
17900
|
+
*
|
|
17901
|
+
* The ROOT may itself be a symlink (an asset dir handed over through a link
|
|
17902
|
+
* passes `resolveAssetCodePath`'s `statSync`). `readdirSync` follows a root
|
|
17903
|
+
* link by itself, so the `realpathSync` is not what makes that case work,
|
|
17904
|
+
* and it does not move where a dangling or looping root fails either (the
|
|
17905
|
+
* first `readdirSync` raises the same `ENOENT` / `ELOOP` naming the asset
|
|
17906
|
+
* path); it only keeps every path the walk joins real.
|
|
17907
|
+
*
|
|
17908
|
+
* Bound, stated rather than handled: a FIFO or a socket inside a layer
|
|
17909
|
+
* makes the single-file `cpSync` throw (`ERR_FS_CP_FIFO_PIPE` /
|
|
17910
|
+
* `ERR_FS_CP_SOCKET`, immediately, never blocking) where the old recursive
|
|
17911
|
+
* copy skipped it in silence on some releases. CDK's own asset staging
|
|
17912
|
+
* cannot stage such an entry, so no real layer carries one.
|
|
17913
|
+
*
|
|
17914
|
+
* Both layer merges (`cdkl invoke` and `cdkl start-api`) call
|
|
17915
|
+
* this; keep them on the one helper rather than re-spelling copy options.
|
|
17916
|
+
*/
|
|
17917
|
+
function copyLayerTreeLastWins(src, dest) {
|
|
17918
|
+
mkdirSync(dest, { recursive: true });
|
|
17919
|
+
copyDirLastWins(realpathSync(src), dest);
|
|
17920
|
+
}
|
|
17921
|
+
function copyDirLastWins(fromDir, toDir) {
|
|
17922
|
+
for (const entry of readdirSync(fromDir, { withFileTypes: true })) {
|
|
17923
|
+
const from = join(fromDir, entry.name);
|
|
17924
|
+
const to = join(toDir, entry.name);
|
|
17925
|
+
if (entry.isSymbolicLink()) {
|
|
17926
|
+
removeUnless(to, "symlink");
|
|
17927
|
+
symlinkSync(readlinkSync(from), to);
|
|
17928
|
+
} else if (entry.isDirectory()) {
|
|
17929
|
+
removeUnless(to, "directory");
|
|
17930
|
+
mkdirSync(to, { recursive: true });
|
|
17931
|
+
copyDirLastWins(from, to);
|
|
17932
|
+
} else {
|
|
17933
|
+
removeUnless(to, "file");
|
|
17934
|
+
cpSync(from, to, { force: true });
|
|
17935
|
+
}
|
|
17936
|
+
}
|
|
17937
|
+
}
|
|
17938
|
+
/**
|
|
17939
|
+
* Clear the destination path unless what is there is already of `kind`. A
|
|
17940
|
+
* directory is kept so a later layer MERGES into it; a file is kept because
|
|
17941
|
+
* `cpSync` overwrites it in place; a symlink is always replaced (its target
|
|
17942
|
+
* string comes from the later layer).
|
|
17943
|
+
*/
|
|
17944
|
+
function removeUnless(to, kind) {
|
|
17945
|
+
let st;
|
|
17946
|
+
try {
|
|
17947
|
+
st = lstatSync(to);
|
|
17948
|
+
} catch {
|
|
17949
|
+
return;
|
|
17950
|
+
}
|
|
17951
|
+
if (kind === "directory" && st.isDirectory()) return;
|
|
17952
|
+
if (kind === "file" && st.isFile()) return;
|
|
17953
|
+
rmSync(to, {
|
|
17954
|
+
recursive: true,
|
|
17955
|
+
force: true
|
|
17956
|
+
});
|
|
17957
|
+
}
|
|
17958
|
+
|
|
17152
17959
|
//#endregion
|
|
17153
17960
|
//#region src/local/stage-resolver.ts
|
|
17154
17961
|
/**
|
|
@@ -18587,12 +19394,12 @@ async function resolveLocalBuildPlan$1(lambda) {
|
|
|
18587
19394
|
* when the entry is a same-stack asset. Literal-ARN entries always
|
|
18588
19395
|
* pre-materialize first.
|
|
18589
19396
|
* - 2+ layers → copy each into a fresh tmpdir IN ORDER (later
|
|
18590
|
-
* layers overwrite earlier files via `
|
|
19397
|
+
* layers overwrite earlier files via `copyLayerTreeLastWins`),
|
|
18591
19398
|
* bind-mount the tmpdir at `/opt`. Records the tmpdir in
|
|
18592
19399
|
* `layerTmpDirs` so `shutdown(...)` removes it.
|
|
18593
19400
|
*
|
|
18594
19401
|
* Issue #448: literal-ARN entries (`{kind: 'arn', ...}`) are downloaded
|
|
18595
|
-
* + unzipped via `lambda:GetLayerVersion` BEFORE the
|
|
19402
|
+
* + unzipped via `lambda:GetLayerVersion` BEFORE the merge
|
|
18596
19403
|
* branches run. Every per-ARN tmpdir is also recorded in `layerTmpDirs`
|
|
18597
19404
|
* so the same shutdown path cleans it up — even for the single-layer
|
|
18598
19405
|
* fast path that bind-mounts the dir directly.
|
|
@@ -18622,10 +19429,15 @@ async function materializeLambdaLayers$1(layers, layerTmpDirs, layerRoleArn) {
|
|
|
18622
19429
|
}
|
|
18623
19430
|
if (flat.length === 1) return flat[0].assetPath;
|
|
18624
19431
|
const dir = mkdtempSync(path$1.join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-start-api-layers-`));
|
|
18625
|
-
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
19432
|
+
try {
|
|
19433
|
+
for (const layer of flat) copyLayerTreeLastWins(layer.assetPath, dir);
|
|
19434
|
+
} catch (error) {
|
|
19435
|
+
rmSync(dir, {
|
|
19436
|
+
recursive: true,
|
|
19437
|
+
force: true
|
|
19438
|
+
});
|
|
19439
|
+
throw error;
|
|
19440
|
+
}
|
|
18629
19441
|
layerTmpDirs.add(dir);
|
|
18630
19442
|
return dir;
|
|
18631
19443
|
}
|
|
@@ -18758,12 +19570,18 @@ function resolveImageLambda(args) {
|
|
|
18758
19570
|
* Locate the Lambda's local code directory using the CDK-blessed
|
|
18759
19571
|
* `Metadata['aws:asset:path']` hint. Bind-mounted directly at
|
|
18760
19572
|
* `/var/task` (read-only) by the docker-runner.
|
|
19573
|
+
*
|
|
19574
|
+
* The resolution itself is `lambda-resolver.ts`'s
|
|
19575
|
+
* {@link resolveAssetCodeDirectory} — THE one spelling, so this twin and
|
|
19576
|
+
* `cdkl invoke`'s cannot disagree about the containment bound. It REFUSES an
|
|
19577
|
+
* escaping RELATIVE value and WARNS on an ABSOLUTE one that leaves the asset
|
|
19578
|
+
* outdir; that function's header records why the two shapes differ.
|
|
18761
19579
|
*/
|
|
18762
19580
|
function resolveAssetCodePath(stack, logicalId, resource) {
|
|
18763
19581
|
const assetPath = resource.Metadata?.["aws:asset:path"];
|
|
18764
|
-
if (typeof assetPath !== "string" || assetPath.length === 0) throw new Error(`Lambda '${logicalId}' has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} start-api needs this hint to find the local asset directory. Re-synthesize the app and retry.`);
|
|
18765
|
-
const
|
|
18766
|
-
return
|
|
19582
|
+
if (typeof assetPath !== "string" || assetPath.length === 0) throw new Error(`Lambda '${sanitizeServiceExceptionMessage(logicalId)}' has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} start-api needs this hint to find the local asset directory. Re-synthesize the app and retry.`);
|
|
19583
|
+
const { manifestDir, assetOutdir } = assetPathDirs(stack);
|
|
19584
|
+
return resolveAssetCodeDirectory(manifestDir, assetPath, (message) => new Error(message), assetOutdir, logicalId);
|
|
18767
19585
|
}
|
|
18768
19586
|
/**
|
|
18769
19587
|
* Print the discovered route table to stdout. Format mirrors the spec
|
|
@@ -19484,6 +20302,44 @@ async function resolveZipImagePlan$1(lambda, options) {
|
|
|
19484
20302
|
...tmpfs !== void 0 && { tmpfs }
|
|
19485
20303
|
};
|
|
19486
20304
|
}
|
|
20305
|
+
/**
|
|
20306
|
+
* Render an STS AssumeRole failure into the one line a warn may print.
|
|
20307
|
+
*
|
|
20308
|
+
* The single home for the policy that used to be copy-pasted as a ternary at
|
|
20309
|
+
* four relay sites (one here, three in `local-invoke-agentcore.ts`). Both
|
|
20310
|
+
* branches matter and neither is the obvious one:
|
|
20311
|
+
*
|
|
20312
|
+
* - `AssumeRoleFailure` carries a detail `assumeRoleCredentials` ALREADY
|
|
20313
|
+
* rendered through the policy, so it is taken verbatim. Re-rendering it here
|
|
20314
|
+
* would hand `describeAwsFailureForWarn` a plain `Error` -- cdk-local's own,
|
|
20315
|
+
* so no `$fault` -- and WITHHOLD text that was already sanitized, which is
|
|
20316
|
+
* how `ExpiredTokenException: ...` briefly became
|
|
20317
|
+
* `Error; 138-character message withheld` (issue #579 review round 4).
|
|
20318
|
+
* - Anything else is a raw SDK error and goes through
|
|
20319
|
+
* `describeAwsFailureForWarn`, which keeps a modeled service exception's
|
|
20320
|
+
* message and withholds everything else -- credential-chain failures above
|
|
20321
|
+
* all, which can carry a `credential_process` command line.
|
|
20322
|
+
*
|
|
20323
|
+
* `op` is the label `describeAwsFailureForWarn` puts on the `debug` line it
|
|
20324
|
+
* emits when it withholds a message -- NOT on the returned string, which is the
|
|
20325
|
+
* clamped class plus a character count and carries no operation. Callers pass
|
|
20326
|
+
* `'STS AssumeRole'` or a qualified variant such as
|
|
20327
|
+
* `'STS AssumeRole (--sigv4 signing)'`, and that qualification is the whole
|
|
20328
|
+
* reason the label is a parameter rather than a constant in here.
|
|
20329
|
+
*
|
|
20330
|
+
* This is deliberately a ONE-EXPRESSION function, and that is a hard
|
|
20331
|
+
* constraint rather than a style preference:
|
|
20332
|
+
* `tests/unit/cli/sts-error-relay-source-fence.test.ts` only accepts a NAMED
|
|
20333
|
+
* RENDERER whose body carries the full policy shape, does not `warn` itself,
|
|
20334
|
+
* and closes within `RENDERER_BODY_MAX_LINES`. A longer body, or one that
|
|
20335
|
+
* warned, would stop being collected and every site delegating to it would be
|
|
20336
|
+
* reported as an unguarded relay. Before issue #644 the fence could not model
|
|
20337
|
+
* indirection at all, so these four sites had to stay inline; it can now, and
|
|
20338
|
+
* this is the refactor that was waiting on it.
|
|
20339
|
+
*/
|
|
20340
|
+
function assumeRoleDetail(err, op) {
|
|
20341
|
+
return err instanceof AssumeRoleFailure ? err.detail : describeAwsFailureForWarn(err, op);
|
|
20342
|
+
}
|
|
19487
20343
|
async function materializeLambdaLayersIncludingArns(layers, options) {
|
|
19488
20344
|
const extraTmpDirs = [];
|
|
19489
20345
|
const flat = [];
|
|
@@ -19525,10 +20381,15 @@ function materializeLambdaLayers(layers) {
|
|
|
19525
20381
|
readOnly: true
|
|
19526
20382
|
} };
|
|
19527
20383
|
const tmpDir = mkdtempSync(path$1.join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-invoke-layers-`));
|
|
19528
|
-
|
|
19529
|
-
|
|
19530
|
-
|
|
19531
|
-
|
|
20384
|
+
try {
|
|
20385
|
+
for (const layer of layers) copyLayerTreeLastWins(layer.assetPath, tmpDir);
|
|
20386
|
+
} catch (error) {
|
|
20387
|
+
rmSync(tmpDir, {
|
|
20388
|
+
recursive: true,
|
|
20389
|
+
force: true
|
|
20390
|
+
});
|
|
20391
|
+
throw error;
|
|
20392
|
+
}
|
|
19532
20393
|
return {
|
|
19533
20394
|
mount: {
|
|
19534
20395
|
hostPath: tmpDir,
|
|
@@ -19818,7 +20679,7 @@ async function resolveLambdaContainerEnv(lambda, options, profileCredentials, ex
|
|
|
19818
20679
|
if (stsRegion) dockerEnv["AWS_REGION"] = stsRegion;
|
|
19819
20680
|
assumeSucceeded = true;
|
|
19820
20681
|
} catch (err) {
|
|
19821
|
-
const reason =
|
|
20682
|
+
const reason = assumeRoleDetail(err, "STS AssumeRole");
|
|
19822
20683
|
logger.warn(`--assume-role: STS AssumeRole(${flattenToOneLine(resolvedAssumeRoleArn)}) failed: ${reason}. Falling back to the developer's shell credentials.`);
|
|
19823
20684
|
}
|
|
19824
20685
|
}
|
|
@@ -21529,7 +22390,7 @@ async function resolveHostCredentialsForSigV4(options, resolved, loaded, region,
|
|
|
21529
22390
|
if (assumeRoleArn) try {
|
|
21530
22391
|
return await assumeAgentCoreExecutionRole(assumeRoleArn, region, options.profile);
|
|
21531
22392
|
} catch (err) {
|
|
21532
|
-
const sigv4Detail =
|
|
22393
|
+
const sigv4Detail = assumeRoleDetail(err, "STS AssumeRole (--sigv4 signing)");
|
|
21533
22394
|
logger.warn(`--assume-role: STS AssumeRole(${flattenToOneLine(assumeRoleArn)}) failed for --sigv4 signing: ${sigv4Detail}. Falling back to ${options.profile ? `--profile ${options.profile}` : "shell credentials"}.`);
|
|
21534
22395
|
}
|
|
21535
22396
|
if (options.profile) {
|
|
@@ -21653,7 +22514,7 @@ async function resolveAgentCoreCodeImageFromS3(resolved, code, s3Source, options
|
|
|
21653
22514
|
if (assumeRoleArn) try {
|
|
21654
22515
|
credentials = await assumeAgentCoreExecutionRole(assumeRoleArn, region, options.profile);
|
|
21655
22516
|
} catch (err) {
|
|
21656
|
-
const bundleDetail =
|
|
22517
|
+
const bundleDetail = assumeRoleDetail(err, "STS AssumeRole (fromS3 bundle download)");
|
|
21657
22518
|
logger.warn(`--assume-role: STS AssumeRole(${flattenToOneLine(assumeRoleArn)}) failed for the fromS3 bundle download: ${bundleDetail}. Falling back to ${options.profile ? `--profile ${options.profile}` : "the default credentials"}.`);
|
|
21658
22519
|
}
|
|
21659
22520
|
const bundle = await downloadAndExtractS3Bundle(location, {
|
|
@@ -21852,7 +22713,7 @@ async function applyAgentCoreCredentialEnv(dockerEnv, args) {
|
|
|
21852
22713
|
if (stsRegion) dockerEnv["AWS_REGION"] = stsRegion;
|
|
21853
22714
|
assumeSucceeded = true;
|
|
21854
22715
|
} catch (err) {
|
|
21855
|
-
const assumeDetail =
|
|
22716
|
+
const assumeDetail = assumeRoleDetail(err, "STS AssumeRole");
|
|
21856
22717
|
logger.warn(`--assume-role: STS AssumeRole(${flattenToOneLine(args.assumeRoleArn)}) failed: ${assumeDetail}. Falling back to the developer's shell credentials.`);
|
|
21857
22718
|
}
|
|
21858
22719
|
}
|
|
@@ -40142,4 +41003,4 @@ function addStudioSpecificOptions(cmd) {
|
|
|
40142
41003
|
|
|
40143
41004
|
//#endregion
|
|
40144
41005
|
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 };
|
|
40145
|
-
//# sourceMappingURL=local-studio-
|
|
41006
|
+
//# sourceMappingURL=local-studio-CP1PAUS4.js.map
|