Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions src/roots.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,43 @@ assert.equal(
);

assert.equal(
resolveAllowedPath("~/file.txt", "/workspace", ["/workspace"]),
resolve("/workspace", "~/file.txt"),
resolveAllowedPath("~/personal/devspace", "/some/other/cwd", [join(home, "personal")]),
resolve(home, "personal", "devspace"),
);

assert.equal(
resolveAllowedPath("~/personal/devspace", home, [join(home, "personal")]),
resolve(home, "personal", "devspace"),
);

assert.equal(
resolveAllowedPath("~/personal/devspace", "/some/other/cwd", ["~/personal"]),
resolve(home, "personal", "devspace"),
);

assert.throws(
() => resolveAllowedPath("~/outside", home, [join(home, "personal")]),
/Path is outside allowed roots/,
);

assert.throws(
() => resolveAllowedPath("~/file.txt", "/workspace", ["/workspace"]),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Rejection test depends on HOME

If the test runner’s home directory is /workspace or beneath it, ~/file.txt is inside the allowed root. The helper correctly accepts it, but this assertion fails. Use an allowed root that excludes the file, such as join(home, "personal"). This is a non-blocking test reliability issue.

Suggested change
() => resolveAllowedPath("~/file.txt", "/workspace", ["/workspace"]),
() => resolveAllowedPath("~/file.txt", "/workspace", [join(home, "personal")]),
Artifacts

Command used to run and capture the HOME boundary checks

  • This executed shell command ran the actual test and focused reproduction under three HOME values, capturing each command and exit code.

Focused executable source for the allowed-root check

  • This executed TypeScript source calls the changed helper with the original and proposed roots, showing their outcomes.

Actual root test passes with HOME outside /workspace

  • Running `src/roots.test.ts` with `HOME=/home/user` exited 0, establishing the passing comparison.

Actual root test fails with HOME=/workspace

  • Running the same test with `HOME=/workspace` exited 1 with a missing-expected-exception error at line 43, confirming the finding.

Focused path check with HOME outside /workspace

  • The direct helper call rejected `~/file.txt` under the original `/workspace` root when HOME was `/home/user`.

Focused path check with HOME=/workspace

  • The direct helper call accepted `~/file.txt` under `/workspace` but rejected it under `join(home, 'personal')`, confirming the proposed root avoids this failure.

Focused path check with HOME below /workspace

  • With `HOME=/workspace/child`, the original root accepted `~/file.txt` while the proposed root rejected it.

Captured command and executed reproduction source

  • A command captured the shell runner and TypeScript reproduction source used for the checks, making the execution inspectable.

View artifacts

T-Rex Ran code and verified through T-Rex

/Path is outside allowed roots/,
);

assert.equal(
resolveAllowedPath("relative/path", "/workspace", ["/workspace"]),
resolve("/workspace", "relative/path"),
);

assert.equal(
resolveAllowedPath("./file.txt", join(home, "personal"), [join(home, "personal")]),
resolve(home, "personal", "file.txt"),
);

assert.throws(
() => resolveAllowedPath("../outside", join(home, "personal"), [join(home, "personal")]),
/Path is outside allowed roots/,
);

if (process.platform === "win32") {
Expand Down
3 changes: 2 additions & 1 deletion src/roots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export function assertAllowedPath(path: string, allowedRoots: string[]): string
}

export function resolveAllowedPath(inputPath: string, cwd: string, allowedRoots: string[]): string {
const absolutePath = resolve(cwd, inputPath);
const expanded = expandHomePath(inputPath);
const absolutePath = isAbsolute(expanded) ? resolve(expanded) : resolve(cwd, expanded);
return assertAllowedPath(absolutePath, allowedRoots);
}

Expand Down
Loading