Skip to content

mcp: codedb_read/edit reject absolute paths inside the project root as 'path traversal' #629

Description

@justrach

Problem

isPathSafe (src/mcp.zig:5075) rejects every absolute path via a blanket if (path[0] == '/') return false. Because handleRead/handleEdit gate on isPathSafe before resolving anything, an absolute path that points at a file inside the indexed project root is rejected as path traversal not allowed.

This is invisible in scalar metrics but shows up in real traces. In the #626 session trajectory the agent called codedb twice, got an instant (10µs) path traversal not allowed / PathNotAllowed rejection both times, then abandoned codedb and fell back to seven bash calls to finish the task:

webfetch, codedb!, codedb!, read_file!, bash, bash, bash, bash, bash, bash, bash

Agents naturally hold and pass absolute paths. A bare rejection with no remediation makes them drop the tool — codedb's correctness is masked by bash fallback, so the task still "passes" while codedb contributed nothing.

Failing Test

src/test_mcp.zig (run zig build test-mcp):

test "issue-629: isPathSafe rejects absolute paths inside the project root" {
    const MCP = @import("mcp.zig");

    var buf: [std.fs.max_path_bytes]u8 = undefined;
    const root_len = try std.Io.Dir.cwd().realPathFile(io, ".", &buf);
    const root = buf[0..root_len];

    const abs = try std.fs.path.join(testing.allocator, &.{ root, "src", "main.zig" });
    defer testing.allocator.free(abs);

    // An absolute path inside the project root is safe to read.
    try testing.expect(MCP.isPathSafe(abs));

    // Security must still hold: paths outside the project root stay rejected.
    try testing.expect(!MCP.isPathSafe("/etc/passwd"));
    try testing.expect(!MCP.isPathSafe("../../../etc/passwd"));
}

Fails on main at testing.expect(MCP.isPathSafe(abs)) (123 pass, 1 fail).

Expected

An absolute path that resolves under an indexable project root is accepted (and treated as the equivalent relative path). Absolute paths outside the root (/etc/passwd, system dirs) and .. traversal stay rejected — the existing issue-93 security assertions must still pass.

Fix

Make the read/edit path-safety check root-aware instead of blanket-rejecting a leading /:

  • If path is absolute, accept it only when it is an exact-or-child of an indexable root (reuse root_policy.isExactOrChild / isIndexableRoot), then strip the root prefix to the relative form the explorer expects.
  • Keep rejecting .. components, null bytes, backslashes, and out-of-root absolutes.
  • Consider returning a remediation hint in the error (the allowed root, or "pass a path relative to ") so agents recover instead of abandoning the tool.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:p2Medium priority

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions