@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 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, realpathSync } from "fs";
1328
- import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
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 (resolve2(agent.file) === agent.file) throw new Error(`Agent instruction paths must be relative: ${agent.file}`);
1420
- const root = realpathSync(resolve2(cwd));
1421
- const candidate = resolve2(root, agent.file);
1422
- const parent = realpathSync(dirname2(candidate));
1423
- if (parent !== root && !parent.startsWith(`${root}/`)) throw new Error(`Agent instruction path escapes the project: ${agent.file}`);
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, realpathSync as realpathSync2, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
1581
- import { dirname as dirname3, isAbsolute, join as join4, relative, resolve as resolve3 } from "path";
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 realpathSync2(resolve3(root));
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 = realpathSync2(resolve3(rootPath, requested));
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 = resolve3(rootPath, requested);
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 = realpathSync2(parent);
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, realpathSync as realpathSync3, rmSync as rmSync3 } from "fs";
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, resolve as resolve4 } from "path";
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 = realpathSync3(resolve4(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
- if (!pathsEqual(realpathSync3(root), this.sourceRoot)) throw new Error(`Runtime source root must be the Git repository root: ${root}`);
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"]);