gossipcat 0.4.4 → 0.4.6

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.
@@ -3836,8 +3836,9 @@ var init_file_tools = __esm({
3836
3836
  constructor(sandbox) {
3837
3837
  this.sandbox = sandbox;
3838
3838
  }
3839
- async fileRead(args) {
3840
- const absPath = this.sandbox.validatePath(args.path);
3839
+ async fileRead(args, agentRoot) {
3840
+ const allowed = agentRoot ? [agentRoot] : [];
3841
+ const absPath = this.sandbox.validatePath(args.path, allowed);
3841
3842
  try {
3842
3843
  const content = await (0, import_promises.readFile)(absPath, "utf-8");
3843
3844
  const lines = content.split("\n");
@@ -3854,25 +3855,29 @@ var init_file_tools = __esm({
3854
3855
  throw err;
3855
3856
  }
3856
3857
  }
3857
- async fileWrite(args) {
3858
- const absPath = this.sandbox.validatePath(args.path);
3858
+ async fileWrite(args, agentRoot) {
3859
+ const allowed = agentRoot ? [agentRoot] : [];
3860
+ const absPath = this.sandbox.validatePath(args.path, allowed);
3859
3861
  const dir = (0, import_path3.resolve)(absPath, "..");
3860
3862
  await (0, import_promises.mkdir)(dir, { recursive: true });
3861
3863
  await (0, import_promises.writeFile)(absPath, args.content, "utf-8");
3862
3864
  return `Written ${args.content.length} bytes to ${args.path}`;
3863
3865
  }
3864
- async fileDelete(args) {
3865
- const absPath = this.sandbox.validatePath(args.path);
3866
+ async fileDelete(args, agentRoot) {
3867
+ const allowed = agentRoot ? [agentRoot] : [];
3868
+ const absPath = this.sandbox.validatePath(args.path, allowed);
3866
3869
  await (0, import_promises.unlink)(absPath);
3867
3870
  return `Deleted ${args.path}`;
3868
3871
  }
3869
- async fileSearch(args) {
3872
+ async fileSearch(args, agentRoot) {
3870
3873
  const results = [];
3871
- await this.walkDir(this.sandbox.projectRoot, args.pattern, results, 0, 10);
3874
+ const root = agentRoot || this.sandbox.projectRoot;
3875
+ await this.walkDir(root, args.pattern, results, 0, 10);
3872
3876
  return results.join("\n") || "No files found";
3873
3877
  }
3874
- async fileGrep(args) {
3875
- const searchRoot = args.path ? this.sandbox.validatePath(args.path) : this.sandbox.projectRoot;
3878
+ async fileGrep(args, agentRoot) {
3879
+ const allowed = agentRoot ? [agentRoot] : [];
3880
+ const searchRoot = args.path ? this.sandbox.validatePath(args.path, allowed) : agentRoot || this.sandbox.projectRoot;
3876
3881
  let regex;
3877
3882
  try {
3878
3883
  regex = new RegExp(args.pattern);
@@ -3883,8 +3888,9 @@ var init_file_tools = __esm({
3883
3888
  await this.grepDir(searchRoot, regex, results, 0, 10);
3884
3889
  return results.join("\n") || "No matches found";
3885
3890
  }
3886
- async fileTree(args) {
3887
- const root = args.path ? this.sandbox.validatePath(args.path) : this.sandbox.projectRoot;
3891
+ async fileTree(args, agentRoot) {
3892
+ const allowed = agentRoot ? [agentRoot] : [];
3893
+ const root = args.path ? this.sandbox.validatePath(args.path, allowed) : agentRoot || this.sandbox.projectRoot;
3888
3894
  const maxDepth = args.depth || 3;
3889
3895
  const lines = [];
3890
3896
  await this.buildTree(root, "", lines, 0, maxDepth);
@@ -4218,12 +4224,19 @@ var init_skill_tools = __esm({
4218
4224
  });
4219
4225
 
4220
4226
  // packages/tools/src/sandbox.ts
4221
- var import_path5, import_fs4, Sandbox;
4227
+ function fold(p) {
4228
+ return CASE_INSENSITIVE_FS ? p.toLowerCase() : p;
4229
+ }
4230
+ function rootWithSlash(p) {
4231
+ return p.endsWith("/") ? p : p + "/";
4232
+ }
4233
+ var import_path5, import_fs4, CASE_INSENSITIVE_FS, Sandbox;
4222
4234
  var init_sandbox = __esm({
4223
4235
  "packages/tools/src/sandbox.ts"() {
4224
4236
  "use strict";
4225
4237
  import_path5 = require("path");
4226
4238
  import_fs4 = require("fs");
4239
+ CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
4227
4240
  Sandbox = class {
4228
4241
  root;
4229
4242
  constructor(projectRoot) {
@@ -4233,12 +4246,26 @@ var init_sandbox = __esm({
4233
4246
  return this.root;
4234
4247
  }
4235
4248
  /**
4236
- * Validate that a path resolves within the project root.
4237
- * Handles non-existent files (for file_write) by walking up to the
4238
- * deepest existing ancestor and resolving from there.
4239
- * Resolves symlinks to prevent symlink escape attacks.
4249
+ * Validate that a path resolves within the project root OR inside any
4250
+ * entry in `allowedRoots` (union-of-roots). Handles non-existent files
4251
+ * (for file_write) by walking up to the deepest existing ancestor and
4252
+ * resolving from there. Resolves symlinks BEFORE the membership check
4253
+ * to prevent symlink escape attacks.
4254
+ *
4255
+ * Preserves these security properties:
4256
+ * - path.resolve() on candidate AND every allowed root before compare
4257
+ * - realpathSync on deepest existing ancestor of candidate
4258
+ * - trailing-slash canonical form on root side (blocks sibling-prefix
4259
+ * bypass like `/tmp/gossip-wt-AB` vs `/tmp/gossip-wt-ABXYZ`)
4260
+ * - case-fold on darwin/win32
4261
+ *
4262
+ * @param filePath Path to validate. May be relative (resolved against
4263
+ * projectRoot) or absolute.
4264
+ * @param allowedRoots Optional absolute paths that should also be
4265
+ * accepted as roots. Defaults to `[]` so existing
4266
+ * callers are unchanged.
4240
4267
  */
4241
- validatePath(filePath) {
4268
+ validatePath(filePath, allowedRoots = []) {
4242
4269
  const resolved = (0, import_path5.resolve)(this.root, filePath);
4243
4270
  let checkPath = resolved;
4244
4271
  while (!(0, import_fs4.existsSync)(checkPath)) {
@@ -4249,10 +4276,27 @@ var init_sandbox = __esm({
4249
4276
  const real = (0, import_fs4.existsSync)(checkPath) ? (0, import_fs4.realpathSync)(checkPath) : checkPath;
4250
4277
  const remainder = resolved.slice(checkPath.length);
4251
4278
  const fullReal = real + remainder;
4252
- if (!fullReal.startsWith(this.root + "/") && fullReal !== this.root) {
4253
- throw new Error(`Path "${filePath}" resolves outside project root`);
4279
+ const candidateRoots = [this.root];
4280
+ for (const r of allowedRoots) {
4281
+ if (!r) continue;
4282
+ const absR = (0, import_path5.resolve)(r);
4283
+ const realR = (0, import_fs4.existsSync)(absR) ? (() => {
4284
+ try {
4285
+ return (0, import_fs4.realpathSync)(absR);
4286
+ } catch {
4287
+ return absR;
4288
+ }
4289
+ })() : absR;
4290
+ candidateRoots.push(realR);
4291
+ }
4292
+ const foldedFull = fold(fullReal);
4293
+ for (const r of candidateRoots) {
4294
+ const foldedRoot = fold(r);
4295
+ if (foldedFull === foldedRoot) return fullReal;
4296
+ if (foldedFull.startsWith(rootWithSlash(foldedRoot))) return fullReal;
4254
4297
  }
4255
- return fullReal;
4298
+ const msg = allowedRoots.length > 0 ? `Path "${filePath}" resolves outside project root or agent root` : `Path "${filePath}" resolves outside project root`;
4299
+ throw new Error(msg);
4256
4300
  }
4257
4301
  };
4258
4302
  }
@@ -8478,19 +8522,19 @@ function canonicalizeForBoundary(p) {
8478
8522
  }
8479
8523
  }
8480
8524
  }
8481
- if (CASE_INSENSITIVE_FS) out = out.toLowerCase();
8525
+ if (CASE_INSENSITIVE_FS2) out = out.toLowerCase();
8482
8526
  return out.endsWith("/") ? out : out + "/";
8483
8527
  }
8484
8528
  function validatePathInScope(scope, canonicalPath) {
8485
8529
  return canonicalPath.startsWith(scope);
8486
8530
  }
8487
- var import_path6, import_fs5, CASE_INSENSITIVE_FS;
8531
+ var import_path6, import_fs5, CASE_INSENSITIVE_FS2;
8488
8532
  var init_scope = __esm({
8489
8533
  "packages/tools/src/scope.ts"() {
8490
8534
  "use strict";
8491
8535
  import_path6 = require("path");
8492
8536
  import_fs5 = require("fs");
8493
- CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
8537
+ CASE_INSENSITIVE_FS2 = process.platform === "darwin" || process.platform === "win32";
8494
8538
  }
8495
8539
  });
8496
8540
 
@@ -8742,7 +8786,7 @@ var init_tool_server = __esm({
8742
8786
  throw new Error(`Read blocked: "${args.path}" is outside scope "${readScope}"`);
8743
8787
  }
8744
8788
  }
8745
- return this.fileTools.fileRead(args);
8789
+ return this.fileTools.fileRead(args, agentRoot);
8746
8790
  }
8747
8791
  case "file_write": {
8748
8792
  if (callerId) {
@@ -8752,20 +8796,20 @@ var init_tool_server = __esm({
8752
8796
  throw new Error(`Agent ${callerId} exceeded max tracked file writes (${_ToolServer.MAX_WRITTEN_FILES_PER_AGENT})`);
8753
8797
  }
8754
8798
  }
8755
- const result = await this.fileTools.fileWrite(args);
8799
+ const result = await this.fileTools.fileWrite(args, agentRoot);
8756
8800
  if (callerId) {
8757
8801
  this.agentWrittenFiles.get(callerId).add(args.path);
8758
8802
  }
8759
8803
  return result;
8760
8804
  }
8761
8805
  case "file_delete":
8762
- return this.fileTools.fileDelete(args);
8806
+ return this.fileTools.fileDelete(args, agentRoot);
8763
8807
  case "file_search":
8764
- return this.fileTools.fileSearch(args);
8808
+ return this.fileTools.fileSearch(args, agentRoot);
8765
8809
  case "file_grep":
8766
- return this.fileTools.fileGrep(args);
8810
+ return this.fileTools.fileGrep(args, agentRoot);
8767
8811
  case "file_tree":
8768
- return this.fileTools.fileTree(args);
8812
+ return this.fileTools.fileTree(args, agentRoot);
8769
8813
  case "shell_exec":
8770
8814
  return this.shellTools.shellExec({
8771
8815
  ...args,
@@ -9224,7 +9268,7 @@ var init_definitions = __esm({
9224
9268
  var src_exports2 = {};
9225
9269
  __export(src_exports2, {
9226
9270
  ALL_TOOLS: () => ALL_TOOLS,
9227
- CASE_INSENSITIVE_FS: () => CASE_INSENSITIVE_FS,
9271
+ CASE_INSENSITIVE_FS: () => CASE_INSENSITIVE_FS2,
9228
9272
  FILE_TOOLS: () => FILE_TOOLS,
9229
9273
  FileTools: () => FileTools,
9230
9274
  GIT_TOOLS: () => GIT_TOOLS,
@@ -21573,6 +21617,13 @@ function buildAuditExclusions(projectRoot, ownWorktree) {
21573
21617
  const root = canonicalize(projectRoot);
21574
21618
  for (const v of expandTmpVariants(`${root}/.gossip`)) excl.add(v);
21575
21619
  for (const v of expandTmpVariants(`${root}/.claude`)) excl.add(v);
21620
+ try {
21621
+ const home = canonicalize((0, import_os2.homedir)());
21622
+ for (const sub of ["Library", ".cache", ".npm", ".claude/projects"]) {
21623
+ for (const v of expandTmpVariants(`${home}/${sub}`)) excl.add(v);
21624
+ }
21625
+ } catch {
21626
+ }
21576
21627
  if (ownWorktree) {
21577
21628
  const wt = canonicalize(ownWorktree);
21578
21629
  for (const v of expandTmpVariants(wt)) excl.add(v);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gossipcat",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "Multi-agent orchestration for Claude Code — parallel review, consensus, adaptive dispatch",
5
5
  "mcpName": "io.github.ataberk-xyz/gossipcat",
6
6
  "repository": {