CommandCodeAI / CommandCodeAI/command-code
macOS: a path that differs only in letter case from the workspace root is flagged as outside the workspace
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Không có dữ liệu ngôn ngữ
- Star
- 4k
- Fork
- 350
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Mô tả
Summary
On macOS (case-insensitive filesystem), a file tool call whose absolute path differs only in
letter case from the workspace root is treated as outside the workspace and raises the
external-directory prompt, even though the path resolves to a file inside the project. Every
occurrence costs a human approval, and approving one grants the case-variant directory, so the
session root set slowly accumulates duplicates of the real root (visible in the prompt's
allowed: list).
Expected Behavior
A path that names an existing file inside the workspace — regardless of letter case, on a
case-insensitive volume — should be recognized as inside the workspace: no external-directory
prompt, no directory grant.
Actual Behavior
Workspace root /Users/dev/Projects/my-app (capital P, macOS). A grep call for
/Users/dev/projects/my-app/apps/web/README.md (lowercase projects) triggers:
Allow Command Code to use GREP on this file?
This action accesses a path outside the current project.
/Users/dev/projects/my-app/apps/web/README.md is outside workspace
(allowed: /Users/dev/Projects/my-app, /Users/dev/.commandcode, /tmp, /private/tmp, /var/folders, …)
Both spellings resolve to the same inode.
Steps to reproduce the issue
- On macOS, start a session in a workspace whose absolute path contains at least one uppercase
letter, e.g./Users/dev/Projects/my-app. - Trigger a file tool call on the same path spelled in lowercase (this happens organically
because the model occasionally re-types absolute paths instead of copying them):grepwithpath: "/Users/dev/projects/my-app/apps/web/README.md", orread_file/edit_filewith the same lowercased path.
- The external-directory gate fires (
risk: { kind: "outside-workspace" }) and the call waits
for human approval. - Approving grants the lowercased directory, so the allowed-roots list grows with case-variant
entries that duplicate the real root.
Root cause
src/permissions/workspace.ts (bundled in @commandcode/harness):
function getCanonicalPath(dirPath: string) {
try {
return process.platform === 'win32' ? fs.realpathSync.native(dirPath) : fs.realpathSync(dirPath);
} catch {
return path.normalize(dirPath);
}
}
canonicalizeForLookup() feeds this into isWithinRoot(), which decides containment with
path.relative(canonicalRoot, canonicalPath) — a case-sensitive string comparison.
On darwin, fs.realpathSync (the JS implementation — .native is only used for win32)
preserves the case of the input for every component that is not a symlink, so a lowercased path
stays lowercased. The native variant goes through libuv's uv_fs_realpath → realpath(3) and
returns the on-disk spelling.
Reproduction without Command Code (Node on macOS):
$ node -e 'const fs=require("fs"),path=require("path");const p="/users/dev/projects/my-app/apps/web/README.md";
console.log("js ", fs.realpathSync(p));
console.log("native", fs.realpathSync.native(p));
console.log("rel ", path.relative("/Users/dev/Projects/my-app", p));'
js /users/dev/projects/my-app/apps/web/README.md
native /Users/dev/Projects/my-app/apps/web/README.md
rel ../../../../users/dev/projects/my-app/apps/web/README.md # → isWithinRoot() === false
Notes: no user-side config fully fixes this
deny/askrule matching already folds case on macOS/Windows, but the workspace boundary does not.- The external-directory gate runs before the allow rules, so an
allowrule cannot silence it. permissions.additionalDirectoriesis the only workaround, and it is itself case-sensitive —
one entry per observed case variant.- Mods cannot help either:
cmd.hooks.beforeToolCallruns afterpermissions.check.
Suggested fix
Use the native realpath wherever it exists, or fold case only when the volume is
case-insensitive:
return fs.realpathSync.native?.(dirPath) ?? fs.realpathSync(dirPath);
The requirement is that two spellings of the same existing path land on the same side of the
workspace boundary.
Command Code Version
1.53.1
Operating System
macOS
Additional context
- macOS 27.0 (APFS, case-insensitive), Node v24.19.0, Command Code 1.53.1.
- Code locations:
getCanonicalPath/canonicalizeForLookup/isWithinRootin
src/permissions/workspace.ts; the gate is theallowExternalDirectorybranch of the
permission engine (risk: { kind: 'outside-workspace' }, message built by
formatOutsideWorkspaceMessage). - A fix would also keep the prompt's
allowed:list clean: today it mixes both spellings of the
same directory once a case-variant path has been approved.
Fix prompt (optional)
In the harness permission engine, make workspace containment case-insensitive for existing paths
on case-insensitive volumes: getCanonicalPath should call fs.realpathSync.native whenever it
is available (not only on win32), so /users/dev/projects/my-app/... canonicalizes to
/Users/dev/Projects/my-app/.... Add a unit test asserting that a case-variant spelling of an
existing workspace path returns isPathInWorkspace() === true, while a genuinely external path
still returns false.
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu trong src/permissions/workspace.ts bằng cách lần theo getCanonicalPath, canonicalizeForLookup và isWithinRoot; sau đó kiểm tra nhánh allowExternalDirectory và formatOutsideWorkspaceMessage trong permission engine. Tái hiện đường dẫn có biến thể chữ hoa chữ thường trên macOS và bổ sung coverage unit test được đề xuất: một đường dẫn workspace hiện có với biến thể chữ hoa chữ thường phải nằm bên trong, trong khi một đường dẫn thực sự bên ngoài vẫn phải nằm bên ngoài và không tạo ra allowed root trùng lặp.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- node.js, typescript
- Lĩnh vực
- cli, security
- Loại issue
- Lỗi
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 78/100