cdk-local 0.149.2 → 0.149.4
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/dist/cli.js +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +5 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/{local-studio-CSIZELLP.js → local-studio-Cdq8klwg.js} +1037 -327
- package/dist/local-studio-Cdq8klwg.js.map +1 -0
- package/dist/local-studio-D-j3TZWX.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/local-studio-CSIZELLP.js.map +0 -1
|
@@ -1699,6 +1699,393 @@ function effectiveAssumeRoleArn(logicalId, opt) {
|
|
|
1699
1699
|
return opt.perLambda?.[logicalId] ?? opt.globalArn;
|
|
1700
1700
|
}
|
|
1701
1701
|
|
|
1702
|
+
//#endregion
|
|
1703
|
+
//#region src/utils/assembly-path.ts
|
|
1704
|
+
/**
|
|
1705
|
+
* `true` when `candidate` names something strictly beneath `base`, both given
|
|
1706
|
+
* as already-resolved absolute paths.
|
|
1707
|
+
*
|
|
1708
|
+
* The `..` test is SEPARATOR-AWARE rather than a bare `startsWith('..')`,
|
|
1709
|
+
* which would also reject a legitimate sibling named `..foo`. The empty-string
|
|
1710
|
+
* case is `base` itself; callers decide whether that counts as an escape,
|
|
1711
|
+
* because an asset path legitimately names a DIRECTORY while a template path
|
|
1712
|
+
* does not.
|
|
1713
|
+
*/
|
|
1714
|
+
function isInside(base, candidate) {
|
|
1715
|
+
const rel = relative(base, candidate);
|
|
1716
|
+
if (rel === "" || rel === "..") return false;
|
|
1717
|
+
if (rel.startsWith(`..${sep}`)) return false;
|
|
1718
|
+
return !isAbsolute(rel);
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* `realpath(3)`, or `undefined` for a path that does not fully resolve.
|
|
1722
|
+
*
|
|
1723
|
+
* `.native` is load-bearing and must not be simplified to `fs.realpathSync`.
|
|
1724
|
+
* The plain form is a JavaScript walker that folds `..` LEXICALLY, so it
|
|
1725
|
+
* disagrees with the kernel on a target carrying a `..` after a symlinked
|
|
1726
|
+
* component — answering `ENOENT` for a path the kernel resolves, and on a
|
|
1727
|
+
* case-insensitive filesystem (APFS by default) it can spin on the same shape.
|
|
1728
|
+
* `.native` is libuv's `realpath(3)` and throws the same
|
|
1729
|
+
* `ENOENT` / `ELOOP` / `EACCES`.
|
|
1730
|
+
*/
|
|
1731
|
+
function tryRealpath(p) {
|
|
1732
|
+
try {
|
|
1733
|
+
return realpathSync.native(p);
|
|
1734
|
+
} catch {
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
/** `fs.readlinkSync`, or `undefined` when `p` is not a symbolic link. */
|
|
1739
|
+
function tryReadlink(p) {
|
|
1740
|
+
try {
|
|
1741
|
+
return readlinkSync(p);
|
|
1742
|
+
} catch {
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Link follows before the walk gives up and reports the path as unresolvable.
|
|
1748
|
+
* It does NOT refuse: an exhausted budget answers `undefined`, which leaves
|
|
1749
|
+
* the symlink arm silent and the lexical verdict standing. Safe because the OS
|
|
1750
|
+
* gives up first (macOS caps at 32, `ELOOP`), so a chain that reaches this cap
|
|
1751
|
+
* is one nothing can open anyway.
|
|
1752
|
+
*/
|
|
1753
|
+
const MAX_LINK_HOPS = 40;
|
|
1754
|
+
/**
|
|
1755
|
+
* Unresolvable path COMPONENTS the climb walks before giving up. Bounds the
|
|
1756
|
+
* recursion below, which is one frame per component, so a pathological value
|
|
1757
|
+
* cannot raise a `RangeError` from inside {@link tryRealpath}'s own `try` (its
|
|
1758
|
+
* `catch` would swallow the crash into a silent `undefined`).
|
|
1759
|
+
*
|
|
1760
|
+
* KNOW THAT EXHAUSTING THIS FAILS OPEN, and why that is accepted rather than
|
|
1761
|
+
* unnoticed. Unlike {@link MAX_LINK_HOPS}, which has the OS's own `ELOOP` cap
|
|
1762
|
+
* behind it, nothing else stops a path of 1 001 absent components: the climb
|
|
1763
|
+
* gives up, the symlink arm goes silent, and a value that would land outside
|
|
1764
|
+
* reads as CONTAINED on the lexical verdict alone. It is not reachable in
|
|
1765
|
+
* effect — such a path cannot exist, so `cdkl invoke`'s `existsSync` refuses it
|
|
1766
|
+
* and `start-api`'s `docker run` fails to mount it — but that safety lives in
|
|
1767
|
+
* the CONSUMERS, not here. A consumer that neither checks existence nor mounts
|
|
1768
|
+
* would need its own answer.
|
|
1769
|
+
*/
|
|
1770
|
+
const MAX_PATH_COMPONENTS = 1e3;
|
|
1771
|
+
/**
|
|
1772
|
+
* Where `target` REALLY points, for a path that may not exist yet.
|
|
1773
|
+
*
|
|
1774
|
+
* `realpathSync` answers only for a path that fully resolves, and it throws
|
|
1775
|
+
* `ENOENT` for a DANGLING symbolic link exactly as it does for an absent file.
|
|
1776
|
+
* `cdkl start-api` resolves an asset path without an existence check, so an
|
|
1777
|
+
* absent or dangling component must not silence the symlink arm.
|
|
1778
|
+
*
|
|
1779
|
+
* Each unresolvable component is therefore handled by hand: climb to the
|
|
1780
|
+
* deepest ancestor that DOES resolve, then re-apply the remaining components,
|
|
1781
|
+
* following any symbolic link with `readlink` and re-resolving its target.
|
|
1782
|
+
* `undefined` means the walk could not resolve the path at all.
|
|
1783
|
+
*
|
|
1784
|
+
* For a path that FULLY RESOLVES the answer is the kernel's and is exact. For
|
|
1785
|
+
* one that does not exist yet this is a best-effort MODEL of kernel
|
|
1786
|
+
* resolution; the known edge is a `..` INSIDE an unresolvable link's target,
|
|
1787
|
+
* folded lexically here while the kernel folds it only after following each
|
|
1788
|
+
* preceding component. That edge is benign for THESE callers rather than in
|
|
1789
|
+
* general: it needs the target to be absent, and both consumers resolve the
|
|
1790
|
+
* path and then open or mount it, so the divergence is between this model and
|
|
1791
|
+
* a path nothing can reach. A caller that CREATED a file through such a path
|
|
1792
|
+
* would need an `lstat` of its own.
|
|
1793
|
+
*
|
|
1794
|
+
* `EACCES` is folded into "does not resolve" along with `ENOENT`, so a link
|
|
1795
|
+
* under a directory this process cannot traverse reads as contained. Same
|
|
1796
|
+
* bound: the consumer's own `existsSync` / mount runs as the same user and
|
|
1797
|
+
* fails identically.
|
|
1798
|
+
*/
|
|
1799
|
+
function resolveThroughLinks(target, hops = 0, climbs = 0) {
|
|
1800
|
+
const direct = tryRealpath(target);
|
|
1801
|
+
if (direct !== void 0) return direct;
|
|
1802
|
+
if (hops >= MAX_LINK_HOPS) return void 0;
|
|
1803
|
+
if (climbs >= MAX_PATH_COMPONENTS) return void 0;
|
|
1804
|
+
const parent = dirname(target);
|
|
1805
|
+
if (parent === target) return void 0;
|
|
1806
|
+
const realParent = resolveThroughLinks(parent, hops, climbs + 1);
|
|
1807
|
+
if (realParent === void 0) return void 0;
|
|
1808
|
+
const link = tryReadlink(target);
|
|
1809
|
+
if (link === void 0) return join(realParent, basename(target));
|
|
1810
|
+
return resolveThroughLinks(resolve(realParent, link), hops + 1, climbs);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Resolve an assembly-supplied `candidate` against `dir` and report whether
|
|
1814
|
+
* the result stays inside it.
|
|
1815
|
+
*
|
|
1816
|
+
* The lexical arm joins exactly the way the call sites used to (`path.join`,
|
|
1817
|
+
* NOT `path.resolve`), so the verdict is about the path the caller will
|
|
1818
|
+
* actually use. `join`'s handling of an absolute candidate makes this arm
|
|
1819
|
+
* strictly more permissive than a `resolve`-based one would be; an absolute
|
|
1820
|
+
* value is therefore answered by {@link absoluteAssemblyPathEscape} instead,
|
|
1821
|
+
* because this function structurally cannot.
|
|
1822
|
+
*
|
|
1823
|
+
* The SYMLINK arm exists because the lexical arm alone leaves an equivalent
|
|
1824
|
+
* hole: `cdk.out/link -> /etc` plus a candidate of `link/passwd` is lexically
|
|
1825
|
+
* contained and still reaches `/etc/passwd`. Both sides go through
|
|
1826
|
+
* {@link resolveThroughLinks}, so an assembly directory REACHED through a link
|
|
1827
|
+
* (macOS spells `/tmp` as `/private/tmp`) is unaffected.
|
|
1828
|
+
*
|
|
1829
|
+
* The verdict is about the assembly AS IT SITS ON DISK. It is NOT a defence
|
|
1830
|
+
* against a process rewriting that directory concurrently — the caller uses
|
|
1831
|
+
* the path after this returns.
|
|
1832
|
+
*/
|
|
1833
|
+
function resolveAssemblyPath(dir, candidate, options) {
|
|
1834
|
+
const base = resolve(dir);
|
|
1835
|
+
const bound = options?.containWithin === void 0 ? base : resolve(options.containWithin);
|
|
1836
|
+
const joined = resolve(join(base, candidate));
|
|
1837
|
+
if (!isInside(bound, joined)) return {
|
|
1838
|
+
contained: false,
|
|
1839
|
+
escape: "lexical",
|
|
1840
|
+
path: joined
|
|
1841
|
+
};
|
|
1842
|
+
const realBound = resolveThroughLinks(bound);
|
|
1843
|
+
if (realBound !== void 0) {
|
|
1844
|
+
const realTarget = resolveThroughLinks(joined);
|
|
1845
|
+
if (realTarget !== void 0 && !isInside(realBound, realTarget)) return {
|
|
1846
|
+
contained: false,
|
|
1847
|
+
escape: "symlink",
|
|
1848
|
+
path: joined,
|
|
1849
|
+
realPath: realTarget
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
return {
|
|
1853
|
+
contained: true,
|
|
1854
|
+
path: joined
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Whether an ALREADY-ABSOLUTE assembly-supplied path lies outside `bound`.
|
|
1859
|
+
*
|
|
1860
|
+
* {@link resolveAssemblyPath} cannot answer this, and the reason is structural
|
|
1861
|
+
* rather than an oversight: its lexical arm joins with `path.join`, which does
|
|
1862
|
+
* NOT honour a leading separator, so an absolute candidate is folded INTO the
|
|
1863
|
+
* directory and the verdict would describe a path no caller opens. A site that
|
|
1864
|
+
* HONOURS an absolute value needs the verdict about the value itself.
|
|
1865
|
+
*
|
|
1866
|
+
* The callers are `cdkl invoke`'s and `cdkl start-api`'s
|
|
1867
|
+
* `Metadata['aws:asset:path']` resolution. Those honour an absolute path
|
|
1868
|
+
* because `cdk synth --no-staging` emits one — CDK writes the asset's absolute
|
|
1869
|
+
* SOURCE directory under `aws:cdk:disable-asset-staging`, usually outside the
|
|
1870
|
+
* outdir — and they WARN rather than refuse when it leaves the bound, so this
|
|
1871
|
+
* returns a verdict rather than throwing. The asset-MANIFEST readers that
|
|
1872
|
+
* honour an absolute value take the same verdict: `'honour-warn'` (the
|
|
1873
|
+
* start-cloudfront origin) warns when the value is a folder inside the user's
|
|
1874
|
+
* project not under a credential or version-control directory, and refuses
|
|
1875
|
+
* otherwise, `'honour'` (the soft-reload
|
|
1876
|
+
* sources) refuses (#745).
|
|
1877
|
+
*
|
|
1878
|
+
* It lives HERE so the containment rule has one spelling: it reuses this
|
|
1879
|
+
* module's own {@link isInside} and {@link resolveThroughLinks}, symlink arm
|
|
1880
|
+
* included, rather than letting a caller re-spell `path.relative` and drift.
|
|
1881
|
+
*
|
|
1882
|
+
* `bound` itself is NOT an escape, unlike in {@link resolveAssemblyPath},
|
|
1883
|
+
* where an empty `path.relative` means "names the directory rather than a file
|
|
1884
|
+
* inside it". An asset path legitimately names a DIRECTORY, so a value equal
|
|
1885
|
+
* to the bound is inside it and reporting it as outside would be false.
|
|
1886
|
+
*
|
|
1887
|
+
* THE REAL PATHS DECIDE IN BOTH DIRECTIONS, which is the one place this
|
|
1888
|
+
* deliberately does more than {@link resolveAssemblyPath}'s lexical-first
|
|
1889
|
+
* ordering. The lexical verdict here is about a SPELLING, and two spellings of
|
|
1890
|
+
* the same directory are common rather than exotic: macOS resolves `/tmp` to
|
|
1891
|
+
* `/private/tmp` and `/var` to `/private/var`, and a user may symlink `cdk.out`
|
|
1892
|
+
* itself. With an outdir of `/tmp/cdk.out` and a `--no-staging` value of
|
|
1893
|
+
* `/private/tmp/cdk.out/src`, a lexical-only verdict cries "pointing outside
|
|
1894
|
+
* the assembly ... treat this assembly as untrusted" about an asset that is
|
|
1895
|
+
* plainly inside it — and this arm exists to make an UNEXPECTED path visible,
|
|
1896
|
+
* so a false alarm is the failure that costs it its meaning.
|
|
1897
|
+
*
|
|
1898
|
+
* Exonerating requires the kernel to answer for BOTH operands: an unresolvable
|
|
1899
|
+
* one leaves the lexical verdict standing, so the arm stays loud when it cannot
|
|
1900
|
+
* see. (`resolveAssemblyPath` cannot take the same shape: its verdict is about
|
|
1901
|
+
* a path built with `path.join`, and letting a real path overrule the lexical
|
|
1902
|
+
* `..` there would answer about a location the caller never opens.)
|
|
1903
|
+
*/
|
|
1904
|
+
function absoluteAssemblyPathEscape(bound, absolutePath) {
|
|
1905
|
+
const resolvedBound = resolve(bound);
|
|
1906
|
+
const target = resolve(absolutePath);
|
|
1907
|
+
const lexicallyOutside = !isInside(resolvedBound, target) && target !== resolvedBound;
|
|
1908
|
+
const realBound = resolveThroughLinks(resolvedBound);
|
|
1909
|
+
const realTarget = resolveThroughLinks(target);
|
|
1910
|
+
const reallyOutside = realBound === void 0 || realTarget === void 0 ? void 0 : !isInside(realBound, realTarget) && realTarget !== realBound;
|
|
1911
|
+
if (lexicallyOutside) {
|
|
1912
|
+
if (reallyOutside === false) return void 0;
|
|
1913
|
+
return {
|
|
1914
|
+
contained: false,
|
|
1915
|
+
escape: "lexical",
|
|
1916
|
+
path: target
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
if (reallyOutside === true) return {
|
|
1920
|
+
contained: false,
|
|
1921
|
+
escape: "symlink",
|
|
1922
|
+
path: target,
|
|
1923
|
+
realPath: realTarget
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Whether `candidate` names the SAME directory as `bound` — lexically, or
|
|
1928
|
+
* through a symbolic link on either side.
|
|
1929
|
+
*
|
|
1930
|
+
* An asset source legitimately names a DIRECTORY, so a value that resolves
|
|
1931
|
+
* onto the output directory itself is not an escape; but it hands the WHOLE
|
|
1932
|
+
* assembly to the sink, which is worth a line. A caller that re-spells this as
|
|
1933
|
+
* `resolve(a) === resolve(b)` misses every second spelling of the bound
|
|
1934
|
+
* (macOS `/tmp` vs `/private/tmp`, a symlinked `cdk.out`), and
|
|
1935
|
+
* {@link absoluteAssemblyPathEscape} exonerates exactly those spellings as
|
|
1936
|
+
* inside — so the equality beside it would stay silent for them.
|
|
1937
|
+
*
|
|
1938
|
+
* Conservative on failure: an unresolvable side answers from the lexical
|
|
1939
|
+
* comparison alone, so it can only say "not the same", never wrongly claim
|
|
1940
|
+
* identity.
|
|
1941
|
+
*/
|
|
1942
|
+
function namesTheSameDirectory(bound, candidate) {
|
|
1943
|
+
const resolvedBound = resolve(bound);
|
|
1944
|
+
const target = resolve(candidate);
|
|
1945
|
+
if (target === resolvedBound) return true;
|
|
1946
|
+
const realBound = resolveThroughLinks(resolvedBound);
|
|
1947
|
+
const realTarget = resolveThroughLinks(target);
|
|
1948
|
+
return realBound !== void 0 && realTarget !== void 0 && realBound === realTarget;
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* The escape verdict for an assembly-supplied path WITHOUT throwing, taking
|
|
1952
|
+
* whichever arm the value's own shape calls for: {@link absoluteAssemblyPathEscape}
|
|
1953
|
+
* for an absolute value, {@link resolveAssemblyPath} for a relative one.
|
|
1954
|
+
*
|
|
1955
|
+
* For a caller that only WARNS and has many values to judge — the BuildKit
|
|
1956
|
+
* passthroughs in `src/assets/buildkit-passthrough-warnings.ts`. `base` is what
|
|
1957
|
+
* a relative value resolves against, `bound` what it must stay inside.
|
|
1958
|
+
* Returns `undefined` when the value is fine, including when it names `bound`
|
|
1959
|
+
* itself ({@link namesTheSameDirectory}).
|
|
1960
|
+
*/
|
|
1961
|
+
function assemblyPathEscape(base, bound, candidate) {
|
|
1962
|
+
if (isAbsolute(candidate)) return absoluteAssemblyPathEscape(bound, candidate);
|
|
1963
|
+
const resolved = resolveAssemblyPath(base, candidate, { containWithin: bound });
|
|
1964
|
+
if (resolved.contained || namesTheSameDirectory(bound, resolved.path)) return void 0;
|
|
1965
|
+
return resolved;
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* The shared tail of a containment refusal: what the value resolved to, what
|
|
1969
|
+
* it escaped, and why that means the assembly is not CDK-generated. The call
|
|
1970
|
+
* site supplies its own subject ("Lambda 'X' has ... which ") and its own
|
|
1971
|
+
* error class. `action` completes "Refusing to ..." and is REQUIRED rather
|
|
1972
|
+
* than defaulted: a default is a branch no caller takes, so it can neither be
|
|
1973
|
+
* fenced nor be right for the next caller.
|
|
1974
|
+
*
|
|
1975
|
+
* `provenanceOverride` replaces that whole closing sentence for a caller that
|
|
1976
|
+
* does NOT refuse — the BuildKit passthrough warnings, where "Refusing to ..."
|
|
1977
|
+
* would be a false statement about a value that is forwarded anyway.
|
|
1978
|
+
*/
|
|
1979
|
+
function renderAssemblyPathEscape(escape, dir, action, provenanceOverride) {
|
|
1980
|
+
const provenance = provenanceOverride ?? `CDK emits assembly paths that stay inside the assembly directory; one that leaves it indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to ${action}.`;
|
|
1981
|
+
const base = resolve(dir);
|
|
1982
|
+
const realBase = resolveThroughLinks(base) ?? base;
|
|
1983
|
+
const shownPath = displayUntrustedValue(escape.path);
|
|
1984
|
+
const shownBase = displayUntrustedValue(base);
|
|
1985
|
+
if (escape.escape === "symlink") {
|
|
1986
|
+
if (escape.realPath === realBase) return `resolves to ${shownPath}, a symbolic link to the directory ${shownBase} itself rather than to a path inside it. ${provenance}`;
|
|
1987
|
+
return `resolves to ${shownPath}, which leads through a symbolic link to ${displayUntrustedValue(escape.realPath)}, outside ${shownBase}. ${provenance}`;
|
|
1988
|
+
}
|
|
1989
|
+
if (escape.path === base) return `names the directory ${shownBase} itself rather than a path inside it. ${provenance}`;
|
|
1990
|
+
return `resolves to ${shownPath}, outside ${shownBase}. ${provenance}`;
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* A letter or digit that neither draws as a blank nor reads as a quote.
|
|
1994
|
+
* `\p{L}` alone admits both, so two classes are carved out: the
|
|
1995
|
+
* default-ignorables, which hold letters that draw as a blank (the Hangul
|
|
1996
|
+
* fillers U+3164 and U+FFA0), and the quote-shaped letters — the Spacing
|
|
1997
|
+
* Modifier Letters block (U+02BA reads as `"`, U+02BC as `'`) plus the ones
|
|
1998
|
+
* outside it (U+0374, U+0559, U+07F4-U+07F5, U+A78B-U+A78C, and the halfwidth
|
|
1999
|
+
* sound marks U+FF9E-U+FF9F). Not all of `\p{Lm}`: U+30FC is in it, and it is
|
|
2000
|
+
* an ordinary character of a Japanese directory name.
|
|
2001
|
+
*/
|
|
2002
|
+
const VISIBLE_LETTER = new RegExp(String.raw`^(?![\p{Default_Ignorable_Code_Point}\u02b0-\u02ff\u0374\u0559\u07f4\u07f5\ua78b\ua78c\uff9e\uff9f])[\p{L}\p{N}]$`, "u");
|
|
2003
|
+
/**
|
|
2004
|
+
* A combining mark, which counts only DIRECTLY after a visible letter (or a
|
|
2005
|
+
* mark that itself counted): on a space or on punctuation it draws on its own,
|
|
2006
|
+
* and U+030B / U+030E there look like a quote.
|
|
2007
|
+
*/
|
|
2008
|
+
const COMBINING_MARK = /^\p{M}$/u;
|
|
2009
|
+
/**
|
|
2010
|
+
* A default-ignorable code point, which draws as NOTHING. Tested before the
|
|
2011
|
+
* mark rule, because some are combining marks (U+034F, U+17B4, U+180B, the
|
|
2012
|
+
* variation selectors U+FE00-U+FE0F and U+E0100-U+E01EF): after a letter they
|
|
2013
|
+
* would otherwise pass as bare, and `/home/me/.ss\u034fh` would print exactly
|
|
2014
|
+
* like `.ssh` while naming a different path. The host's classifier
|
|
2015
|
+
* (go-to-k/cdkd#3509) does not carve these out; this one does.
|
|
2016
|
+
*/
|
|
2017
|
+
const DEFAULT_IGNORABLE = /^\p{Default_Ignorable_Code_Point}$/u;
|
|
2018
|
+
/**
|
|
2019
|
+
* The ASCII a bare value may carry besides letters and digits. Everything else
|
|
2020
|
+
* — any whitespace, any quote, any other symbol — takes the boundary, which
|
|
2021
|
+
* costs a legitimate path nothing but a pair of quotes.
|
|
2022
|
+
*/
|
|
2023
|
+
const BARE_PUNCTUATION = /^[/\\._~+@:=-]$/;
|
|
2024
|
+
/**
|
|
2025
|
+
* Classify each code point: `bare` (allowed in a bare value), `shown` (shown
|
|
2026
|
+
* as itself inside the boundary), or `escaped`. An ALLOWLIST, because a
|
|
2027
|
+
* denylist has to enumerate every character that draws as a blank or reads as
|
|
2028
|
+
* a quote.
|
|
2029
|
+
*
|
|
2030
|
+
* Each test is ONE character and this walks the value — never `^(...)+$` over
|
|
2031
|
+
* the whole of it: alternatives that overlap under a quantifier backtrack
|
|
2032
|
+
* exponentially on a long value that fails near its end.
|
|
2033
|
+
*/
|
|
2034
|
+
function classify(chars) {
|
|
2035
|
+
let afterLetter = false;
|
|
2036
|
+
return chars.map((ch) => {
|
|
2037
|
+
if (DEFAULT_IGNORABLE.test(ch)) {
|
|
2038
|
+
afterLetter = false;
|
|
2039
|
+
return "escaped";
|
|
2040
|
+
}
|
|
2041
|
+
if (COMBINING_MARK.test(ch)) return afterLetter ? "bare" : "escaped";
|
|
2042
|
+
afterLetter = VISIBLE_LETTER.test(ch);
|
|
2043
|
+
if (afterLetter || BARE_PUNCTUATION.test(ch)) return "bare";
|
|
2044
|
+
return ch >= " " && ch <= "~" ? "shown" : "escaped";
|
|
2045
|
+
});
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Render an untrusted value — a filesystem path, identifier or command an
|
|
2049
|
+
* assembly chose, or a key a request supplied — into cdk-local's own prose
|
|
2050
|
+
* (go-to-k/cdk-local#758; the host's `displayAssemblyPath`, go-to-k/cdkd#3509).
|
|
2051
|
+
* The caller writes NO quotes around the result.
|
|
2052
|
+
*
|
|
2053
|
+
* `sanitizeServiceExceptionMessage` alone is not enough inside quotes of ours:
|
|
2054
|
+
* it flattens control characters and passes `'`, so a value carrying one
|
|
2055
|
+
* closed the quote and wrote a clause of its own into the message. This keeps
|
|
2056
|
+
* a plain value bare and puts any other one inside a JSON string literal.
|
|
2057
|
+
* Inside it, `"` and `\` are escaped as JSON escapes them, and so is every
|
|
2058
|
+
* character that is neither printable ASCII nor a visible letter — a curly or
|
|
2059
|
+
* fullwidth quote that could pass for the boundary's own closing `"`, and a
|
|
2060
|
+
* blank that could pass for a space. The result stays valid JSON: `JSON.parse`
|
|
2061
|
+
* returns the sanitized value.
|
|
2062
|
+
*
|
|
2063
|
+
* A legitimate value with a space or any symbol outside `/ \ . _ ~ + @ : = -`
|
|
2064
|
+
* (`/Users/me/My Project/cdk.out`) renders quoted; a non-ASCII letter is
|
|
2065
|
+
* shown as itself, and a non-letter non-ASCII character (an emoji, `©`) as
|
|
2066
|
+
* its `\u` escape. A value the sanitizer ALTERED (a control character
|
|
2067
|
+
* flattened, an over-long value truncated) did not arrive plain, so it always
|
|
2068
|
+
* takes the boundary. The empty string renders as `""`.
|
|
2069
|
+
*
|
|
2070
|
+
* The sanitizer also CAPS the value at 512 code points, so a longer path
|
|
2071
|
+
* prints truncated (inside the boundary, with its true length named). That
|
|
2072
|
+
* is deliberate: an unbounded assembly-chosen value would otherwise make the
|
|
2073
|
+
* log line as long as the attacker likes, and a real path is far shorter.
|
|
2074
|
+
*/
|
|
2075
|
+
function displayUntrustedValue(value) {
|
|
2076
|
+
const clean = sanitizeServiceExceptionMessage(value);
|
|
2077
|
+
const chars = Array.from(clean);
|
|
2078
|
+
const kinds = classify(chars);
|
|
2079
|
+
if (clean === value && chars.length > 0 && kinds.every((k) => k === "bare")) return clean;
|
|
2080
|
+
let body = "";
|
|
2081
|
+
chars.forEach((ch, i) => {
|
|
2082
|
+
if (ch === "\"" || ch === "\\") body += `\\${ch}`;
|
|
2083
|
+
else if (kinds[i] !== "escaped") body += ch;
|
|
2084
|
+
else for (let u = 0; u < ch.length; u++) body += `\\u${ch.charCodeAt(u).toString(16).padStart(4, "0")}`;
|
|
2085
|
+
});
|
|
2086
|
+
return `"${body}"`;
|
|
2087
|
+
}
|
|
2088
|
+
|
|
1702
2089
|
//#endregion
|
|
1703
2090
|
//#region src/utils/error-handler.ts
|
|
1704
2091
|
/**
|
|
@@ -2383,247 +2770,6 @@ function stringifyValue(value) {
|
|
|
2383
2770
|
}
|
|
2384
2771
|
}
|
|
2385
2772
|
|
|
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
|
-
|
|
2627
2773
|
//#endregion
|
|
2628
2774
|
//#region src/local/lambda-resolver.ts
|
|
2629
2775
|
/**
|
|
@@ -2949,7 +3095,7 @@ function resolveAssetCodeDirectory(manifestDir, assetPath, wrapError, assetOutdi
|
|
|
2949
3095
|
if (isAbsolute(assetPath)) {
|
|
2950
3096
|
const absolute = resolve(assetPath);
|
|
2951
3097
|
const escape = absoluteAssemblyPathEscape(assetOutdir, absolute);
|
|
2952
|
-
if (escape !== void 0) getLogger().warn(`Lambda
|
|
3098
|
+
if (escape !== void 0) getLogger().warn(`Lambda ${displayUntrustedValue(logicalId)} has an absolute Metadata['aws:asset:path'] pointing outside the assembly: ${displayUntrustedValue(absolute)}` + (escape.escape === "symlink" ? ` (through a symbolic link to ${displayUntrustedValue(escape.realPath)})` : "") + `. ${getEmbedConfig().productName} will read that directory and expose its contents to the container — bind-mounted read-only, or copied when the asset is a .zip or one of several merged layers — where the code in this assembly can read it. This is what cdk synth --no-staging emits, and is expected for it; if you did not synthesize with that flag, treat this assembly as untrusted.`);
|
|
2953
3099
|
warnOutsideNamedDirectory(assetOutdir, absolute);
|
|
2954
3100
|
return absolute;
|
|
2955
3101
|
}
|
|
@@ -2961,7 +3107,7 @@ function resolveAssetCodeDirectory(manifestDir, assetPath, wrapError, assetOutdi
|
|
|
2961
3107
|
if (!resolved.contained) {
|
|
2962
3108
|
const climbsOut = assetPath === ".." || assetPath.startsWith(`..${sep}`) || assetPath.startsWith("../");
|
|
2963
3109
|
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
|
|
3110
|
+
throw wrapError(`Lambda ${displayUntrustedValue(logicalId)} has Metadata['aws:asset:path']=${displayUntrustedValue(assetPath)} which ${renderAssemblyPathEscape(resolved, assetOutdir, "mount it")}${stageHint}`);
|
|
2965
3111
|
}
|
|
2966
3112
|
warnOutsideNamedDirectory(assetOutdir, resolved.path);
|
|
2967
3113
|
return resolved.path;
|
|
@@ -3049,6 +3195,30 @@ function assetPathDirs(stack) {
|
|
|
3049
3195
|
};
|
|
3050
3196
|
}
|
|
3051
3197
|
/**
|
|
3198
|
+
* The containment bound for the `--watch` reload readers, which re-read
|
|
3199
|
+
* `<output>/<stack>.assets.json` after each synth (their BASE is
|
|
3200
|
+
* {@link watchManifestDir}).
|
|
3201
|
+
*
|
|
3202
|
+
* A stack carrying `assetOutdir` (always, through `AssemblyReader`) gets the
|
|
3203
|
+
* same Stage-aware bound as {@link assetPathDirs}. One without it gets
|
|
3204
|
+
* `outputDir` itself, NOT `assetPathDirs`'s fallback, which can be the process
|
|
3205
|
+
* cwd — a DISJOINT bound that refuses every value with a message blaming the
|
|
3206
|
+
* assembly. `outputDir` is the user's own flag, never assembly-supplied, so it
|
|
3207
|
+
* is a legitimate bound.
|
|
3208
|
+
*/
|
|
3209
|
+
function outputAssetBound(stack, outputDir) {
|
|
3210
|
+
return stack.assetOutdir === void 0 || stack.assetOutdir === "" ? outputDir : assetPathDirs(stack).assetOutdir;
|
|
3211
|
+
}
|
|
3212
|
+
/**
|
|
3213
|
+
* Where a `--watch` reader resolves a RELATIVE manifest value from: the
|
|
3214
|
+
* manifest's own directory, exactly as the build does — so a value resolves
|
|
3215
|
+
* to the same directory the running image was built from. Falls back to the
|
|
3216
|
+
* user's `--output` for a stack record without a manifest path.
|
|
3217
|
+
*/
|
|
3218
|
+
function watchManifestDir(stack, outputDir) {
|
|
3219
|
+
return stack.assetManifestPath ? dirname(stack.assetManifestPath) : outputDir;
|
|
3220
|
+
}
|
|
3221
|
+
/**
|
|
3052
3222
|
* The real assembly ROOT for a `--app` that names a `cdk.Stage`
|
|
3053
3223
|
* SUB-assembly.
|
|
3054
3224
|
*
|
|
@@ -3191,7 +3361,7 @@ function warnOutsideNamedDirectory(assetOutdir, resolved) {
|
|
|
3191
3361
|
if (absoluteAssemblyPathEscape(named, resolved) === void 0) return;
|
|
3192
3362
|
if (warnedOutsideNamed.has(resolved)) return;
|
|
3193
3363
|
warnedOutsideNamed.add(resolved);
|
|
3194
|
-
getLogger().warn(
|
|
3364
|
+
getLogger().warn(`${displayUntrustedValue(resolved)} is outside ${displayUntrustedValue(named)}, the directory --app named. It is inside the assembly root derived from it, so ${getEmbedConfig().productName} is using it.`);
|
|
3195
3365
|
}
|
|
3196
3366
|
/**
|
|
3197
3367
|
* Say that the assembly root was DERIVED rather than given.
|
|
@@ -3204,7 +3374,7 @@ function warnOutsideNamedDirectory(assetOutdir, resolved) {
|
|
|
3204
3374
|
function warnDerivedAssemblyRoot(named, derived) {
|
|
3205
3375
|
if (warnedDerivedRoots.has(derived)) return;
|
|
3206
3376
|
warnedDerivedRoots.add(derived);
|
|
3207
|
-
getLogger().warn(
|
|
3377
|
+
getLogger().warn(`${displayUntrustedValue(named)} is a cdk.Stage sub-assembly, so ${getEmbedConfig().productName} is treating its parent ${displayUntrustedValue(derived)} as the assembly root — that is where cdk synth stages a Stage's assets. Everything under that parent is now inside the containment bound, including siblings of the directory you named. If you did not expect the wider directory, point --app at the app's own output directory instead.`);
|
|
3208
3378
|
}
|
|
3209
3379
|
/**
|
|
3210
3380
|
* Resolve the local directory that corresponds to a function's deployed
|
|
@@ -3223,14 +3393,14 @@ function warnDerivedAssemblyRoot(named, derived) {
|
|
|
3223
3393
|
*/
|
|
3224
3394
|
function resolveAssetCodePath$1(stack, logicalId, resource, options = {}) {
|
|
3225
3395
|
const assetPath = resource.Metadata?.["aws:asset:path"];
|
|
3226
|
-
if (typeof assetPath !== "string" || assetPath.length === 0) throw new LocalInvokeResolutionError(`Lambda
|
|
3396
|
+
if (typeof assetPath !== "string" || assetPath.length === 0) throw new LocalInvokeResolutionError(`Lambda ${displayUntrustedValue(logicalId)} has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} invoke needs this hint to find the local asset directory. Re-synthesize the app (without \`--output <stale-dir>\`) and retry.`);
|
|
3227
3397
|
const { manifestDir, assetOutdir } = assetPathDirs(stack);
|
|
3228
3398
|
const abs = resolveAssetCodeDirectory(manifestDir, assetPath, (message) => new LocalInvokeResolutionError(message), assetOutdir, logicalId);
|
|
3229
|
-
if (!existsSync(abs)) throw new LocalInvokeResolutionError(`Lambda
|
|
3399
|
+
if (!existsSync(abs)) throw new LocalInvokeResolutionError(`Lambda ${displayUntrustedValue(logicalId)} asset path ${displayUntrustedValue(abs)} does not exist. Re-synthesize the app and retry.`);
|
|
3230
3400
|
const stat = statSync(abs);
|
|
3231
3401
|
if (stat.isDirectory()) return abs;
|
|
3232
3402
|
if (options.allowZip && stat.isFile() && abs.toLowerCase().endsWith(".zip")) return abs;
|
|
3233
|
-
throw new LocalInvokeResolutionError(`Lambda
|
|
3403
|
+
throw new LocalInvokeResolutionError(`Lambda ${displayUntrustedValue(logicalId)} asset path ${displayUntrustedValue(abs)} is not a directory` + (options.allowZip ? " or a .zip archive" : "") + ". Re-synthesize the app and retry.");
|
|
3234
3404
|
}
|
|
3235
3405
|
/**
|
|
3236
3406
|
* Turn a resolved function-code asset path into a directory ready to
|
|
@@ -3249,14 +3419,14 @@ function resolveAssetCodePath$1(stack, logicalId, resource, options = {}) {
|
|
|
3249
3419
|
* existing tmpdir cleanup.
|
|
3250
3420
|
*/
|
|
3251
3421
|
function materializeAssetCodeDir(codePath) {
|
|
3252
|
-
if (!existsSync(codePath)) throw new LocalInvokeResolutionError(`Lambda asset path
|
|
3422
|
+
if (!existsSync(codePath)) throw new LocalInvokeResolutionError(`Lambda asset path ${displayUntrustedValue(codePath)} does not exist. Re-synthesize the app and retry.`);
|
|
3253
3423
|
if (statSync(codePath).isDirectory()) return { dir: codePath };
|
|
3254
3424
|
const zipBytes = readFileSync(codePath);
|
|
3255
3425
|
let files;
|
|
3256
3426
|
try {
|
|
3257
3427
|
files = unzipSync(zipBytes);
|
|
3258
3428
|
} catch (err) {
|
|
3259
|
-
throw new LocalInvokeResolutionError(`Lambda asset
|
|
3429
|
+
throw new LocalInvokeResolutionError(`Lambda asset ${displayUntrustedValue(codePath)} is a file but could not be read as a ZIP archive: ${err instanceof Error ? err.message : String(err)}. Re-synthesize the app and retry.`);
|
|
3260
3430
|
}
|
|
3261
3431
|
const modes = parseZipUnixModes(zipBytes);
|
|
3262
3432
|
const S_IFMT = 61440;
|
|
@@ -6676,7 +6846,7 @@ var CfnLocalStateProvider = class {
|
|
|
6676
6846
|
return map.get(exportName);
|
|
6677
6847
|
},
|
|
6678
6848
|
async resolveGetStackOutput(producerStack, producerRegion, outputName) {
|
|
6679
|
-
logger.warn(`${label}: Fn::GetStackOutput
|
|
6849
|
+
logger.warn(`${label}: Fn::GetStackOutput ${displayUntrustedValue(producerStack)}.${displayUntrustedValue(outputName)} (${displayUntrustedValue(producerRegion)}) has no CloudFormation equivalent and cannot be resolved when reading state from a CloudFormation stack. Use Fn::ImportValue against an exported output instead.`);
|
|
6680
6850
|
}
|
|
6681
6851
|
};
|
|
6682
6852
|
}
|
|
@@ -7591,7 +7761,7 @@ async function resolveImportValueAsync(arg, context) {
|
|
|
7591
7761
|
const exportName = inner.value;
|
|
7592
7762
|
if (!context.crossStackResolver) return {
|
|
7593
7763
|
kind: "unresolved",
|
|
7594
|
-
reason: `Fn::ImportValue
|
|
7764
|
+
reason: `Fn::ImportValue ${displayUntrustedValue(exportName)}: no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`
|
|
7595
7765
|
};
|
|
7596
7766
|
let resolved;
|
|
7597
7767
|
try {
|
|
@@ -7599,12 +7769,12 @@ async function resolveImportValueAsync(arg, context) {
|
|
|
7599
7769
|
} catch (err) {
|
|
7600
7770
|
return {
|
|
7601
7771
|
kind: "unresolved",
|
|
7602
|
-
reason: `Fn::ImportValue
|
|
7772
|
+
reason: `Fn::ImportValue ${displayUntrustedValue(exportName)}: lookup failed: ${describeAwsFailureForWarn(err, "CrossStackResolver.resolveImport (Fn::ImportValue)")}`
|
|
7603
7773
|
};
|
|
7604
7774
|
}
|
|
7605
7775
|
if (resolved === void 0) return {
|
|
7606
7776
|
kind: "unresolved",
|
|
7607
|
-
reason: `Fn::ImportValue
|
|
7777
|
+
reason: `Fn::ImportValue ${displayUntrustedValue(exportName)}: export not found in any deployed stack in this region`
|
|
7608
7778
|
};
|
|
7609
7779
|
return {
|
|
7610
7780
|
kind: "literal",
|
|
@@ -7658,15 +7828,15 @@ async function resolveGetStackOutputAsync(arg, context) {
|
|
|
7658
7828
|
} else region = context.consumerRegion ?? context.pseudoParameters?.region;
|
|
7659
7829
|
if (!region) return {
|
|
7660
7830
|
kind: "unresolved",
|
|
7661
|
-
reason: `Fn::GetStackOutput
|
|
7831
|
+
reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)}: no Region supplied and consumer region is unknown (set --region, AWS_REGION, or env.region on the CDK stack)`
|
|
7662
7832
|
};
|
|
7663
7833
|
if (args["RoleArn"] !== void 0 && args["RoleArn"] !== null) return {
|
|
7664
7834
|
kind: "unresolved",
|
|
7665
|
-
reason: `Fn::GetStackOutput
|
|
7835
|
+
reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)}: RoleArn (cross-account) is not yet supported by the state source — tracked under issue #449`
|
|
7666
7836
|
};
|
|
7667
7837
|
if (!context.crossStackResolver) return {
|
|
7668
7838
|
kind: "unresolved",
|
|
7669
|
-
reason: `Fn::GetStackOutput
|
|
7839
|
+
reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)}: no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`
|
|
7670
7840
|
};
|
|
7671
7841
|
let resolved;
|
|
7672
7842
|
try {
|
|
@@ -7674,12 +7844,12 @@ async function resolveGetStackOutputAsync(arg, context) {
|
|
|
7674
7844
|
} catch (err) {
|
|
7675
7845
|
return {
|
|
7676
7846
|
kind: "unresolved",
|
|
7677
|
-
reason: `Fn::GetStackOutput
|
|
7847
|
+
reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)} (${displayUntrustedValue(region)}): lookup failed: ${describeAwsFailureForWarn(err, "CrossStackResolver.resolveGetStackOutput (Fn::GetStackOutput)")}`
|
|
7678
7848
|
};
|
|
7679
7849
|
}
|
|
7680
7850
|
if (resolved === void 0) return {
|
|
7681
7851
|
kind: "unresolved",
|
|
7682
|
-
reason: `Fn::GetStackOutput
|
|
7852
|
+
reason: `Fn::GetStackOutput ${displayUntrustedValue(stackName)}.${displayUntrustedValue(outputName)} (${displayUntrustedValue(region)}): output not found in producer stack state`
|
|
7683
7853
|
};
|
|
7684
7854
|
return {
|
|
7685
7855
|
kind: "literal",
|
|
@@ -8967,6 +9137,380 @@ function resolveRuntimeCodeMountPath(runtime) {
|
|
|
8967
9137
|
return "/var/task";
|
|
8968
9138
|
}
|
|
8969
9139
|
|
|
9140
|
+
//#endregion
|
|
9141
|
+
//#region src/assets/asset-source-path.ts
|
|
9142
|
+
/**
|
|
9143
|
+
* Resolve a manifest-supplied asset path and REFUSE it when it leaves the
|
|
9144
|
+
* app's outdir — lexically, or through a symbolic link. The one exception is
|
|
9145
|
+
* an ABSOLUTE value under `'honour-warn'`, which is ACCEPTED with a warning
|
|
9146
|
+
* when it is a folder inside the user's project, not under a credential or
|
|
9147
|
+
* version-control directory, and refused
|
|
9148
|
+
* otherwise (see {@link AssetSourcePathOptions.absolute}).
|
|
9149
|
+
*
|
|
9150
|
+
* Returns the RESOLVED, normalized absolute path, and callers must use THAT
|
|
9151
|
+
* rather than re-joining the raw value. It matters for the kernel: a raw
|
|
9152
|
+
* `<dir>/sub/link/..` handed to the OS applies `..` AFTER following `link`,
|
|
9153
|
+
* while every lexical model folds it first. Opening the normalized path makes
|
|
9154
|
+
* the judged path and the opened path the same string, so the `<link>/..`
|
|
9155
|
+
* shape has no second reading left to exploit.
|
|
9156
|
+
*
|
|
9157
|
+
* Naming the bound ITSELF (`.`, or `..` from a Stage manifest) is accepted —
|
|
9158
|
+
* an asset source is a directory by design, and the absolute arm treats the
|
|
9159
|
+
* bound as inside, so the two arms must agree — but it is WARNED about,
|
|
9160
|
+
* because it hands the whole assembly to the sink.
|
|
9161
|
+
*/
|
|
9162
|
+
function resolveAssetSourcePath(opts) {
|
|
9163
|
+
const { manifestDir, value, assetOutdir, field, subject, action, wrapError } = opts;
|
|
9164
|
+
const shownValue = displayUntrustedValue(value);
|
|
9165
|
+
if (isAbsolute(value) && opts.absolute !== "fold") {
|
|
9166
|
+
const absolute = resolve(value);
|
|
9167
|
+
const escape = absoluteAssemblyPathEscape(assetOutdir, absolute);
|
|
9168
|
+
if (escape !== void 0 && opts.absolute === "honour") throw wrapError(`${subject} has an absolute ${field}=${shownValue} which ` + renderAssemblyPathEscape(escape, assetOutdir, action));
|
|
9169
|
+
if (escape !== void 0) {
|
|
9170
|
+
const refusal = originScopeRefusal(absolute, assetOutdir);
|
|
9171
|
+
if (refusal !== void 0) throw wrapError(`${subject} has an absolute ${field}=${shownValue} which ${refusal}. An absolute ${field} is accepted only as a cdk synth --no-staging source folder: a directory inside your project (the git work tree holding the output directory, or the current directory when it contains the output directory or is a git work-tree root), not under a credential or version-control directory. Refusing to ${action}.`);
|
|
9172
|
+
warnAbsoluteOutsideAssembly(opts, absolute, escape);
|
|
9173
|
+
return absolute;
|
|
9174
|
+
}
|
|
9175
|
+
if (namesTheSameDirectory(assetOutdir, absolute)) warnWholeAssemblyAsSource(opts, absolute);
|
|
9176
|
+
return absolute;
|
|
9177
|
+
}
|
|
9178
|
+
const resolved = resolveAssemblyPath(manifestDir, value, { containWithin: assetOutdir });
|
|
9179
|
+
if (resolved.contained) return resolved.path;
|
|
9180
|
+
if (namesTheSameDirectory(assetOutdir, resolved.path)) {
|
|
9181
|
+
warnWholeAssemblyAsSource(opts, resolved.path);
|
|
9182
|
+
return resolved.path;
|
|
9183
|
+
}
|
|
9184
|
+
throw wrapError(`${subject} has ${field}=${shownValue} which ` + renderAssemblyPathEscape(resolved, assetOutdir, action));
|
|
9185
|
+
}
|
|
9186
|
+
/**
|
|
9187
|
+
* Why an accepted-absolute root is too broad to be a `--no-staging` source, or
|
|
9188
|
+
* `undefined`. `root` is already a real path ({@link realPath}); the other
|
|
9189
|
+
* sides are resolved the same way, so a link to `/` or to `$HOME` is the same
|
|
9190
|
+
* root:
|
|
9191
|
+
*
|
|
9192
|
+
* - `/` — the whole filesystem;
|
|
9193
|
+
* - the user's home directory (`os.homedir()`) or any ANCESTOR of it
|
|
9194
|
+
* (`/Users`, `/home`) — each contains `~/.aws` / `~/.ssh`; a project folder
|
|
9195
|
+
* UNDER home is fine. The ancestor arm matters when the project is not under
|
|
9196
|
+
* home (`/tmp`, `/workspaces`), where the outdir arm does not cover it;
|
|
9197
|
+
* - an ANCESTOR of the app's outdir, which contains the assembly and
|
|
9198
|
+
* everything beside it.
|
|
9199
|
+
*/
|
|
9200
|
+
function broadRootReason(root, assetOutdir) {
|
|
9201
|
+
if (root === resolve("/")) return "names the filesystem root";
|
|
9202
|
+
if (containsOrIs(root, realPath(homedir()))) return "names your home directory or a directory containing it";
|
|
9203
|
+
if (containsOrIs(root, realPath(assetOutdir))) return "names a directory containing the app's output directory";
|
|
9204
|
+
}
|
|
9205
|
+
/**
|
|
9206
|
+
* Why an `'honour-warn'` absolute root is refused, or `undefined` to accept it
|
|
9207
|
+
* with the warning. Every comparison is on REAL paths, so a symlink whose
|
|
9208
|
+
* target leaves the project is refused however it is spelled.
|
|
9209
|
+
*
|
|
9210
|
+
* 1. {@link broadRootReason} — roots no `--no-staging` source can be.
|
|
9211
|
+
* 2. The root must lie STRICTLY inside a project root
|
|
9212
|
+
* ({@link projectRoots}), and no path component between that project root
|
|
9213
|
+
* and the origin may be a credential / VCS directory
|
|
9214
|
+
* ({@link CREDENTIAL_DIR_NAMES}, case-insensitive — APFS folds case). Other
|
|
9215
|
+
* hidden directories are fine: `docs/.vitepress/dist`, Nuxt's
|
|
9216
|
+
* `.output/public` and SvelteKit's `.svelte-kit/output` are real
|
|
9217
|
+
* `--no-staging` site folders. A monorepo sibling
|
|
9218
|
+
* (`<repo>/packages/web/dist` beside `<repo>/packages/infra/cdk.out`) is
|
|
9219
|
+
* inside the git work tree and accepted. Hidden FILES under an accepted
|
|
9220
|
+
* root are kept out at serve time instead (`cloudfront-static-origin.ts`).
|
|
9221
|
+
*/
|
|
9222
|
+
function originScopeRefusal(absolute, assetOutdir) {
|
|
9223
|
+
const root = realPath(absolute);
|
|
9224
|
+
const broad = broadRootReason(root, assetOutdir);
|
|
9225
|
+
if (broad !== void 0) return broad;
|
|
9226
|
+
const projects = projectRoots(assetOutdir);
|
|
9227
|
+
let hidden;
|
|
9228
|
+
let isProjectRoot = false;
|
|
9229
|
+
for (const project of projects) {
|
|
9230
|
+
const rel = relative(project, root);
|
|
9231
|
+
if (rel === "") isProjectRoot = true;
|
|
9232
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) continue;
|
|
9233
|
+
const dotted = rel.split(sep).find((c) => CREDENTIAL_DIR_NAMES.has(c.toLowerCase()));
|
|
9234
|
+
if (dotted === void 0) return void 0;
|
|
9235
|
+
hidden = dotted;
|
|
9236
|
+
}
|
|
9237
|
+
if (isProjectRoot) return "is your project root itself; name a folder inside it";
|
|
9238
|
+
if (hidden !== void 0) return `passes through the credential / version-control directory ${displayUntrustedValue(hidden)} inside your project`;
|
|
9239
|
+
const shown = projects.map((p) => displayUntrustedValue(p)).join(" or ");
|
|
9240
|
+
return projects.length === 0 ? "is outside any usable project root (the current directory and the git work tree are too broad to scope it)" : `resolves to ${displayUntrustedValue(root)}, outside your project (${shown})`;
|
|
9241
|
+
}
|
|
9242
|
+
/**
|
|
9243
|
+
* Directory names no `--no-staging` site folder sits under: credential stores
|
|
9244
|
+
* and version-control / package-manager metadata. Compared lower-cased.
|
|
9245
|
+
*/
|
|
9246
|
+
const CREDENTIAL_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
9247
|
+
".git",
|
|
9248
|
+
".hg",
|
|
9249
|
+
".svn",
|
|
9250
|
+
".ssh",
|
|
9251
|
+
".aws",
|
|
9252
|
+
".gnupg",
|
|
9253
|
+
".docker",
|
|
9254
|
+
".kube",
|
|
9255
|
+
".config",
|
|
9256
|
+
".azure",
|
|
9257
|
+
".gcloud",
|
|
9258
|
+
".terraform",
|
|
9259
|
+
".npm",
|
|
9260
|
+
".pnpm-store",
|
|
9261
|
+
".yarn",
|
|
9262
|
+
".cache",
|
|
9263
|
+
".local"
|
|
9264
|
+
]);
|
|
9265
|
+
/**
|
|
9266
|
+
* The directories an accepted absolute origin may live in, both real paths:
|
|
9267
|
+
*
|
|
9268
|
+
* - the process cwd, ONLY when it contains the outdir or is itself a git work
|
|
9269
|
+
* tree root (holds a `.git` entry) — running from a folder of repos (`~/work`,
|
|
9270
|
+
* `/tmp`) must not make every project under it a scope;
|
|
9271
|
+
* - the nearest ancestor of the outdir holding a `.git` entry (a directory, or
|
|
9272
|
+
* a FILE in a linked worktree / submodule). `git` is not spawned.
|
|
9273
|
+
*
|
|
9274
|
+
* A candidate that is `/`, the home directory or an ancestor of it is DROPPED
|
|
9275
|
+
* rather than used — running from `~` must not widen the scope to all of home.
|
|
9276
|
+
*/
|
|
9277
|
+
function projectRoots(assetOutdir) {
|
|
9278
|
+
const roots = [];
|
|
9279
|
+
const usable = (p) => p !== resolve("/") && !containsOrIs(p, realPath(homedir()));
|
|
9280
|
+
const cwd = realPath(process.cwd());
|
|
9281
|
+
const outdir = realPath(assetOutdir);
|
|
9282
|
+
if (usable(cwd) && (containsOrIs(cwd, outdir) || existsSync(join(cwd, ".git")))) roots.push(cwd);
|
|
9283
|
+
let dir = outdir;
|
|
9284
|
+
for (;;) {
|
|
9285
|
+
if (existsSync(join(dir, ".git"))) {
|
|
9286
|
+
if (usable(dir) && !roots.includes(dir)) roots.push(dir);
|
|
9287
|
+
break;
|
|
9288
|
+
}
|
|
9289
|
+
const parent = dirname(dir);
|
|
9290
|
+
if (parent === dir) break;
|
|
9291
|
+
dir = parent;
|
|
9292
|
+
}
|
|
9293
|
+
return roots;
|
|
9294
|
+
}
|
|
9295
|
+
/** `outer` is `inner` itself or an ancestor of it (both real paths). */
|
|
9296
|
+
function containsOrIs(outer, inner) {
|
|
9297
|
+
const rel = relative(outer, inner);
|
|
9298
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
9299
|
+
}
|
|
9300
|
+
/**
|
|
9301
|
+
* `realpath(3)` of `p`, or — when `p` does not exist — the real path of its
|
|
9302
|
+
* deepest existing ancestor with the rest re-appended, so a not-yet-existing
|
|
9303
|
+
* path under a symlinked parent (`/tmp` -> `/private/tmp`) compares in the
|
|
9304
|
+
* same spelling as its existing neighbours. A DANGLING link component is
|
|
9305
|
+
* followed by hand (`readlink`), so `project/site -> /outside/missing` is
|
|
9306
|
+
* judged as `/outside/missing` — the place a later-created target would be
|
|
9307
|
+
* served from — not as `project/site`.
|
|
9308
|
+
*/
|
|
9309
|
+
function realPath(p, hops = 0) {
|
|
9310
|
+
const abs = resolve(p);
|
|
9311
|
+
try {
|
|
9312
|
+
return realpathSync.native(abs);
|
|
9313
|
+
} catch {
|
|
9314
|
+
const parent = dirname(abs);
|
|
9315
|
+
if (parent === abs) return abs;
|
|
9316
|
+
const realParent = realPath(parent, hops);
|
|
9317
|
+
let link;
|
|
9318
|
+
try {
|
|
9319
|
+
link = readlinkSync(abs);
|
|
9320
|
+
} catch {
|
|
9321
|
+
link = void 0;
|
|
9322
|
+
}
|
|
9323
|
+
if (link !== void 0 && hops < 40) return realPath(resolve(realParent, link), hops + 1);
|
|
9324
|
+
return join(realParent, basename(abs));
|
|
9325
|
+
}
|
|
9326
|
+
}
|
|
9327
|
+
/**
|
|
9328
|
+
* Absolute-outside lines already printed this process (deduped for the same
|
|
9329
|
+
* reason as {@link warnedWholeAssembly}).
|
|
9330
|
+
*/
|
|
9331
|
+
const warnedAbsoluteOutside = /* @__PURE__ */ new Set();
|
|
9332
|
+
function warnAbsoluteOutsideAssembly(opts, absolute, escape) {
|
|
9333
|
+
const logger = getLogger().child("assets");
|
|
9334
|
+
const line = `${opts.subject} has an absolute ${opts.field} pointing outside the assembly: ` + displayUntrustedValue(absolute) + (escape.escape === "symlink" ? ` (through a symbolic link to ${displayUntrustedValue(escape.realPath)})` : "") + `. ${getEmbedConfig().productName} will ${opts.sink}. ` + (escape.escape === "lexical" ? "This is what cdk synth --no-staging emits, and is expected for it; if you did not synthesize with that flag, treat this assembly as untrusted." : "No CDK synth writes this, so treat this assembly as untrusted unless you made that link yourself.");
|
|
9335
|
+
if (warnedAbsoluteOutside.has(line)) {
|
|
9336
|
+
logger.debug(line);
|
|
9337
|
+
return;
|
|
9338
|
+
}
|
|
9339
|
+
warnedAbsoluteOutside.add(line);
|
|
9340
|
+
logger.warn(line);
|
|
9341
|
+
}
|
|
9342
|
+
/**
|
|
9343
|
+
* Whole-assembly lines already printed this process. A `start-service` with
|
|
9344
|
+
* `DesiredCount: 3` builds per replica and again per crash-loop restart, so an
|
|
9345
|
+
* undeduped line is a storm, and a storm is not read. Repeats drop to debug;
|
|
9346
|
+
* the WORK is never deduped.
|
|
9347
|
+
*/
|
|
9348
|
+
const warnedWholeAssembly = /* @__PURE__ */ new Set();
|
|
9349
|
+
function warnWholeAssemblyAsSource(opts, outdir) {
|
|
9350
|
+
const logger = getLogger().child("assets");
|
|
9351
|
+
const line = `${opts.subject} has ${opts.field} naming the assembly's output directory ITSELF: ${displayUntrustedValue(outdir)}. ${getEmbedConfig().productName} will ${opts.sink} — that is the WHOLE assembly, every template and every staged asset, not one asset directory. No CDK synth emits this, so treat this assembly as untrusted unless you wrote that path yourself.`;
|
|
9352
|
+
if (warnedWholeAssembly.has(line)) {
|
|
9353
|
+
logger.debug(line);
|
|
9354
|
+
return;
|
|
9355
|
+
}
|
|
9356
|
+
warnedWholeAssembly.add(line);
|
|
9357
|
+
logger.warn(line);
|
|
9358
|
+
}
|
|
9359
|
+
|
|
9360
|
+
//#endregion
|
|
9361
|
+
//#region src/assets/docker-cache-option.ts
|
|
9362
|
+
/**
|
|
9363
|
+
* Render a `cacheFrom` / `cacheTo` entry as the single `--cache-from` /
|
|
9364
|
+
* `--cache-to` value BuildKit parses: `type=<t>,<k>=<v>,...`, unquoted.
|
|
9365
|
+
*
|
|
9366
|
+
* Its own module because two readers need the SAME string: the argv builder in
|
|
9367
|
+
* `docker-build.ts`, and the passthrough warnings, which judge the rendered
|
|
9368
|
+
* flag rather than the struct (a `params` value can smuggle a `,src=` that the
|
|
9369
|
+
* struct never shows).
|
|
9370
|
+
*/
|
|
9371
|
+
function cacheOptionToFlag(option) {
|
|
9372
|
+
let flag = `type=${option.type}`;
|
|
9373
|
+
if (option.params) for (const [k, v] of Object.entries(option.params)) flag += `,${k}=${v}`;
|
|
9374
|
+
return flag;
|
|
9375
|
+
}
|
|
9376
|
+
|
|
9377
|
+
//#endregion
|
|
9378
|
+
//#region src/assets/buildkit-passthrough-warnings.ts
|
|
9379
|
+
/**
|
|
9380
|
+
* Every candidate host path in a buildx option value: a part with a key yields
|
|
9381
|
+
* its value when the key is `src` / `source` / `dest` (the keys that make
|
|
9382
|
+
* BuildKit open a file), and a part without a key yields itself.
|
|
9383
|
+
*
|
|
9384
|
+
* Over-inclusive on purpose for a BARE part: a non-path string resolves inside
|
|
9385
|
+
* the build context and is dropped before anything is printed. A keyed part is
|
|
9386
|
+
* limited to the three file keys because `ref=` / `scope=` / `env=` are not
|
|
9387
|
+
* paths, and taking them announced a registry ref as a host read.
|
|
9388
|
+
*
|
|
9389
|
+
* Known incomplete, and both misses cost a WARNING, never a refusal: a quoted
|
|
9390
|
+
* CSV value containing a comma is split wrongly here; a leading or trailing
|
|
9391
|
+
* space is trimmed here while buildx keeps it (an over-warn only).
|
|
9392
|
+
*/
|
|
9393
|
+
function candidateHostPaths(value, opts) {
|
|
9394
|
+
const out = [];
|
|
9395
|
+
for (const part of value.split(",")) {
|
|
9396
|
+
const eq = part.indexOf("=");
|
|
9397
|
+
if (eq < 0) {
|
|
9398
|
+
const bare = part.trim();
|
|
9399
|
+
if (bare.length > 0) out.push({
|
|
9400
|
+
path: bare,
|
|
9401
|
+
write: opts.bareIsWrite
|
|
9402
|
+
});
|
|
9403
|
+
continue;
|
|
9404
|
+
}
|
|
9405
|
+
const key = part.slice(0, eq).trim().toLowerCase();
|
|
9406
|
+
const v = part.slice(eq + 1).trim();
|
|
9407
|
+
if (v.length === 0) continue;
|
|
9408
|
+
if (key !== "src" && key !== "source" && key !== "dest") continue;
|
|
9409
|
+
out.push({
|
|
9410
|
+
path: v,
|
|
9411
|
+
write: key === "dest"
|
|
9412
|
+
});
|
|
9413
|
+
}
|
|
9414
|
+
return out;
|
|
9415
|
+
}
|
|
9416
|
+
/**
|
|
9417
|
+
* Every host path this source hands to BuildKit. Each field is judged as the
|
|
9418
|
+
* RENDERED argv string `buildDockerBuildCommand` pushes, not as the struct —
|
|
9419
|
+
* nothing there is quoted, so CSV smuggled into a key or a `params` value
|
|
9420
|
+
* reaches BuildKit and must reach this walk too.
|
|
9421
|
+
*/
|
|
9422
|
+
function hostPathsOf(source) {
|
|
9423
|
+
const refs = [];
|
|
9424
|
+
if (source.dockerFile) refs.push({
|
|
9425
|
+
field: "dockerFile",
|
|
9426
|
+
where: "dockerFile",
|
|
9427
|
+
path: source.dockerFile,
|
|
9428
|
+
write: false
|
|
9429
|
+
});
|
|
9430
|
+
for (const [k, v] of Object.entries(source.dockerBuildContexts ?? {})) {
|
|
9431
|
+
const rendered = `${k}=${v}`;
|
|
9432
|
+
const contextPath = rendered.slice(rendered.indexOf("=") + 1).trim().replace(/^oci-layout:\/\//, "");
|
|
9433
|
+
if (contextPath.length === 0) continue;
|
|
9434
|
+
refs.push({
|
|
9435
|
+
field: "dockerBuildContexts",
|
|
9436
|
+
where: k,
|
|
9437
|
+
path: contextPath,
|
|
9438
|
+
write: false
|
|
9439
|
+
});
|
|
9440
|
+
}
|
|
9441
|
+
const ssh = source.dockerBuildSsh ?? "";
|
|
9442
|
+
const sshEq = ssh.indexOf("=");
|
|
9443
|
+
(sshEq < 0 ? [] : ssh.slice(sshEq + 1).split(",")).forEach((entry, i) => {
|
|
9444
|
+
const p = entry.trim();
|
|
9445
|
+
if (p.length > 0) refs.push({
|
|
9446
|
+
field: "dockerBuildSsh",
|
|
9447
|
+
where: String(i),
|
|
9448
|
+
path: p,
|
|
9449
|
+
write: false
|
|
9450
|
+
});
|
|
9451
|
+
});
|
|
9452
|
+
for (const [k, v] of Object.entries(source.dockerBuildSecrets ?? {})) for (const { path: p, write } of candidateHostPaths(`id=${k},${v}`, { bareIsWrite: false })) refs.push({
|
|
9453
|
+
field: "dockerBuildSecrets",
|
|
9454
|
+
where: k,
|
|
9455
|
+
path: p,
|
|
9456
|
+
write
|
|
9457
|
+
});
|
|
9458
|
+
(source.cacheFrom ?? []).forEach((c, i) => {
|
|
9459
|
+
for (const { path: p, write } of candidateHostPaths(cacheOptionToFlag(c), { bareIsWrite: false })) refs.push({
|
|
9460
|
+
field: "cacheFrom",
|
|
9461
|
+
where: String(i),
|
|
9462
|
+
path: p,
|
|
9463
|
+
write
|
|
9464
|
+
});
|
|
9465
|
+
});
|
|
9466
|
+
if (source.cacheTo) for (const { path: p, write } of candidateHostPaths(cacheOptionToFlag(source.cacheTo), { bareIsWrite: false })) refs.push({
|
|
9467
|
+
field: "cacheTo",
|
|
9468
|
+
where: "cacheTo",
|
|
9469
|
+
path: p,
|
|
9470
|
+
write
|
|
9471
|
+
});
|
|
9472
|
+
(source.dockerOutputs ?? []).forEach((o, i) => {
|
|
9473
|
+
for (const { path: p, write } of candidateHostPaths(o, { bareIsWrite: true })) refs.push({
|
|
9474
|
+
field: "dockerOutputs",
|
|
9475
|
+
where: String(i),
|
|
9476
|
+
path: p,
|
|
9477
|
+
write
|
|
9478
|
+
});
|
|
9479
|
+
});
|
|
9480
|
+
return refs;
|
|
9481
|
+
}
|
|
9482
|
+
/**
|
|
9483
|
+
* Lines already printed this process; a repeat drops to debug. Replicas and
|
|
9484
|
+
* reloads rebuild the same asset, and the same line N times is a storm.
|
|
9485
|
+
*/
|
|
9486
|
+
const warnedPassthroughs = /* @__PURE__ */ new Set();
|
|
9487
|
+
/**
|
|
9488
|
+
* Warn for each BuildKit passthrough whose host path leaves the assembly.
|
|
9489
|
+
*
|
|
9490
|
+
* `base` is the build context directory (what BuildKit resolves a relative
|
|
9491
|
+
* value against — the build's cwd); `bound` is the app's outdir. Called above
|
|
9492
|
+
* the spawn so the line precedes the read or write it describes.
|
|
9493
|
+
*
|
|
9494
|
+
* No "inside the build context" exemption is needed, unlike a builder that
|
|
9495
|
+
* honours an absolute context: `buildDockerImage` has already contained the
|
|
9496
|
+
* context within `bound`, so a path inside the context is inside the bound.
|
|
9497
|
+
*/
|
|
9498
|
+
function warnEscapingBuildKitPaths(source, base, bound) {
|
|
9499
|
+
const logger = getLogger().child("assets");
|
|
9500
|
+
for (const ref of hostPathsOf(source)) {
|
|
9501
|
+
const escape = assemblyPathEscape(base, bound, ref.path);
|
|
9502
|
+
if (escape === void 0) continue;
|
|
9503
|
+
const verb = ref.write ? "WRITE to" : "read";
|
|
9504
|
+
const line = `Docker asset ${ref.field}` + (ref.where === ref.field ? "" : `[${displayUntrustedValue(ref.where)}]`) + ` names a host path outside the assembly, which ` + renderAssemblyPathEscape(escape, bound, "load", `${getEmbedConfig().productName} will ${verb} it during the image build, matching the CDK CLI — a pre-synthesized assembly is trusted input. If you did not produce this assembly, that is a host path it chose and the CloudFormation template does not show.`);
|
|
9505
|
+
if (warnedPassthroughs.has(line)) {
|
|
9506
|
+
logger.debug(line);
|
|
9507
|
+
continue;
|
|
9508
|
+
}
|
|
9509
|
+
warnedPassthroughs.add(line);
|
|
9510
|
+
logger.warn(line);
|
|
9511
|
+
}
|
|
9512
|
+
}
|
|
9513
|
+
|
|
8970
9514
|
//#endregion
|
|
8971
9515
|
//#region src/assets/docker-build.ts
|
|
8972
9516
|
/**
|
|
@@ -9005,9 +9549,10 @@ function resolveRuntimeCodeMountPath(runtime) {
|
|
|
9005
9549
|
* to `debug`, where the argv already is. Deduping the LINE must never dedupe
|
|
9006
9550
|
* the WORK: the spawn happens every time regardless.
|
|
9007
9551
|
*
|
|
9008
|
-
* `
|
|
9009
|
-
*
|
|
9010
|
-
* unsanitized one lets the command forge its own log line
|
|
9552
|
+
* `displayUntrustedValue` for the same reason `assembly-path.ts` uses it: the
|
|
9553
|
+
* value is manifest-chosen, it keeps control characters and quotes, and an
|
|
9554
|
+
* unsanitized one lets the command forge its own log line or close a quote
|
|
9555
|
+
* of ours (go-to-k/cdk-local#758). It is applied to
|
|
9011
9556
|
* the DISPLAYED command only — the dedupe key is built from the raw argv and
|
|
9012
9557
|
* cwd, so two commands differing only in control characters stay distinct
|
|
9013
9558
|
* keys.
|
|
@@ -9017,12 +9562,12 @@ function warnManifestExecutable(cmd, executable, cwd) {
|
|
|
9017
9562
|
const logger = getLogger().child("docker-build");
|
|
9018
9563
|
const key = JSON.stringify([cwd, executable]);
|
|
9019
9564
|
if (warnedExecutables.has(key)) {
|
|
9020
|
-
logger.debug(`source.executable already announced this process: ${
|
|
9565
|
+
logger.debug(`source.executable already announced this process: ${displayUntrustedValue(cmd)} (cwd=${displayUntrustedValue(cwd)})`);
|
|
9021
9566
|
return;
|
|
9022
9567
|
}
|
|
9023
9568
|
warnedExecutables.add(key);
|
|
9024
9569
|
const argCount = executable.length - 1;
|
|
9025
|
-
logger.warn(`Docker asset source.executable runs a command this asset manifest chose, on this machine:
|
|
9570
|
+
logger.warn(`Docker asset source.executable runs a command this asset manifest chose, on this machine: ${displayUntrustedValue(cmd)}` + (argCount > 0 ? ` (with ${argCount} argument(s); --verbose shows them)` : "") + `. ${getEmbedConfig().productName} runs it, matching the CDK CLI — a pre-synthesized assembly is trusted input. Note that this means running a local command against a pre-synthesized assembly DOES execute code from it, which the CloudFormation template does not show.`);
|
|
9026
9571
|
}
|
|
9027
9572
|
/**
|
|
9028
9573
|
* Build a Docker image from a CDK asset source. Returns the local image
|
|
@@ -9042,11 +9587,23 @@ function warnManifestExecutable(cmd, executable, cwd) {
|
|
|
9042
9587
|
async function buildDockerImage(asset, cdkOutDir, options) {
|
|
9043
9588
|
const source = asset.source;
|
|
9044
9589
|
const logger = getLogger().child("docker-build");
|
|
9590
|
+
const assetOutdir = options.assetOutdir ?? cdkOutDir;
|
|
9591
|
+
const contextDirectory = (directory, sink) => resolveAssetSourcePath({
|
|
9592
|
+
manifestDir: cdkOutDir,
|
|
9593
|
+
value: directory,
|
|
9594
|
+
assetOutdir,
|
|
9595
|
+
absolute: "fold",
|
|
9596
|
+
field: "source.directory",
|
|
9597
|
+
subject: "Docker image asset",
|
|
9598
|
+
action: "build it",
|
|
9599
|
+
sink,
|
|
9600
|
+
wrapError: (message) => new LocalInvokeBuildError(message)
|
|
9601
|
+
});
|
|
9045
9602
|
if (source.executable && source.executable.length > 0) {
|
|
9046
9603
|
const [cmd, ...args] = source.executable;
|
|
9047
9604
|
if (!cmd) throw options.wrapError("asset source.executable[] is empty");
|
|
9048
|
-
const cwd = source.directory ?
|
|
9049
|
-
logger.debug(`Building Docker image via executable: ${source.executable.join(" ")} (cwd=${cwd})`);
|
|
9605
|
+
const cwd = source.directory ? contextDirectory(source.directory, "run this asset's source.executable with that directory as its working directory") : cdkOutDir;
|
|
9606
|
+
logger.debug(`Building Docker image via executable: ${sanitizeServiceExceptionMessage(source.executable.join(" "))} (cwd=${displayUntrustedValue(cwd)})`);
|
|
9050
9607
|
warnManifestExecutable(cmd, source.executable, cwd);
|
|
9051
9608
|
let result;
|
|
9052
9609
|
try {
|
|
@@ -9062,8 +9619,9 @@ async function buildDockerImage(asset, cdkOutDir, options) {
|
|
|
9062
9619
|
if (!source.directory) throw options.wrapError(`DockerImageAssetSource must set either 'directory' or 'executable' (got: ${JSON.stringify(source)})`);
|
|
9063
9620
|
if (!options.tag) throw options.wrapError("buildDockerImage(directory mode) requires options.tag");
|
|
9064
9621
|
const buildArgs = buildDockerBuildCommand(source, options.tag, options.platform);
|
|
9065
|
-
const contextDir =
|
|
9622
|
+
const contextDir = contextDirectory(source.directory, `send that directory to docker build as the context of an image ${getEmbedConfig().productName} then runs locally`);
|
|
9066
9623
|
buildArgs.push(".");
|
|
9624
|
+
warnEscapingBuildKitPaths(source, contextDir, assetOutdir);
|
|
9067
9625
|
logger.debug(`${getDockerCmd()} ${buildArgs.join(" ")} (cwd=${contextDir})`);
|
|
9068
9626
|
try {
|
|
9069
9627
|
await runDockerStreaming(buildArgs, {
|
|
@@ -9104,11 +9662,6 @@ function buildDockerBuildCommand(source, tag, platformOverride) {
|
|
|
9104
9662
|
if (source.cacheDisabled) args.push("--no-cache");
|
|
9105
9663
|
return args;
|
|
9106
9664
|
}
|
|
9107
|
-
function cacheOptionToFlag(option) {
|
|
9108
|
-
let flag = `type=${option.type}`;
|
|
9109
|
-
if (option.params) for (const [k, v] of Object.entries(option.params)) flag += `,${k}=${v}`;
|
|
9110
|
-
return flag;
|
|
9111
|
-
}
|
|
9112
9665
|
|
|
9113
9666
|
//#endregion
|
|
9114
9667
|
//#region src/local/ecr-puller.ts
|
|
@@ -9384,8 +9937,9 @@ async function buildContainerImage(asset, cdkOutDir, options) {
|
|
|
9384
9937
|
const actualTag = await buildDockerImage(asset, cdkOutDir, {
|
|
9385
9938
|
tag,
|
|
9386
9939
|
platform,
|
|
9387
|
-
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
|
|
9388
|
-
progressLabel: `Building container image (platform=${platform})
|
|
9940
|
+
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${sanitizeServiceExceptionMessage(asset.source.directory ?? asset.source.executable?.join(" ") ?? "")}): ${stderr}`),
|
|
9941
|
+
progressLabel: `Building container image (platform=${platform})`,
|
|
9942
|
+
...options.assetOutdir !== void 0 && { assetOutdir: options.assetOutdir }
|
|
9389
9943
|
});
|
|
9390
9944
|
if (actualTag !== tag) {
|
|
9391
9945
|
logger.debug(`Re-tagging executable-built image '${actualTag}' → '${tag}'`);
|
|
@@ -9817,6 +10371,8 @@ var AssetManifestLoader = class {
|
|
|
9817
10371
|
* @returns Asset manifest or null if not found
|
|
9818
10372
|
*/
|
|
9819
10373
|
async loadManifest(cdkOutputDir, stackName) {
|
|
10374
|
+
const resolved = resolveAssemblyPath(cdkOutputDir, `${stackName}.assets.json`);
|
|
10375
|
+
if (!resolved.contained) throw new Error(`Refusing to read the asset manifest for stack ${displayUntrustedValue(stackName)}: it ` + renderAssemblyPathEscape(resolved, cdkOutputDir, "read it"));
|
|
9820
10376
|
const manifestPath = join$1(cdkOutputDir, `${stackName}.assets.json`);
|
|
9821
10377
|
try {
|
|
9822
10378
|
this.logger.debug(`Loading asset manifest from: ${manifestPath}`);
|
|
@@ -9851,14 +10407,31 @@ var AssetManifestLoader = class {
|
|
|
9851
10407
|
return fileAssets;
|
|
9852
10408
|
}
|
|
9853
10409
|
/**
|
|
9854
|
-
* Get asset source
|
|
10410
|
+
* Get a file asset's source directory (absolute path), REFUSING a
|
|
10411
|
+
* `source.path` that leaves `opts.assetOutdir` (go-to-k/cdk-local#745).
|
|
9855
10412
|
*
|
|
9856
|
-
*
|
|
9857
|
-
*
|
|
9858
|
-
*
|
|
10413
|
+
* The value resolves against `cdkOutputDir` with `path.join` semantics, as
|
|
10414
|
+
* it always has, so an ABSOLUTE value is folded under it rather than
|
|
10415
|
+
* honoured (`'fold'` in `resolveAssetSourcePath`). `assetOutdir` is the
|
|
10416
|
+
* app's outdir — `assetPathDirs(stack).assetOutdir` — and is REQUIRED so a
|
|
10417
|
+
* caller cannot silently bound a Stage's `../asset.<hash>` at the manifest
|
|
10418
|
+
* directory.
|
|
10419
|
+
*
|
|
10420
|
+
* The one reader is the AgentCore CodeConfiguration bundle, which builds an
|
|
10421
|
+
* image FROM this directory; `subject` names it in the refusal.
|
|
9859
10422
|
*/
|
|
9860
|
-
getAssetSourcePath(cdkOutputDir, asset) {
|
|
9861
|
-
return
|
|
10423
|
+
getAssetSourcePath(cdkOutputDir, asset, opts) {
|
|
10424
|
+
return resolveAssetSourcePath({
|
|
10425
|
+
manifestDir: cdkOutputDir,
|
|
10426
|
+
value: asset.source.path,
|
|
10427
|
+
assetOutdir: opts.assetOutdir,
|
|
10428
|
+
absolute: "fold",
|
|
10429
|
+
field: "source.path",
|
|
10430
|
+
subject: opts.subject,
|
|
10431
|
+
action: "build an image from it",
|
|
10432
|
+
sink: "build an image from that directory and run it locally",
|
|
10433
|
+
wrapError: opts.wrapError
|
|
10434
|
+
});
|
|
9862
10435
|
}
|
|
9863
10436
|
/**
|
|
9864
10437
|
* Resolve asset destination values (replace ${AWS::AccountId}, ${AWS::Region}, etc.)
|
|
@@ -14577,7 +15150,7 @@ function parseHttpV2IdentitySources(raw) {
|
|
|
14577
15150
|
*/
|
|
14578
15151
|
function parseCognitoUserPoolArn(arn, location) {
|
|
14579
15152
|
const m = /^arn:aws[a-z0-9-]*:cognito-idp:([a-z0-9-]+):[0-9]+:userpool\/(.+)$/.exec(arn);
|
|
14580
|
-
if (!m) throw new RouteDiscoveryError(`${location}: malformed Cognito User Pool ARN
|
|
15153
|
+
if (!m) throw new RouteDiscoveryError(`${location}: malformed Cognito User Pool ARN ${displayUntrustedValue(arn)}. Expected 'arn:aws:cognito-idp:<region>:<account>:userpool/<id>'.`);
|
|
14581
15154
|
return {
|
|
14582
15155
|
region: m[1],
|
|
14583
15156
|
userPoolId: m[2]
|
|
@@ -19409,6 +19982,7 @@ async function resolveContainerImageForStartApi(lambda, skipPull, profile, optio
|
|
|
19409
19982
|
const localBuild = await resolveLocalBuildPlan$1(lambda);
|
|
19410
19983
|
if (localBuild) return { imageRef: await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {
|
|
19411
19984
|
architecture: lambda.architecture,
|
|
19985
|
+
assetOutdir: localBuild.assetOutdir,
|
|
19412
19986
|
...options?.noBuild !== void 0 && { noBuild: options.noBuild }
|
|
19413
19987
|
}) };
|
|
19414
19988
|
if (!parseEcrUri(lambda.imageUri)) throw new Error(`Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri '${lambda.imageUri}' is not an ECR URI ${getEmbedConfig().binaryName} can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
|
|
@@ -19438,7 +20012,8 @@ async function resolveLocalBuildPlan$1(lambda) {
|
|
|
19438
20012
|
if (!entry) return void 0;
|
|
19439
20013
|
return {
|
|
19440
20014
|
asset: entry.asset,
|
|
19441
|
-
cdkOutDir
|
|
20015
|
+
cdkOutDir,
|
|
20016
|
+
assetOutdir: assetPathDirs(lambda.stack).assetOutdir
|
|
19442
20017
|
};
|
|
19443
20018
|
}
|
|
19444
20019
|
/**
|
|
@@ -19639,7 +20214,7 @@ function resolveImageLambda(args) {
|
|
|
19639
20214
|
*/
|
|
19640
20215
|
function resolveAssetCodePath(stack, logicalId, resource) {
|
|
19641
20216
|
const assetPath = resource.Metadata?.["aws:asset:path"];
|
|
19642
|
-
if (typeof assetPath !== "string" || assetPath.length === 0) throw new Error(`Lambda
|
|
20217
|
+
if (typeof assetPath !== "string" || assetPath.length === 0) throw new Error(`Lambda ${displayUntrustedValue(logicalId)} has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} start-api needs this hint to find the local asset directory. Re-synthesize the app and retry.`);
|
|
19643
20218
|
const { manifestDir, assetOutdir } = assetPathDirs(stack);
|
|
19644
20219
|
return resolveAssetCodeDirectory(manifestDir, assetPath, (message) => new Error(message), assetOutdir, logicalId);
|
|
19645
20220
|
}
|
|
@@ -20466,7 +21041,8 @@ async function resolveContainerImagePlan$1(lambda, options) {
|
|
|
20466
21041
|
let imageRef;
|
|
20467
21042
|
if (localBuild) imageRef = await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {
|
|
20468
21043
|
architecture: lambda.architecture,
|
|
20469
|
-
noBuild: options.build === false
|
|
21044
|
+
noBuild: options.build === false,
|
|
21045
|
+
assetOutdir: localBuild.assetOutdir
|
|
20470
21046
|
});
|
|
20471
21047
|
else {
|
|
20472
21048
|
if (!parseEcrUri(lambda.imageUri)) throw new Error(`Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri '${lambda.imageUri}' is not an ECR URI ${getEmbedConfig().binaryName} can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
|
|
@@ -20500,7 +21076,8 @@ async function resolveLocalBuildPlan(lambda) {
|
|
|
20500
21076
|
if (!entry) return void 0;
|
|
20501
21077
|
return {
|
|
20502
21078
|
asset: entry.asset,
|
|
20503
|
-
cdkOutDir
|
|
21079
|
+
cdkOutDir,
|
|
21080
|
+
assetOutdir: assetPathDirs(lambda.stack).assetOutdir
|
|
20504
21081
|
};
|
|
20505
21082
|
}
|
|
20506
21083
|
function envHasIntrinsicValue(templateEnv) {
|
|
@@ -21150,7 +21727,7 @@ async function buildAgentCoreCodeImage(options) {
|
|
|
21150
21727
|
], { progressLabel: `Building agent image (runtime=${options.runtime})` });
|
|
21151
21728
|
} catch (err) {
|
|
21152
21729
|
const stderr = err.stderr?.trim();
|
|
21153
|
-
throw new LocalInvokeBuildError(`docker build failed for AgentCore code artifact (${options.sourceDir})${stderr ? `: ${stderr}` : ""}`);
|
|
21730
|
+
throw new LocalInvokeBuildError(`docker build failed for AgentCore code artifact (${displayUntrustedValue(options.sourceDir)})${stderr ? `: ${stderr}` : ""}`);
|
|
21154
21731
|
} finally {
|
|
21155
21732
|
await rm(buildDir, {
|
|
21156
21733
|
recursive: true,
|
|
@@ -21195,14 +21772,14 @@ async function warnIfDependenciesNotVendored(sourceDir, runtime, isNode, logger)
|
|
|
21195
21772
|
}
|
|
21196
21773
|
const has = (name) => entries.includes(name);
|
|
21197
21774
|
if (isNode) {
|
|
21198
|
-
if (has("package.json") && !has("node_modules")) logger.warn(`AgentCore code bundle
|
|
21775
|
+
if (has("package.json") && !has("node_modules")) logger.warn(`AgentCore code bundle ${displayUntrustedValue(sourceDir)} declares package.json but does not vendor node_modules. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail to resolve them. Vendor dependencies into the bundle (e.g. 'npm install --omit=dev' in the bundle dir) so the deploy artifact is self-contained. cdk-local runs the bundle as-is to match the deployed runtime.`);
|
|
21199
21776
|
return;
|
|
21200
21777
|
}
|
|
21201
21778
|
const manifest = await pythonManifestDeclaringDeps(sourceDir, entries);
|
|
21202
21779
|
const vendored = entries.some((e) => e.endsWith(".dist-info"));
|
|
21203
21780
|
if (manifest && !vendored) {
|
|
21204
21781
|
const pyVersion = runtime.replace("PYTHON_", "").replace("_", ".");
|
|
21205
|
-
logger.warn(`AgentCore code bundle
|
|
21782
|
+
logger.warn(`AgentCore code bundle ${displayUntrustedValue(sourceDir)} declares ${manifest} but does not vendor its dependencies. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail with ModuleNotFoundError. Vendor arm64 wheels into the bundle, e.g.:
|
|
21206
21783
|
uv pip install --python-platform aarch64-manylinux2014 --python-version ${pyVersion} --target <bundle-dir> -r requirements.txt\ncdk-local runs the bundle as-is to match the deployed runtime.`);
|
|
21207
21784
|
}
|
|
21208
21785
|
}
|
|
@@ -22500,7 +23077,8 @@ async function resolveAgentCoreImage(resolved, options, loaded, stateProvider) {
|
|
|
22500
23077
|
const entry = getDockerImageBySourceHash(manifest, containerUri);
|
|
22501
23078
|
if (entry) return buildContainerImage(entry.asset, cdkOutDir, {
|
|
22502
23079
|
architecture,
|
|
22503
|
-
noBuild: options.build === false
|
|
23080
|
+
noBuild: options.build === false,
|
|
23081
|
+
assetOutdir: assetPathDirs(resolved.stack).assetOutdir
|
|
22504
23082
|
});
|
|
22505
23083
|
}
|
|
22506
23084
|
}
|
|
@@ -22535,8 +23113,12 @@ async function resolveAgentCoreCodeImage(resolved, code, options, architecture,
|
|
|
22535
23113
|
const fileAssets = manifest ? loader.getFileAssets(manifest) : void 0;
|
|
22536
23114
|
const asset = fileAssets ? fileAssets.get(code.codeAssetHash) ?? findFileAssetByObjectKey$1(fileAssets, code.codeAssetHash) : void 0;
|
|
22537
23115
|
if (!asset) throw new CdkLocalError(`AgentCore Runtime '${resolved.logicalId}' code bundle (asset ${code.codeAssetHash}) was not found in the cdk.out asset manifest. ${getEmbedConfig().cliName} invoke-agentcore runs a local from-source build of a fromCodeAsset bundle — re-synthesize the app so the asset is staged in cdk.out and retry. (A fromS3 bundle is downloaded from S3 instead; this runtime has no literal Code.S3.Bucket.)`, "LOCAL_INVOKE_AGENTCORE_CODE_ASSET_NOT_FOUND");
|
|
22538
|
-
const sourceDir = loader.getAssetSourcePath(cdkOutDir, asset
|
|
22539
|
-
|
|
23116
|
+
const sourceDir = loader.getAssetSourcePath(cdkOutDir, asset, {
|
|
23117
|
+
assetOutdir: assetPathDirs(resolved.stack).assetOutdir,
|
|
23118
|
+
subject: `AgentCore Runtime ${displayUntrustedValue(resolved.logicalId)} code bundle`,
|
|
23119
|
+
wrapError: (message) => new CdkLocalError(message, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_ESCAPES_ASSEMBLY")
|
|
23120
|
+
});
|
|
23121
|
+
if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) throw new CdkLocalError(`AgentCore Runtime ${displayUntrustedValue(resolved.logicalId)} code bundle source ${displayUntrustedValue(sourceDir)} does not exist or is not a directory. Re-synthesize the app and retry.`, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_MISSING");
|
|
22540
23122
|
return buildAgentCoreCodeImage({
|
|
22541
23123
|
sourceDir,
|
|
22542
23124
|
runtime: code.runtime,
|
|
@@ -23252,7 +23834,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
|
|
|
23252
23834
|
throw new CdkLocalError(`softReloadAgentContainer: docker inspect of container '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_INSPECT_FAILED");
|
|
23253
23835
|
}
|
|
23254
23836
|
const workdirDest = workdir.endsWith("/") ? workdir : `${workdir}/`;
|
|
23255
|
-
logger.info(`Soft-reload: docker cp ${newAssetSourceDir} -> ${containerId}:${workdirDest}; restart.`);
|
|
23837
|
+
logger.info(`Soft-reload: docker cp ${flattenToOneLine(newAssetSourceDir)} -> ${containerId}:${workdirDest}; restart.`);
|
|
23256
23838
|
try {
|
|
23257
23839
|
await execFileAsync$4(dockerCmd, [
|
|
23258
23840
|
"cp",
|
|
@@ -23299,7 +23881,11 @@ async function loadAgentCoreAssetContext(args) {
|
|
|
23299
23881
|
if (!manifest) return void 0;
|
|
23300
23882
|
const asset = assetLoader.getFileAssets(manifest).get(resolved.codeArtifact.codeAssetHash);
|
|
23301
23883
|
if (!asset) return void 0;
|
|
23302
|
-
const sourceDir = assetLoader.getAssetSourcePath(cdkOutDir, asset
|
|
23884
|
+
const sourceDir = assetLoader.getAssetSourcePath(watchManifestDir(newCandidate, cdkOutDir), asset, {
|
|
23885
|
+
assetOutdir: outputAssetBound(newCandidate, cdkOutDir),
|
|
23886
|
+
subject: `AgentCore Runtime ${displayUntrustedValue(resolved.logicalId)} code bundle`,
|
|
23887
|
+
wrapError: (message) => new CdkLocalError(message, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_ESCAPES_ASSEMBLY")
|
|
23888
|
+
});
|
|
23303
23889
|
return {
|
|
23304
23890
|
...oldAssetHash !== void 0 && { oldAssetHash },
|
|
23305
23891
|
newAssetHash: resolved.codeArtifact.codeAssetHash,
|
|
@@ -23315,7 +23901,17 @@ async function loadAgentCoreAssetContext(args) {
|
|
|
23315
23901
|
const newAssetHash = dockerImageEntry.hash;
|
|
23316
23902
|
const newDockerImage = dockerImageEntry.asset;
|
|
23317
23903
|
if (!newDockerImage.source.directory) return void 0;
|
|
23318
|
-
const newAssetSourceDir =
|
|
23904
|
+
const newAssetSourceDir = resolveAssetSourcePath({
|
|
23905
|
+
manifestDir: watchManifestDir(newCandidate, cdkOutDir),
|
|
23906
|
+
value: newDockerImage.source.directory,
|
|
23907
|
+
assetOutdir: outputAssetBound(newCandidate, cdkOutDir),
|
|
23908
|
+
absolute: "honour",
|
|
23909
|
+
field: "source.directory",
|
|
23910
|
+
subject: `AgentCore Runtime ${displayUntrustedValue(resolved.logicalId)} image asset`,
|
|
23911
|
+
action: "copy from it",
|
|
23912
|
+
sink: "copy that directory into the running agent container on a soft reload",
|
|
23913
|
+
wrapError: (message) => new CdkLocalError(message, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_ESCAPES_ASSEMBLY")
|
|
23914
|
+
});
|
|
23319
23915
|
return {
|
|
23320
23916
|
...oldAssetHash !== void 0 && { oldAssetHash },
|
|
23321
23917
|
newAssetHash,
|
|
@@ -23889,7 +24485,7 @@ async function resolveOne(entry, secretsClient, ssmClient) {
|
|
|
23889
24485
|
switch (shape.kind) {
|
|
23890
24486
|
case "secrets-manager": return resolveSecretsManager(entry, shape, secretsClient);
|
|
23891
24487
|
case "ssm": return resolveSsm(entry, shape, ssmClient);
|
|
23892
|
-
case "unknown": throw new EcsSecretsResolutionError(`Container
|
|
24488
|
+
case "unknown": throw new EcsSecretsResolutionError(`Container ${displayUntrustedValue(entry.containerName)} secret ${displayUntrustedValue(entry.name)} references an unsupported ValueFrom shape ${displayUntrustedValue(arn)}. Expected Secrets Manager ARN (optionally with :<json-key>::) or SSM Parameter ARN.`);
|
|
23893
24489
|
}
|
|
23894
24490
|
}
|
|
23895
24491
|
/**
|
|
@@ -24352,8 +24948,9 @@ async function prepareOneImage(task, container, options) {
|
|
|
24352
24948
|
}
|
|
24353
24949
|
const actualTag = await buildDockerImage(asset, cdkOutDir, {
|
|
24354
24950
|
tag,
|
|
24951
|
+
assetOutdir: assetPathDirs(task.stack).assetOutdir,
|
|
24355
24952
|
...options.platformOverride !== void 0 && { platform: options.platformOverride },
|
|
24356
|
-
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
|
|
24953
|
+
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${sanitizeServiceExceptionMessage(asset.source.directory ?? asset.source.executable?.join(" ") ?? "")}): ${stderr}`),
|
|
24357
24954
|
progressLabel: `Building container image for '${container.name}'`
|
|
24358
24955
|
});
|
|
24359
24956
|
if (actualTag !== tag) try {
|
|
@@ -24380,7 +24977,7 @@ async function realizeDockerVolumes(volumes, state) {
|
|
|
24380
24977
|
const out = /* @__PURE__ */ new Map();
|
|
24381
24978
|
for (const v of volumes) {
|
|
24382
24979
|
if (v.kind === "host") {
|
|
24383
|
-
if (v.hostPath && !checkVolumeHostPath(v.hostPath)) logger.warn(`Volume
|
|
24980
|
+
if (v.hostPath && !checkVolumeHostPath(v.hostPath)) logger.warn(`Volume ${displayUntrustedValue(v.name)}: host path ${displayUntrustedValue(v.hostPath)} does not exist or is not a directory. Docker will create an anonymous bind mount; create the host path before run-task if you expected to bind-mount it.`);
|
|
24384
24981
|
out.set(v.name, v);
|
|
24385
24982
|
continue;
|
|
24386
24983
|
}
|
|
@@ -25730,7 +26327,7 @@ async function softReloadReplica(args) {
|
|
|
25730
26327
|
instance.softReloadInProgress = true;
|
|
25731
26328
|
instance.softReloadGeneration = (instance.softReloadGeneration ?? 0) + 1;
|
|
25732
26329
|
try {
|
|
25733
|
-
logger.info(`Soft-reloading replica r${instance.index} (gen ${instance.generation}): docker cp ${sourceDirToCopy} -> ${targets.length} essential container(s); restart.`);
|
|
26330
|
+
logger.info(`Soft-reloading replica r${instance.index} (gen ${instance.generation}): docker cp ${flattenToOneLine(sourceDirToCopy)} -> ${targets.length} essential container(s); restart.`);
|
|
25734
26331
|
for (const target of targets) {
|
|
25735
26332
|
let workdir;
|
|
25736
26333
|
try {
|
|
@@ -27858,7 +28455,10 @@ async function resolveContainerImagePlan(lambda, opts) {
|
|
|
27858
28455
|
const manifest = await new AssetManifestLoader().loadManifest(cdkOutDir, lambda.stack.stackName);
|
|
27859
28456
|
const entry = manifest ? getDockerImageBySourceHash(manifest, lambda.imageUri) : void 0;
|
|
27860
28457
|
if (entry) {
|
|
27861
|
-
imageRef = await buildContainerImage(entry.asset, cdkOutDir, {
|
|
28458
|
+
imageRef = await buildContainerImage(entry.asset, cdkOutDir, {
|
|
28459
|
+
architecture: lambda.architecture,
|
|
28460
|
+
assetOutdir: assetPathDirs(lambda.stack).assetOutdir
|
|
28461
|
+
});
|
|
27862
28462
|
localBuilt = true;
|
|
27863
28463
|
}
|
|
27864
28464
|
}
|
|
@@ -29255,7 +29855,17 @@ async function loadAssetContextForTarget(args) {
|
|
|
29255
29855
|
logger.debug(`loadAssetContext: docker asset '${newAssetHash}' for stack '${candidate.stackName}' is executable-mode (no source.directory). Classifier will see no asset context (rebuild).`);
|
|
29256
29856
|
return;
|
|
29257
29857
|
}
|
|
29258
|
-
const newAssetSourceDir =
|
|
29858
|
+
const newAssetSourceDir = resolveAssetSourcePath({
|
|
29859
|
+
manifestDir: watchManifestDir(candidate, cdkOutDir),
|
|
29860
|
+
value: newDockerImage.source.directory,
|
|
29861
|
+
assetOutdir: outputAssetBound(candidate, cdkOutDir),
|
|
29862
|
+
absolute: "honour",
|
|
29863
|
+
field: "source.directory",
|
|
29864
|
+
subject: `Docker image asset ${displayUntrustedValue(newAssetHash)} of stack ${displayUntrustedValue(candidate.stackName)}`,
|
|
29865
|
+
action: "copy from it",
|
|
29866
|
+
sink: "copy that directory into the running replicas on a soft reload",
|
|
29867
|
+
wrapError: (message) => new Error(message)
|
|
29868
|
+
});
|
|
29259
29869
|
let oldAssetHash;
|
|
29260
29870
|
const liveReplica = controller.runState.replicas.find((r) => !r.shuttingDown);
|
|
29261
29871
|
if (liveReplica?.lastDeployedAssetHash !== void 0) oldAssetHash = liveReplica.lastDeployedAssetHash;
|
|
@@ -29603,6 +30213,7 @@ async function buildFrontDoor(plan, options, logger, extraStateProviders) {
|
|
|
29603
30213
|
...action,
|
|
29604
30214
|
auth: authForGuard(guard)
|
|
29605
30215
|
} : action;
|
|
30216
|
+
let bootingLambda;
|
|
29606
30217
|
try {
|
|
29607
30218
|
for (const listener of plan.listeners) {
|
|
29608
30219
|
const defaultRoute = listener.defaultAction ? attachAuth(toRouteAction(listener.defaultAction), listener.defaultAuthGuard) : void 0;
|
|
@@ -29653,12 +30264,15 @@ async function buildFrontDoor(plan, options, logger, extraStateProviders) {
|
|
|
29653
30264
|
if (!listener.defaultAction) logger.info(" (no default action: unmatched requests return 404)");
|
|
29654
30265
|
}
|
|
29655
30266
|
for (const runner of lambdaRegistry.values()) {
|
|
29656
|
-
|
|
30267
|
+
bootingLambda = flattenToOneLine(runner.logicalId);
|
|
30268
|
+
logger.info(`Booting Lambda target '${bootingLambda}' behind the ALB front-door...`);
|
|
29657
30269
|
await runner.start();
|
|
29658
30270
|
}
|
|
30271
|
+
bootingLambda = void 0;
|
|
29659
30272
|
} catch (err) {
|
|
29660
30273
|
await Promise.allSettled(servers.map((s) => s.close()));
|
|
29661
30274
|
await Promise.allSettled([...lambdaRegistry.values()].map((r) => r.stop()));
|
|
30275
|
+
if (bootingLambda !== void 0) throw new LocalStartServiceError(`Failed to boot ALB Lambda target '${bootingLambda}': ${err instanceof Error ? err.message : String(err)}`);
|
|
29662
30276
|
throw new LocalStartServiceError(`Failed to start ALB front-door: ${err instanceof Error ? err.message : String(err)}. If a listener port is privileged (< 1024), remap it to a non-privileged host port with --lb-port <listenerPort>=<hostPort> (e.g. --lb-port 80=8080).`);
|
|
29663
30277
|
}
|
|
29664
30278
|
const frontDoorByService = /* @__PURE__ */ new Map();
|
|
@@ -29776,11 +30390,11 @@ async function resolvePlaceholderAccount$1(arn, region, profile) {
|
|
|
29776
30390
|
}));
|
|
29777
30391
|
try {
|
|
29778
30392
|
const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
29779
|
-
if (!account) throw new LocalStartServiceError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN
|
|
30393
|
+
if (!account) throw new LocalStartServiceError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN ${displayUntrustedValue(arn)}.`);
|
|
29780
30394
|
return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
|
|
29781
30395
|
} catch (err) {
|
|
29782
30396
|
if (err instanceof LocalStartServiceError) throw err;
|
|
29783
|
-
throw new LocalStartServiceError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN
|
|
30397
|
+
throw new LocalStartServiceError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN ${displayUntrustedValue(arn)}: ${describeAwsFailureForWarn(err, "STS GetCallerIdentity (task-role placeholder)")}. Pass the ARN explicitly: --assume-task-role <arn>`);
|
|
29784
30398
|
} finally {
|
|
29785
30399
|
sts.destroy();
|
|
29786
30400
|
}
|
|
@@ -30353,13 +30967,13 @@ async function resolvePlaceholderAccount(arn, region, profile) {
|
|
|
30353
30967
|
}));
|
|
30354
30968
|
try {
|
|
30355
30969
|
const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
30356
|
-
if (!account) throw new PlaceholderAccountError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN
|
|
30970
|
+
if (!account) throw new PlaceholderAccountError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN ${displayUntrustedValue(arn)}. Pass the ARN explicitly: --assume-task-role <arn>`);
|
|
30357
30971
|
return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
|
|
30358
30972
|
} catch (err) {
|
|
30359
30973
|
if (err instanceof PlaceholderAccountError) throw err;
|
|
30360
|
-
const shownArn =
|
|
30974
|
+
const shownArn = displayUntrustedValue(arn);
|
|
30361
30975
|
const detail = describeAwsFailureForWarn(err, "STS GetCallerIdentity (task-role placeholder)");
|
|
30362
|
-
throw new PlaceholderAccountError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN
|
|
30976
|
+
throw new PlaceholderAccountError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN ${shownArn}: ${detail}. Pass the ARN explicitly: --assume-task-role <arn>`);
|
|
30363
30977
|
} finally {
|
|
30364
30978
|
sts.destroy();
|
|
30365
30979
|
}
|
|
@@ -32124,12 +32738,17 @@ function resolveOrigins(dc, template, stack, overrides) {
|
|
|
32124
32738
|
continue;
|
|
32125
32739
|
}
|
|
32126
32740
|
const localDirs = bucketLogicalId && manifest ? resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId) : [];
|
|
32127
|
-
if (localDirs.length > 0)
|
|
32128
|
-
|
|
32129
|
-
|
|
32130
|
-
|
|
32131
|
-
|
|
32132
|
-
|
|
32741
|
+
if (localDirs.length > 0) {
|
|
32742
|
+
const { assetOutdir } = assetPathDirs(stack);
|
|
32743
|
+
const hideDotfilesIn = localDirs.filter((d) => absoluteAssemblyPathEscape(assetOutdir, d) !== void 0);
|
|
32744
|
+
out.set(originId, {
|
|
32745
|
+
kind: "s3",
|
|
32746
|
+
originId,
|
|
32747
|
+
localDirs,
|
|
32748
|
+
fromAssembly: true,
|
|
32749
|
+
...hideDotfilesIn.length > 0 && { hideDotfilesIn }
|
|
32750
|
+
});
|
|
32751
|
+
} else {
|
|
32133
32752
|
const literalBucket = bucketLogicalId === void 0 ? s3Domain.bucketName : void 0;
|
|
32134
32753
|
out.set(originId, {
|
|
32135
32754
|
kind: "s3-unresolved",
|
|
@@ -32204,8 +32823,8 @@ function loadStackManifest(stack) {
|
|
|
32204
32823
|
* object key cannot be matched to a manifest file asset.
|
|
32205
32824
|
*/
|
|
32206
32825
|
function resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId) {
|
|
32207
|
-
|
|
32208
|
-
|
|
32826
|
+
if (!stack.assetManifestPath) return [];
|
|
32827
|
+
const { manifestDir, assetOutdir } = assetPathDirs(stack);
|
|
32209
32828
|
const dirs = [];
|
|
32210
32829
|
for (const resource of Object.values(template.Resources ?? {})) {
|
|
32211
32830
|
if (!String(resource.Type).startsWith("Custom::CDKBucketDeployment")) continue;
|
|
@@ -32216,7 +32835,17 @@ function resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId)
|
|
|
32216
32835
|
if (typeof key !== "string") continue;
|
|
32217
32836
|
const asset = findFileAssetByObjectKey(manifest, key);
|
|
32218
32837
|
if (!asset) continue;
|
|
32219
|
-
dirs.push(
|
|
32838
|
+
dirs.push(resolveAssetSourcePath({
|
|
32839
|
+
manifestDir,
|
|
32840
|
+
value: asset.source.path,
|
|
32841
|
+
assetOutdir,
|
|
32842
|
+
absolute: "honour-warn",
|
|
32843
|
+
field: "source.path",
|
|
32844
|
+
subject: `BucketDeployment source asset for bucket ${displayUntrustedValue(bucketLogicalId)}`,
|
|
32845
|
+
action: "serve it",
|
|
32846
|
+
sink: "serve every file under that directory as this bucket's local S3 origin",
|
|
32847
|
+
wrapError: (message) => new Error(message)
|
|
32848
|
+
}));
|
|
32220
32849
|
}
|
|
32221
32850
|
}
|
|
32222
32851
|
return dirs;
|
|
@@ -32653,14 +33282,16 @@ function normalizeHeaders(headers) {
|
|
|
32653
33282
|
*/
|
|
32654
33283
|
function serveFromStaticOrigin(input) {
|
|
32655
33284
|
const key = uriToKey(input.uri, input.defaultRootObject);
|
|
32656
|
-
const
|
|
33285
|
+
const containLinks = input.containLinks === true;
|
|
33286
|
+
const hideIn = new Set(input.hideDotfilesIn ?? []);
|
|
33287
|
+
const direct = readKey(input.localDirs, key, containLinks, hideIn);
|
|
32657
33288
|
if (direct) return {
|
|
32658
33289
|
statusCode: 200,
|
|
32659
33290
|
headers: { "content-type": contentTypeForKey(key) },
|
|
32660
33291
|
body: direct
|
|
32661
33292
|
};
|
|
32662
33293
|
for (const candidate of resolveErrorResponseCandidates(input.customErrorResponses)) {
|
|
32663
|
-
const body = readKey(input.localDirs, candidate.errorKey);
|
|
33294
|
+
const body = readKey(input.localDirs, candidate.errorKey, containLinks, hideIn);
|
|
32664
33295
|
if (body) return {
|
|
32665
33296
|
statusCode: candidate.responseCode,
|
|
32666
33297
|
headers: { "content-type": contentTypeForKey(candidate.errorKey) },
|
|
@@ -32714,15 +33345,30 @@ function uriToKey(uri, defaultRootObject) {
|
|
|
32714
33345
|
}
|
|
32715
33346
|
/**
|
|
32716
33347
|
* Read a key from the first directory that contains it as a regular file.
|
|
32717
|
-
* Path-traversal safe:
|
|
32718
|
-
*
|
|
32719
|
-
*
|
|
33348
|
+
* Path-traversal safe: a `../` in the key never leaves the origin directory
|
|
33349
|
+
* (`safeJoin`, always), and with `containLinks` neither does a symlink.
|
|
33350
|
+
* Returns `undefined` when no directory has the key.
|
|
33351
|
+
*
|
|
33352
|
+
* The symlink half is {@link realPathInsideRoot}, not `safeJoin`: `safeJoin`
|
|
33353
|
+
* judges the path's TEXT, and `statSync` / `readFileSync` follow links, so a
|
|
33354
|
+
* `cdk.out/asset.x/index.html -> ~/.aws/credentials` inside a contained origin
|
|
33355
|
+
* directory used to be served (go-to-k/cdk-local#745).
|
|
32720
33356
|
*/
|
|
32721
|
-
function readKey(localDirs, key) {
|
|
33357
|
+
function readKey(localDirs, key, containLinks, hideDotfilesIn) {
|
|
32722
33358
|
if (key === "") return void 0;
|
|
32723
33359
|
for (const dir of localDirs) {
|
|
32724
|
-
const
|
|
32725
|
-
if (!
|
|
33360
|
+
const joined = safeJoin(dir, key);
|
|
33361
|
+
if (!joined) continue;
|
|
33362
|
+
let resolved = joined;
|
|
33363
|
+
if (containLinks) {
|
|
33364
|
+
const real = realPathInsideRoot(dir, joined);
|
|
33365
|
+
if (real === false) continue;
|
|
33366
|
+
if (real !== void 0) resolved = real;
|
|
33367
|
+
}
|
|
33368
|
+
if (hideDotfilesIn.has(dir) && isHiddenKey(dir, key, resolved)) {
|
|
33369
|
+
if (isExistingFile(resolved)) warnHiddenKey(dir, key, resolved);
|
|
33370
|
+
continue;
|
|
33371
|
+
}
|
|
32726
33372
|
try {
|
|
32727
33373
|
if (statSync(resolved).isFile()) return readFileSync(resolved);
|
|
32728
33374
|
} catch {}
|
|
@@ -32739,6 +33385,68 @@ function safeJoin(dir, key) {
|
|
|
32739
33385
|
if (candidate !== root && !candidate.startsWith(rootWithSep)) return void 0;
|
|
32740
33386
|
return candidate;
|
|
32741
33387
|
}
|
|
33388
|
+
/**
|
|
33389
|
+
* `file`'s REAL path (every symbolic link followed by the kernel) when it lies
|
|
33390
|
+
* inside `root`'s real path; `false` when it leaves it, with a warning once per
|
|
33391
|
+
* file; `undefined` when either does not resolve — the read after it fails the
|
|
33392
|
+
* same way, so it reaches nothing.
|
|
33393
|
+
*/
|
|
33394
|
+
function realPathInsideRoot(root, file) {
|
|
33395
|
+
let realFile;
|
|
33396
|
+
let realRoot;
|
|
33397
|
+
try {
|
|
33398
|
+
realFile = realpathSync.native(file);
|
|
33399
|
+
realRoot = realpathSync.native(root);
|
|
33400
|
+
} catch {
|
|
33401
|
+
return;
|
|
33402
|
+
}
|
|
33403
|
+
const rel = relative(realRoot, realFile);
|
|
33404
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel)) return realFile;
|
|
33405
|
+
if (!warnedEscapes.has(file)) {
|
|
33406
|
+
warnedEscapes.add(file);
|
|
33407
|
+
getLogger().warn(`Not serving ${displayUntrustedValue(file)}: it is a symbolic link to ${displayUntrustedValue(realFile)}, outside the origin directory ${displayUntrustedValue(root)} the asset manifest named. Answering as if the key did not exist.`);
|
|
33408
|
+
}
|
|
33409
|
+
return false;
|
|
33410
|
+
}
|
|
33411
|
+
/** Files already warned about; a browser re-requests the same key. */
|
|
33412
|
+
const warnedEscapes = /* @__PURE__ */ new Set();
|
|
33413
|
+
/**
|
|
33414
|
+
* Whether a key under an accepted absolute `--no-staging` folder names a
|
|
33415
|
+
* hidden entry — on the request key itself, or on the file's REAL path relative
|
|
33416
|
+
* to the folder's real path (a link inside the folder pointing at `.env`).
|
|
33417
|
+
* `.well-known` is the one hidden name served, for parity with a deployed
|
|
33418
|
+
* bucket's ACME / app-association files. The caller warns, and only for an
|
|
33419
|
+
* entry that exists.
|
|
33420
|
+
*/
|
|
33421
|
+
function isHiddenKey(dir, key, resolved) {
|
|
33422
|
+
const hidden = (rel) => rel.split(/[\\/]/).some((c) => c.startsWith(".") && c !== "." && c !== ".." && c !== ".well-known");
|
|
33423
|
+
let realRel = "";
|
|
33424
|
+
try {
|
|
33425
|
+
realRel = relative(realpathSync.native(dir), realpathSync.native(resolved));
|
|
33426
|
+
} catch {
|
|
33427
|
+
realRel = "";
|
|
33428
|
+
}
|
|
33429
|
+
return hidden(key) || hidden(realRel);
|
|
33430
|
+
}
|
|
33431
|
+
function isExistingFile(p) {
|
|
33432
|
+
try {
|
|
33433
|
+
return statSync(p).isFile();
|
|
33434
|
+
} catch {
|
|
33435
|
+
return false;
|
|
33436
|
+
}
|
|
33437
|
+
}
|
|
33438
|
+
/**
|
|
33439
|
+
* Warn once per withheld FILE. Keyed on the resolved path, not the request
|
|
33440
|
+
* key: `x/../.env`, `xx/../.env`, ... are unbounded spellings of one file, and
|
|
33441
|
+
* keying on them let any client grow this set and the log without limit.
|
|
33442
|
+
*/
|
|
33443
|
+
function warnHiddenKey(dir, key, resolved) {
|
|
33444
|
+
if (warnedHiddenKeys.has(resolved)) return;
|
|
33445
|
+
warnedHiddenKeys.add(resolved);
|
|
33446
|
+
getLogger().warn(`Not serving ${displayUntrustedValue(key)} from ${displayUntrustedValue(dir)}: it is a hidden entry in a cdk synth --no-staging source folder (your own tree, not a staged asset). Answering as if the key did not exist.`);
|
|
33447
|
+
}
|
|
33448
|
+
/** Hidden keys already warned about. */
|
|
33449
|
+
const warnedHiddenKeys = /* @__PURE__ */ new Set();
|
|
32742
33450
|
function stripLeadingSlash(s) {
|
|
32743
33451
|
return s.startsWith("/") ? s.replace(/^\/+/, "") : s;
|
|
32744
33452
|
}
|
|
@@ -33105,6 +33813,8 @@ async function serveFromOrigin(origin, args) {
|
|
|
33105
33813
|
if (origin.kind !== "s3") return void 0;
|
|
33106
33814
|
const result = serveFromStaticOrigin({
|
|
33107
33815
|
localDirs: origin.localDirs,
|
|
33816
|
+
...origin.fromAssembly === true && { containLinks: true },
|
|
33817
|
+
...origin.hideDotfilesIn !== void 0 && { hideDotfilesIn: origin.hideDotfilesIn },
|
|
33108
33818
|
uri: args.uri,
|
|
33109
33819
|
...distribution.defaultRootObject !== void 0 && { defaultRootObject: distribution.defaultRootObject },
|
|
33110
33820
|
customErrorResponses: distribution.customErrorResponses
|
|
@@ -33211,9 +33921,9 @@ function createS3OriginReader(bucketName, options = {}) {
|
|
|
33211
33921
|
};
|
|
33212
33922
|
if (direct.kind === "denied" && !deniedWarned) {
|
|
33213
33923
|
deniedWarned = true;
|
|
33214
|
-
getLogger().warn(`S3 denied reading
|
|
33924
|
+
getLogger().warn(`S3 denied reading ${displayUntrustedValue(key)} from bucket ${displayUntrustedValue(bucketName)}. If this is an OAC-locked / private bucket your credentials cannot read, point the origin at a local directory with --origin <originId>=<dir> (or use credentials with s3:GetObject on the bucket). ${getEmbedConfig().cliName} start-cloudfront reads the origin from real S3.`);
|
|
33215
33925
|
}
|
|
33216
|
-
if (direct.kind === "error") getLogger().warn(`S3 read of
|
|
33926
|
+
if (direct.kind === "error") getLogger().warn(`S3 read of ${displayUntrustedValue(key)} from bucket ${displayUntrustedValue(bucketName)} failed: ${direct.message}`);
|
|
33217
33927
|
}
|
|
33218
33928
|
for (const candidate of resolveErrorResponseCandidates(input.customErrorResponses)) {
|
|
33219
33929
|
const page = await fetchObject(candidate.errorKey);
|
|
@@ -33222,7 +33932,7 @@ function createS3OriginReader(bucketName, options = {}) {
|
|
|
33222
33932
|
headers: { "content-type": contentTypeForKey(candidate.errorKey) },
|
|
33223
33933
|
body: page.body
|
|
33224
33934
|
};
|
|
33225
|
-
if (page.kind === "denied" || page.kind === "error") getLogger().warn(`S3 could not read custom-error page
|
|
33935
|
+
if (page.kind === "denied" || page.kind === "error") getLogger().warn(`S3 could not read custom-error page ${displayUntrustedValue(candidate.errorKey)} from bucket ${displayUntrustedValue(bucketName)} (${page.kind}); falling through.`);
|
|
33226
33936
|
}
|
|
33227
33937
|
return {
|
|
33228
33938
|
statusCode: 404,
|
|
@@ -41071,4 +41781,4 @@ function addStudioSpecificOptions(cmd) {
|
|
|
41071
41781
|
|
|
41072
41782
|
//#endregion
|
|
41073
41783
|
export { applyEdgeResponseResult as $, buildJwksUrlFromIssuer as $n, resolveCfnStackName as $r, buildCloudMapIndex as $t, startAgentCoreHttpServer as A, describeCredentialLoadFailure as Ai, classifySourceChange as An, ConnectionRegistry as Ar, addRunTaskSpecificOptions as At, idFromArn as B, buildStageMap as Bn, resolveRuntimeFileExtension as Br, resolveEcsAssumeRoleOption as Bt, addListSpecificOptions as C, resolveAgentCoreTarget as Ci, waitForAgentCorePing as Cn, tryParseStatus as Cr, parseLbPortOverrides as Ct, createLocalStartAgentCoreCommand as D, tryResolveImageFnJoin as Di, computeCodeImageTag as Dn, probeHostGatewaySupport as Dr, addStartServiceSpecificOptions as Dt, addStartAgentCoreSpecificOptions as E, substituteImagePlaceholders as Ei, buildAgentCoreCodeImage as En, HOST_GATEWAY_MIN_VERSION as Er, resolveAlbFrontDoor as Et, createLocalStartCloudFrontCommand as F, createWatchPredicates as Fn, buildDisconnectEvent as Fr, addImageOverrideOptions as Ft, classifyS3Error as G, filterRoutesByApiIdentifiers as Gn, substituteEnvVarsFromState as Gr, enforceImageOverrideOrphans as Gt, createDeployedKvsDataSource as H, resolveEnvVars$1 as Hn, EcsTaskResolutionError as Hr, runEcsServiceEmulator as Ht, normalizeKvsFileKeys as I, resolveApiTargetSubset as In, buildMessageEvent as Ir, buildEcsImageResolutionContext$1 as It, startCloudFrontServer as J, startApiServer as Jn, createLocalStateProvider as Jr, resolveImageOverrides as Jt, createS3OriginReader as K, groupRoutesByServer as Kn, substituteEnvVarsFromStateAsync as Kr, mergeForService as Kt, parseKvsFileOverrides as L, createAuthorizerCache as Ln, architectureToPlatform as Lr, ecsClusterOption as Lt, startAgentCoreWsBridge as M, resolveProfileCredentials as Mi, createLocalInvokeCommand as Mn, handleConnectionsRequest as Mr, MAX_TASKS_SUBNET_RANGE_CAP as Mt, LocalStartCloudFrontError as N, buildProxyClientConfig as Ni, addStartApiSpecificOptions as Nn, parseConnectionsPath as Nr, addCommonEcsServiceOptions as Nt, buildAgentCoreServeAuthCheck as O, LocalInvokeBuildError as Oi, renderCodeDockerfile as On, resolveHostGatewayExtraHosts as Or, createLocalStartServiceCommand as Ot, addStartCloudFrontSpecificOptions as P, isProxyEnvConfigured as Pi, createLocalStartApiCommand as Pn, buildConnectEvent as Pr, addEcsAssumeRoleOptions as Pt, applyEdgeRequestResult as Q, buildCognitoJwksUrl as Qn, resolveCfnRegion as Qr, listPinnedTargets as Qt, parseOriginOverrides as R, createFileWatcher as Rn, buildContainerImage as Rr, parseMaxTasks as Rt, StudioEventBus as S, pickAgentCoreCandidateStack as Si, waitForAgentCoreHttpReady as Sn, selectIntegrationResponse as Sr, createLocalStartAlbCommand as St, formatTargetListing as T, formatStateRemedy as Ti, SUPPORTED_CODE_RUNTIMES as Tn, HOST_DOCKER_INTERNAL_GATEWAY as Tr, isApplicationLoadBalancer as Tt, resolveDeployedKvsArnByName as U, availableApiIdentifiers as Un, substituteAgainstState as Ur, ImageOverrideError as Ut, resolveKvsModulesForDistribution as V, materializeLayerFromArn as Vn, resolveRuntimeImage as Vr, resolveSharedSidecarCredentials as Vt, resolveDeployedOriginBucket as W, filterRoutesByApiIdentifier as Wn, substituteAgainstStateAsync as Wr, buildImageOverrideTag as Wt, serveFromStaticOrigin as X, resolveServiceIntegrationParameters as Xn, rejectExplicitCfnStackWithMultipleStacks as Xr, describePinnedImageUri as Xt, resolveErrorResponseCandidates as Y, resolveSelectionExpression as Yn, isCfnFlagPresent as Yr, runImageOverrideBuilds as Yt, serveLambdaUrlOrigin as Z, defaultCredentialsLoader as Zn, resolveCfnFallbackRegion as Zr, isLocalCdkAssetImage as Zt, filterStudioTargetGroups as _, AGENTCORE_AGUI_PROTOCOL as _i, parseSseForJsonRpc as _n, applyAuthorizerOverlay as _r, createCloudFrontModule as _t, createLocalStudioCommand as a, countTargets as ai, attachContainerLogStreamer as an, computeRequestIdentityHash as ar, describeS3OriginDomain as at, renderStudioHtml as b, AGENTCORE_RUNTIME_TYPE as bi, AGENTCORE_SESSION_ID_HEADER as bn, evaluateResponseParameters as br, addAlbSpecificOptions as bt, startStudioProxy as c, discoverWebSocketApis as ci, bridgeAgentCoreWs as cn, invokeTokenAuthorizer as cr, pickFunctionUrlLogicalIdFromOrigin as ct, createStudioDispatcher as d, parseSelectionExpressionPath as di, A2A_PATH as dn, buildCorsConfigByApiId as dr, pickTargetFunctionLogicalId as dt, CfnLocalStateProvider as ei, CloudMapRegistry as en, createJwksCache as er, buildEdgeRequestEvent as et, filterStudioCustomResources as f, webSocketApiMatchesIdentifier as fi, a2aInvokeOnce as fn, buildCorsConfigFromCloudFrontChain as fr, resolveCloudFrontDistribution as ft, annotatePinnedEcsTargets as g, AGENTCORE_A2A_PROTOCOL as gi, mcpInvokeOnce as gn, translateLambdaResponse as gr, stripCloudFrontImport as gt, annotateEcsTaskPinnedTargets as h, resolveLambdaArnIntrinsic as hi, MCP_PROTOCOL_VERSION as hn, matchRoute as hr, runViewerResponse as ht, coerceStopRequest as i, resolveSingleTarget as ii, getContainerNetworkIp as in, buildMethodArn as ir, CLOUDFRONT_DISTRIBUTION_TYPE as it, attachAgentCoreWsBridge as j, buildStsClientConfig as ji, addInvokeSpecificOptions as jn, buildMgmtEndpointEnvUrl as jr, createLocalRunTaskCommand as jt, selectServeInboundAuth as k, describeAwsFailureForWarn as ki, toCmdArgv as kn, bufferToBody as kr, serviceStrategy as kt, relayServeRequest as l, discoverWebSocketApisOrThrow as li, invokeAgentCoreWs as ln, attachAuthorizers as lr, pickKvsLogicalIdFromArn as lt, annotateAlbPinnedBackingServices as m, pickRefLogicalId as mi, MCP_PATH as mn, matchPreflight as mr, runViewerRequest as mt, coerceRunRequest as n, resolveSsmParameters as ni, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as nn, verifyJwtAuthorizer as nr, edgeHeadersToHttp as nt, resolveServeBaseUrl as o, listTargets as oi, addInvokeAgentCoreSpecificOptions as on, evaluateCachedLambdaPolicy as or, extractKvsAssociations as ot, isCustomResourceLambdaTarget as p, discoverRoutes as pi, MCP_CONTAINER_PORT as pn, isFunctionUrlOacFronted as pr, compileCloudFrontFunction as pt, matchBehavior as q, readMtlsMaterialsFromDisk as qn, LocalStateSourceError as qr, parseImageOverrideFlags as qt, coerceServeRequest as r, resolveWatchConfig as ri, setShadowReadyTimeoutMs as rn, verifyJwtViaDiscovery as rr, httpHeadersToEdge as rt, createStudioServeManager as s, availableWebSocketApiIdentifiers as si, createLocalInvokeAgentCoreCommand as sn, invokeRequestAuthorizer as sr, isCloudFrontDistribution as st, addStudioSpecificOptions as t, collectSsmParameterRefs as ti, DEFAULT_SHADOW_READY_TIMEOUT_MS as tn, verifyCognitoJwt as tr, buildEdgeResponseEvent as tt, reinvoke as u, filterWebSocketApisByIdentifiers as ui, A2A_CONTAINER_PORT as un, applyCorsResponseHeaders as ur, pickLambdaEdgeFunctionLogicalId as ut, startStudioServer as v, AGENTCORE_HTTP_PROTOCOL as vi, AGENTCORE_SIGV4_SERVICE as vn, buildHttpApiV2Event as vr, createLocalFileKvsDataSource as vt, createLocalListCommand as w, derivePseudoParametersFromRegion as wi, downloadAndExtractS3Bundle as wn, VtlEvaluationError as wr, resolveAlbTarget as wt, createStudioStore as x, AgentCoreResolutionError as xi, invokeAgentCore as xn, pickResponseTemplate as xr, albStrategy as xt, toStudioTargetGroups as y, AGENTCORE_MCP_PROTOCOL as yi, signAgentCoreInvocation as yn, buildRestV1Event as yr, createUnboundCloudFrontModule as yt, resolveCloudFrontTarget as z, attachStageContext as zn, resolveRuntimeCodeMountPath as zr, parseRestartPolicy as zt };
|
|
41074
|
-
//# sourceMappingURL=local-studio-
|
|
41784
|
+
//# sourceMappingURL=local-studio-Cdq8klwg.js.map
|