@kubb/studio 5.11.1 → 5.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/index.cjs +91 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +91 -4
- package/dist/index.js.map +1 -1
- package/dist/{protocol-CrMJymjT.d.ts → protocol-BUZ91Gd4.d.cts} +12 -3
- package/dist/{protocol-CrMJymjT.d.cts → protocol-BUZ91Gd4.d.ts} +12 -3
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as InstalledExtensions, P as PublishedSnapshot, W as SnapshotUploadMetadata, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, s as AgentPermission, u as ConfigEdit } from "./protocol-
|
|
1
|
+
import { D as InstalledExtensions, P as PublishedSnapshot, W as SnapshotUploadMetadata, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, s as AgentPermission, u as ConfigEdit } from "./protocol-BUZ91Gd4.cjs";
|
|
2
2
|
import { Config, Hookable, KubbHooks } from "@kubb/core";
|
|
3
3
|
//#region src/connect.d.ts
|
|
4
4
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as InstalledExtensions, P as PublishedSnapshot, W as SnapshotUploadMetadata, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, s as AgentPermission, u as ConfigEdit } from "./protocol-
|
|
1
|
+
import { D as InstalledExtensions, P as PublishedSnapshot, W as SnapshotUploadMetadata, a as AgentConnectInfo, c as AgentPermissions, d as ConfigEditOutcome, s as AgentPermission, u as ConfigEdit } from "./protocol-BUZ91Gd4.js";
|
|
2
2
|
import { Config, Hookable, KubbHooks } from "@kubb/core";
|
|
3
3
|
//#region src/connect.d.ts
|
|
4
4
|
/**
|
package/dist/index.js
CHANGED
|
@@ -116,6 +116,27 @@ async function read(path) {
|
|
|
116
116
|
if (runtime.isBun) return Bun.file(path).text();
|
|
117
117
|
return readFile(path, { encoding: "utf8" });
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved
|
|
121
|
+
* to absolute paths first, so relative and `..`-containing inputs compare correctly.
|
|
122
|
+
*
|
|
123
|
+
* Guards a destructive or an out-of-tree operation: before wiping an output directory, check that
|
|
124
|
+
* it does not contain the project root, and before loading a path a caller supplied, check that it
|
|
125
|
+
* did not escape the directory it is allowed to read from.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* isPathInside('./src/gen', '.') // true — nested inside the root
|
|
129
|
+
* isPathInside('.', '.') // true — the same directory counts as inside
|
|
130
|
+
* isPathInside('.', './src/gen') // false — the root is not inside its own output
|
|
131
|
+
* isPathInside('../other', '.') // false — escapes the root
|
|
132
|
+
*/
|
|
133
|
+
function isPathInside(path, parent) {
|
|
134
|
+
const resolvedPath = resolve(path);
|
|
135
|
+
const resolvedParent = resolve(parent);
|
|
136
|
+
if (resolvedPath === resolvedParent) return true;
|
|
137
|
+
const rel = relative(resolvedParent, resolvedPath);
|
|
138
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
139
|
+
}
|
|
119
140
|
//#endregion
|
|
120
141
|
//#region ../../internals/utils/src/promise.ts
|
|
121
142
|
/**
|
|
@@ -154,7 +175,7 @@ function getElapsedMs(hrStart) {
|
|
|
154
175
|
}
|
|
155
176
|
//#endregion
|
|
156
177
|
//#region package.json
|
|
157
|
-
var version = "5.11.
|
|
178
|
+
var version = "5.11.2";
|
|
158
179
|
//#endregion
|
|
159
180
|
//#region src/resolveConfig.ts
|
|
160
181
|
/**
|
|
@@ -459,6 +480,33 @@ async function mergePlugins({ diskPlugins, studioPlugins, extensions = /* @__PUR
|
|
|
459
480
|
})];
|
|
460
481
|
}
|
|
461
482
|
/**
|
|
483
|
+
* Throws `KUBB_PATH_TRAVERSAL` when a plugin would write outside the config's output folder to a path
|
|
484
|
+
* the disk config does not set. Studio can change `output.path`, also inside `override`, and add
|
|
485
|
+
* plugins, so without this check any member of the Studio organization could write any file the agent
|
|
486
|
+
* can reach, including `node_modules` code the next run imports. A path from the config file stays
|
|
487
|
+
* allowed, since a monorepo may write outside its root.
|
|
488
|
+
*/
|
|
489
|
+
function assertOutputPaths({ plugins, diskPlugins, root, outputPath }) {
|
|
490
|
+
const base = path.resolve(root, outputPath);
|
|
491
|
+
const targetsOf = (plugin) => {
|
|
492
|
+
const { output, override } = plugin.options ?? {};
|
|
493
|
+
return [output, ...Array.isArray(override) ? override.map((entry) => entry?.options?.output) : []].flatMap((entry) => typeof entry?.path === "string" ? [path.resolve(base, entry.path)] : []);
|
|
494
|
+
};
|
|
495
|
+
const diskTargets = new Set(diskPlugins.flatMap(targetsOf));
|
|
496
|
+
for (const plugin of plugins) {
|
|
497
|
+
const target = targetsOf(plugin).find((resolved) => !diskTargets.has(resolved) && !isPathInside(resolved, base));
|
|
498
|
+
if (!target) continue;
|
|
499
|
+
throw new Diagnostics.Error({
|
|
500
|
+
code: Diagnostics.code.pathTraversal,
|
|
501
|
+
severity: "error",
|
|
502
|
+
message: `Plugin "${plugin.name}" would write to "${target}", outside the output folder "${base}". Studio can only set an output path inside it.`,
|
|
503
|
+
help: "Choose an output path inside the output folder, or set this path in the config file.",
|
|
504
|
+
location: { kind: "config" },
|
|
505
|
+
plugin: plugin.name
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
462
510
|
* Re-invokes the factory behind a disk adapter or parser with Studio's options merged over its own.
|
|
463
511
|
* An instance carries live functions (`parse`, `print`, ...) that cannot survive JSON, so Studio's
|
|
464
512
|
* options are a patch, not a replacement, and the factory is found from the disk instance itself: the
|
|
@@ -1424,7 +1472,7 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
|
|
|
1424
1472
|
/**
|
|
1425
1473
|
* Copies `files` into the store as one set of `jobId`. Above `maxSetMb` only the hashes are kept.
|
|
1426
1474
|
*/
|
|
1427
|
-
async function keep({ jobId, source, files, maxSetMb }) {
|
|
1475
|
+
async function keep({ jobId, source, files, maxSetMb, normalize = source === FileSource.OUTPUT }) {
|
|
1428
1476
|
const maxSetBytes = maxSetMb * MB;
|
|
1429
1477
|
const hashes = {};
|
|
1430
1478
|
let bytes = 0;
|
|
@@ -1435,8 +1483,8 @@ function createGenerationStore({ storage, maxCount, maxMb, ttlMs, now = Date.now
|
|
|
1435
1483
|
if (path.split("/").includes("..")) return;
|
|
1436
1484
|
const content = await files.storage.readItem(resolve(files.root, path));
|
|
1437
1485
|
if (content === null) return;
|
|
1438
|
-
if (
|
|
1439
|
-
hashes[path] = hashOf(
|
|
1486
|
+
if (normalize && !content.trim()) return;
|
|
1487
|
+
hashes[path] = hashOf(normalize ? `${content.trim()}\n` : content);
|
|
1440
1488
|
bytes += Buffer.byteLength(content);
|
|
1441
1489
|
if (bytes <= maxSetBytes) await storage.writeItem(`${dirOf(jobId)}${source}/${path}`, content);
|
|
1442
1490
|
}
|
|
@@ -1900,8 +1948,12 @@ var Agent = class extends RpcTarget {
|
|
|
1900
1948
|
this.#readConfigFileView(source),
|
|
1901
1949
|
this.#installedExtensions(source)
|
|
1902
1950
|
]);
|
|
1951
|
+
const output = await this.#readDiskOutput(config).catch(async (error) => {
|
|
1952
|
+
await this.#warn({ message: `Could not read the output directory: ${getErrorMessage(error)}` });
|
|
1953
|
+
});
|
|
1903
1954
|
this.#session.connected.resolve(info);
|
|
1904
1955
|
return {
|
|
1956
|
+
output,
|
|
1905
1957
|
versions: {
|
|
1906
1958
|
kubb: version,
|
|
1907
1959
|
agent: version$1
|
|
@@ -1938,6 +1990,32 @@ var Agent = class extends RpcTarget {
|
|
|
1938
1990
|
permissions: this.#permissions
|
|
1939
1991
|
};
|
|
1940
1992
|
}
|
|
1993
|
+
async #readDiskOutput(config) {
|
|
1994
|
+
if (this.#sandbox || !this.#permissions.allowRead || this.#session.active) return void 0;
|
|
1995
|
+
const files = await listDisk({
|
|
1996
|
+
root: this.#options.root,
|
|
1997
|
+
outputPath: config.output.path,
|
|
1998
|
+
maxFiles: DISK_SNAPSHOT_MAX_FILES
|
|
1999
|
+
});
|
|
2000
|
+
if (!files) return void 0;
|
|
2001
|
+
const jobId = randomUUID();
|
|
2002
|
+
const output = await this.#generations.keep({
|
|
2003
|
+
jobId,
|
|
2004
|
+
source: FileSource.OUTPUT,
|
|
2005
|
+
files,
|
|
2006
|
+
maxSetMb: this.#limits.maxSnapshotMb,
|
|
2007
|
+
normalize: false
|
|
2008
|
+
});
|
|
2009
|
+
await this.#generations.add({
|
|
2010
|
+
jobId,
|
|
2011
|
+
output
|
|
2012
|
+
});
|
|
2013
|
+
return {
|
|
2014
|
+
jobId,
|
|
2015
|
+
files: output.paths,
|
|
2016
|
+
hashes: output.hashes
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
1941
2019
|
startGeneration(input) {
|
|
1942
2020
|
const stream = createGenerationStream(this.#options.hooks, input.jobId);
|
|
1943
2021
|
const controller = new AbortController();
|
|
@@ -1990,6 +2068,7 @@ var Agent = class extends RpcTarget {
|
|
|
1990
2068
|
settled.resolve();
|
|
1991
2069
|
}
|
|
1992
2070
|
}
|
|
2071
|
+
/** Merges Studio's config over the disk config, refuses output paths outside the output folder, then generates in `root`. */
|
|
1993
2072
|
async #generate({ input, root, signal }) {
|
|
1994
2073
|
const { jobId, config: patch } = input;
|
|
1995
2074
|
const studioInput = this.#sandbox ? patch?.input : void 0;
|
|
@@ -2000,6 +2079,14 @@ var Agent = class extends RpcTarget {
|
|
|
2000
2079
|
diskPlugins: config.plugins,
|
|
2001
2080
|
studioPlugins: patch?.plugins,
|
|
2002
2081
|
extensions
|
|
2082
|
+
}).then((merged) => {
|
|
2083
|
+
assertOutputPaths({
|
|
2084
|
+
plugins: merged ?? [],
|
|
2085
|
+
diskPlugins: config.plugins,
|
|
2086
|
+
root,
|
|
2087
|
+
outputPath: config.output.path
|
|
2088
|
+
});
|
|
2089
|
+
return merged;
|
|
2003
2090
|
}),
|
|
2004
2091
|
mergeAdapter(config.adapter, patch?.adapter, extensions),
|
|
2005
2092
|
mergeParsers({
|