From 4179fe57b20a5dcdc17a0ec493fd02bd291af90b Mon Sep 17 00:00:00 2001 From: Rach Pradhan <54503978+justrach@users.noreply.github.com> Date: Tue, 5 May 2026 11:08:32 +0800 Subject: [PATCH 1/3] test(issue-406): failing test for root_policy bypass via /private/etc On macOS /etc is a symlink to /private/etc; realPathFile resolves user-supplied paths to their /private/* form before isIndexableRoot runs. The system_prefixes deny list covers /etc but not /private/etc, so the canonical form bypasses the policy entirely. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/tests.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tests.zig b/src/tests.zig index af40b19..e0cfb6c 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -9310,3 +9310,15 @@ 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")); +} From 8732e8464f494c25adcb7b61c1b19c088623a9f3 Mon Sep 17 00:00:00 2001 From: Rach Pradhan <54503978+justrach@users.noreply.github.com> Date: Tue, 5 May 2026 11:09:30 +0800 Subject: [PATCH 2/3] test(issue-407): failing test for /var subtree not in root_policy deny list The system_prefixes deny list covers /var/folders and /var/tmp but not /var itself or /var/log, /var/lib, /var/db. On Linux these hold logs, mail, and package state; on macOS realPathFile turns /var into /private/var which is also unblocked. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/tests.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/tests.zig b/src/tests.zig index e0cfb6c..6190eea 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -9322,3 +9322,18 @@ test "issue-406: root_policy blocks /private/etc (macOS realpath of /etc)" { 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")); +} From 0647a730cef0b0308cfbbe12ccb236f7ee4f7657 Mon Sep 17 00:00:00 2001 From: Rach Pradhan <54503978+justrach@users.noreply.github.com> Date: Tue, 5 May 2026 11:21:30 +0800 Subject: [PATCH 3/3] fix(root_policy): block /private/etc and /var subtree (#406, #407) Co-Authored-By: Claude Opus 4.7 (1M context) --- src/root_policy.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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;