@appsforgood/agent-kit-runtime 0.1.1 → 0.1.3
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/CHANGELOG.md +12 -0
- package/dist/index.js +61 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @appsforgood/agent-kit-runtime
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Canonicalize Windows paths with the native filesystem resolver before enforcing instruction, tool, and worktree containment.
|
|
8
|
+
|
|
9
|
+
## 0.1.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Verify Git top-level roots through repository context so Windows long and short temp-path forms do not reject valid runtime worktrees.
|
|
14
|
+
|
|
3
15
|
## 0.1.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -1324,9 +1324,51 @@ var ModelRouter = class {
|
|
|
1324
1324
|
};
|
|
1325
1325
|
|
|
1326
1326
|
// src/roster.ts
|
|
1327
|
-
import { readFileSync as readFileSync3
|
|
1328
|
-
import { dirname as dirname2, join as join3, resolve as
|
|
1327
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1328
|
+
import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
|
|
1329
1329
|
import { z as z4 } from "zod";
|
|
1330
|
+
|
|
1331
|
+
// src/security/paths.ts
|
|
1332
|
+
import { realpathSync } from "fs";
|
|
1333
|
+
import { resolve as resolve2 } from "path";
|
|
1334
|
+
function isSensitiveRelativePath(value) {
|
|
1335
|
+
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1336
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
1337
|
+
const lower = segments.map((segment) => segment.toLowerCase());
|
|
1338
|
+
const basename2 = lower.at(-1) ?? "";
|
|
1339
|
+
if (lower.includes(".git")) return true;
|
|
1340
|
+
if (lower[0] === ".agent-kit" && lower[1] === "runtime" && basename2 !== ".gitignore") return true;
|
|
1341
|
+
if (basename2 === ".npmrc" || basename2 === ".pypirc" || basename2 === ".netrc") return true;
|
|
1342
|
+
if (["id_rsa", "id_dsa", "id_ecdsa", "id_ed25519"].includes(basename2)) return true;
|
|
1343
|
+
if (/\.(?:pem|key|p12|pfx)$/.test(basename2)) return true;
|
|
1344
|
+
if (basename2 === ".env" || basename2.startsWith(".env.")) {
|
|
1345
|
+
return !/\.(?:example|sample|template)$/.test(basename2);
|
|
1346
|
+
}
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
function pathIdentity(value, caseInsensitive = process.platform === "win32") {
|
|
1350
|
+
const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "") || "/";
|
|
1351
|
+
return caseInsensitive ? normalized.toLowerCase() : normalized;
|
|
1352
|
+
}
|
|
1353
|
+
function canonicalPath(value) {
|
|
1354
|
+
return realpathSync.native(resolve2(value));
|
|
1355
|
+
}
|
|
1356
|
+
function pathsEqual(left, right, caseInsensitive = process.platform === "win32") {
|
|
1357
|
+
return pathIdentity(left, caseInsensitive) === pathIdentity(right, caseInsensitive);
|
|
1358
|
+
}
|
|
1359
|
+
function isPathWithin(root, candidate, caseInsensitive = process.platform === "win32") {
|
|
1360
|
+
const rootIdentity = pathIdentity(root, caseInsensitive);
|
|
1361
|
+
const candidateIdentity = pathIdentity(candidate, caseInsensitive);
|
|
1362
|
+
const prefix = rootIdentity.endsWith("/") ? rootIdentity : `${rootIdentity}/`;
|
|
1363
|
+
return candidateIdentity === rootIdentity || candidateIdentity.startsWith(prefix);
|
|
1364
|
+
}
|
|
1365
|
+
function assertSafeToolPath(value) {
|
|
1366
|
+
const segments = value.replace(/\\/g, "/").split("/");
|
|
1367
|
+
if (segments.includes("..")) throw new Error(`Path traversal is not available to runtime tools: ${value}`);
|
|
1368
|
+
if (isSensitiveRelativePath(value)) throw new Error(`Sensitive path is not available to runtime tools: ${value}`);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/roster.ts
|
|
1330
1372
|
var AgentContract = z4.object({
|
|
1331
1373
|
id: z4.string().min(1),
|
|
1332
1374
|
name: z4.string().optional(),
|
|
@@ -1416,11 +1458,12 @@ function validateWorkflowBounds(workflow, config) {
|
|
|
1416
1458
|
}
|
|
1417
1459
|
function readAgentInstructions(cwd, agent) {
|
|
1418
1460
|
if (!agent.file) return `${agent.name ?? agent.id}. Skills: ${agent.skills.join(", ")}.`;
|
|
1419
|
-
if (
|
|
1420
|
-
const root =
|
|
1421
|
-
const candidate =
|
|
1422
|
-
|
|
1423
|
-
|
|
1461
|
+
if (resolve3(agent.file) === agent.file) throw new Error(`Agent instruction paths must be relative: ${agent.file}`);
|
|
1462
|
+
const root = canonicalPath(cwd);
|
|
1463
|
+
const candidate = resolve3(root, agent.file);
|
|
1464
|
+
if (!isPathWithin(root, candidate)) throw new Error(`Agent instruction path escapes the project: ${agent.file}`);
|
|
1465
|
+
const parent = canonicalPath(dirname2(candidate));
|
|
1466
|
+
if (!isPathWithin(root, parent)) throw new Error(`Agent instruction path escapes the project: ${agent.file}`);
|
|
1424
1467
|
return readFileSync3(candidate, "utf8").slice(0, 2e5);
|
|
1425
1468
|
}
|
|
1426
1469
|
|
|
@@ -1577,45 +1620,8 @@ var DockerSandbox = class {
|
|
|
1577
1620
|
|
|
1578
1621
|
// src/tools.ts
|
|
1579
1622
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1580
|
-
import { existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync as readdirSync2,
|
|
1581
|
-
import { dirname as dirname3, isAbsolute, join as join4, relative, resolve as
|
|
1582
|
-
|
|
1583
|
-
// src/security/paths.ts
|
|
1584
|
-
function isSensitiveRelativePath(value) {
|
|
1585
|
-
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1586
|
-
const segments = normalized.split("/").filter(Boolean);
|
|
1587
|
-
const lower = segments.map((segment) => segment.toLowerCase());
|
|
1588
|
-
const basename2 = lower.at(-1) ?? "";
|
|
1589
|
-
if (lower.includes(".git")) return true;
|
|
1590
|
-
if (lower[0] === ".agent-kit" && lower[1] === "runtime" && basename2 !== ".gitignore") return true;
|
|
1591
|
-
if (basename2 === ".npmrc" || basename2 === ".pypirc" || basename2 === ".netrc") return true;
|
|
1592
|
-
if (["id_rsa", "id_dsa", "id_ecdsa", "id_ed25519"].includes(basename2)) return true;
|
|
1593
|
-
if (/\.(?:pem|key|p12|pfx)$/.test(basename2)) return true;
|
|
1594
|
-
if (basename2 === ".env" || basename2.startsWith(".env.")) {
|
|
1595
|
-
return !/\.(?:example|sample|template)$/.test(basename2);
|
|
1596
|
-
}
|
|
1597
|
-
return false;
|
|
1598
|
-
}
|
|
1599
|
-
function pathIdentity(value, caseInsensitive = process.platform === "win32") {
|
|
1600
|
-
const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "") || "/";
|
|
1601
|
-
return caseInsensitive ? normalized.toLowerCase() : normalized;
|
|
1602
|
-
}
|
|
1603
|
-
function pathsEqual(left, right, caseInsensitive = process.platform === "win32") {
|
|
1604
|
-
return pathIdentity(left, caseInsensitive) === pathIdentity(right, caseInsensitive);
|
|
1605
|
-
}
|
|
1606
|
-
function isPathWithin(root, candidate, caseInsensitive = process.platform === "win32") {
|
|
1607
|
-
const rootIdentity = pathIdentity(root, caseInsensitive);
|
|
1608
|
-
const candidateIdentity = pathIdentity(candidate, caseInsensitive);
|
|
1609
|
-
const prefix = rootIdentity.endsWith("/") ? rootIdentity : `${rootIdentity}/`;
|
|
1610
|
-
return candidateIdentity === rootIdentity || candidateIdentity.startsWith(prefix);
|
|
1611
|
-
}
|
|
1612
|
-
function assertSafeToolPath(value) {
|
|
1613
|
-
const segments = value.replace(/\\/g, "/").split("/");
|
|
1614
|
-
if (segments.includes("..")) throw new Error(`Path traversal is not available to runtime tools: ${value}`);
|
|
1615
|
-
if (isSensitiveRelativePath(value)) throw new Error(`Sensitive path is not available to runtime tools: ${value}`);
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
// src/tools.ts
|
|
1623
|
+
import { existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync as readdirSync2, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1624
|
+
import { dirname as dirname3, isAbsolute, join as join4, relative, resolve as resolve4 } from "path";
|
|
1619
1625
|
var READ_TOOLS = [
|
|
1620
1626
|
{
|
|
1621
1627
|
name: "read_file",
|
|
@@ -1779,13 +1785,13 @@ ${result.stderr}` : ""}`.trim(),
|
|
|
1779
1785
|
}
|
|
1780
1786
|
};
|
|
1781
1787
|
function safeRoot(root) {
|
|
1782
|
-
return
|
|
1788
|
+
return canonicalPath(root);
|
|
1783
1789
|
}
|
|
1784
1790
|
function safeExistingPath(root, requested) {
|
|
1785
1791
|
if (!requested || isAbsolute(requested)) throw new Error("Tool paths must be non-empty and relative.");
|
|
1786
1792
|
assertSafeToolPath(requested);
|
|
1787
1793
|
const rootPath = safeRoot(root);
|
|
1788
|
-
const candidate =
|
|
1794
|
+
const candidate = canonicalPath(resolve4(rootPath, requested));
|
|
1789
1795
|
if (!isPathWithin(rootPath, candidate)) throw new Error(`Path escapes the worktree: ${requested}`);
|
|
1790
1796
|
return candidate;
|
|
1791
1797
|
}
|
|
@@ -1793,11 +1799,11 @@ function safeNewPath(root, requested) {
|
|
|
1793
1799
|
if (!requested || isAbsolute(requested)) throw new Error("Tool paths must be non-empty and relative.");
|
|
1794
1800
|
assertSafeToolPath(requested);
|
|
1795
1801
|
const rootPath = safeRoot(root);
|
|
1796
|
-
const candidate =
|
|
1802
|
+
const candidate = resolve4(rootPath, requested);
|
|
1797
1803
|
if (!isPathWithin(rootPath, candidate)) throw new Error(`Path escapes the worktree: ${requested}`);
|
|
1798
1804
|
let parent = dirname3(candidate);
|
|
1799
1805
|
while (!existsSync2(parent) && !pathsEqual(parent, rootPath)) parent = dirname3(parent);
|
|
1800
|
-
const realParent =
|
|
1806
|
+
const realParent = canonicalPath(parent);
|
|
1801
1807
|
if (!isPathWithin(rootPath, realParent)) throw new Error(`Path parent escapes the worktree: ${requested}`);
|
|
1802
1808
|
return candidate;
|
|
1803
1809
|
}
|
|
@@ -2032,9 +2038,9 @@ function compileCouncilGraph(dependencies) {
|
|
|
2032
2038
|
|
|
2033
2039
|
// src/worktree.ts
|
|
2034
2040
|
import { execFile } from "child_process";
|
|
2035
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3,
|
|
2041
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, rmSync as rmSync3 } from "fs";
|
|
2036
2042
|
import { homedir, platform } from "os";
|
|
2037
|
-
import { basename, join as join5
|
|
2043
|
+
import { basename, join as join5 } from "path";
|
|
2038
2044
|
import { promisify } from "util";
|
|
2039
2045
|
var execFileAsync = promisify(execFile);
|
|
2040
2046
|
function cacheRoot() {
|
|
@@ -2055,11 +2061,12 @@ async function git(cwd, args) {
|
|
|
2055
2061
|
var WorktreeManager = class {
|
|
2056
2062
|
sourceRoot;
|
|
2057
2063
|
constructor(sourceRoot) {
|
|
2058
|
-
this.sourceRoot =
|
|
2064
|
+
this.sourceRoot = canonicalPath(sourceRoot);
|
|
2059
2065
|
}
|
|
2060
2066
|
async inspect() {
|
|
2061
2067
|
const root = await git(this.sourceRoot, ["rev-parse", "--show-toplevel"]);
|
|
2062
|
-
|
|
2068
|
+
const prefix = await git(this.sourceRoot, ["rev-parse", "--show-prefix"]);
|
|
2069
|
+
if (prefix) throw new Error(`Runtime source root must be the Git repository root: ${root}`);
|
|
2063
2070
|
const baseCommit = await git(this.sourceRoot, ["rev-parse", "HEAD"]);
|
|
2064
2071
|
const status = await git(this.sourceRoot, ["status", "--porcelain=v1", "--untracked-files=all"]);
|
|
2065
2072
|
const tracked = await git(this.sourceRoot, ["ls-files"]);
|