diff --git a/src/root_policy.zig b/src/root_policy.zig index 6b22493..2323afb 100644 --- a/src/root_policy.zig +++ b/src/root_policy.zig @@ -11,7 +11,6 @@ pub fn isIndexableRoot(path: []const u8) bool { if (std.mem.eql(u8, path, "/")) return false; if (isExactOrChild(path, "/private/tmp")) return false; if (isExactOrChild(path, "/tmp")) return false; - if (isExactOrChild(path, "/var/tmp")) return false; const system_prefixes = [_][]const u8{ "/Applications", @@ -22,13 +21,14 @@ pub fn isIndexableRoot(path: []const u8) bool { "/bin", "/sbin", "/etc", + "/private/etc", "/dev", "/proc", "/sys", "/snap", "/nix", - "/var/folders", - "/private/var/folders", + "/var", + "/private/var", }; for (system_prefixes) |pfx| { if (isExactOrChild(path, pfx)) return false; diff --git a/src/tests.zig b/src/tests.zig index af40b19..6190eea 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -9310,3 +9310,30 @@ test "issue-411: tryLock grants new locks to a crashed agent" { const got = try agents.tryLock(id, "post-crash.zig", 60_000); try testing.expect(got == false); } + +test "issue-406: root_policy blocks /private/etc (macOS realpath of /etc)" { + const root_policy = @import("root_policy.zig"); + // /etc is in the system_prefixes deny list, but on macOS /etc is a symlink + // to /private/etc. Callers feed isIndexableRoot a path resolved by + // realPathFile (see handleIndex in src/mcp.zig), which turns "/etc" into + // "/private/etc" — and then this textual prefix check accepts it. The + // canonical form must be blocked too, otherwise the deny list is bypassed + // by the very normalization step the callers depend on. + try testing.expect(!root_policy.isIndexableRoot("/private/etc")); + try testing.expect(!root_policy.isIndexableRoot("/private/etc/ssh")); +} + +test "issue-407: root_policy blocks /var and its non-folders subtree" { + const root_policy = @import("root_policy.zig"); + // The system_prefixes list explicitly blocks /var/folders and /var/tmp, + // but not /var itself or /var/log, /var/lib, /var/db, /var/spool, etc. + // On Linux those hold logs, mail, and package state; on macOS realPathFile + // turns /var into /private/var (also unblocked). Accidentally pointing + // the indexer at /var/log on a server pulls in GBs of secrets and is + // never a valid "project root". + try testing.expect(!root_policy.isIndexableRoot("/var")); + try testing.expect(!root_policy.isIndexableRoot("/var/log")); + try testing.expect(!root_policy.isIndexableRoot("/var/lib")); + try testing.expect(!root_policy.isIndexableRoot("/private/var")); + try testing.expect(!root_policy.isIndexableRoot("/private/var/log")); +}