From 2c97b987448c424497572456f8d5a59a8abd5ca4 Mon Sep 17 00:00:00 2001 From: jiangxt <971760512@qq.com> Date: Sat, 10 Oct 2026 14:11:03 +0800 Subject: [PATCH] fix(roots): expand home paths in resolveAllowedPath `resolveAllowedPath` resolved its input against the working directory without expanding a leading `~`, while `assertAllowedPath` expanded it. The two disagreed: a path such as `~/.codex/skills/caveman/SKILL.md` became `/~/.codex/skills/caveman/SKILL.md` instead of resolving against the home directory. `open_workspace` advertises skill entrypoints as `~`-prefixed paths, so every skill read failed with ENOENT. The skill read fallback in `resolveReadPath` only runs when workspace path resolution throws, and the literal `~` path resolved silently inside the workspace, so the fallback never fired. --- src/roots.test.ts | 15 +++++++++++++-- src/roots.ts | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/roots.test.ts b/src/roots.test.ts index 90bdaa243..c8fe90f74 100644 --- a/src/roots.test.ts +++ b/src/roots.test.ts @@ -20,9 +20,20 @@ assert.equal( resolve(home, "personal", "devspace"), ); +// Home expansion applies before working-directory resolution, so a `~` path +// resolves against the home directory rather than becoming a literal `~` +// directory inside the workspace. assert.equal( - resolveAllowedPath("~/file.txt", "/workspace", ["/workspace"]), - resolve("/workspace", "~/file.txt"), + resolveAllowedPath("~/personal/devspace", "/workspace", [join(home, "personal")]), + resolve(home, "personal", "devspace"), +); + +// A `~` path outside the allowed roots is denied instead of being silently +// mapped inside the workspace. Skill reads rely on this: the denial lets the +// read fall through to the skill-path resolver. +assert.throws( + () => resolveAllowedPath("~/file.txt", "/workspace", ["/workspace"]), + /Path is outside allowed roots/, ); if (process.platform === "win32") { diff --git a/src/roots.ts b/src/roots.ts index 284726f49..c681f1cc3 100644 --- a/src/roots.ts +++ b/src/roots.ts @@ -42,7 +42,7 @@ export function assertAllowedPath(path: string, allowedRoots: string[]): string } export function resolveAllowedPath(inputPath: string, cwd: string, allowedRoots: string[]): string { - const absolutePath = resolve(cwd, inputPath); + const absolutePath = resolve(cwd, expandHomePath(inputPath)); return assertAllowedPath(absolutePath, allowedRoots); }