google-gemini / google-gemini/gemini-cli
bug(windows): isWithinRoot() path containment check is case-sensitive, so valid in-root paths (e.g. different drive-letter casing) are rejected
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
### What happened?
On Windows, `isWithinRoot()` in `packages/core/src/utils/fileUtils.ts` compares paths **case-sensitively**, even though the Windows file system (NTFS/Win32) treats paths case-insensitively. A path that refers to a file *inside* the project root is rejected as outside-root whenever any segment's casing differs from how the root was resolved — most commonly the drive letter (`C:\...` vs `c:\...`), but also user-name or folder casing.
Verified empirically on Windows against the current implementation:
```text
root: C:\Users\\Desktop\Vs code Projects\gemini-cli
check: c:\users\\Desktop\Vs code Projects\gemini-cli\package.json (same directory)
isWithinRoot → false // startsWith comparison fails on casing
```
The relevant logic (`packages/core/src/utils/fileUtils.ts`, `isWithinRoot`):
```ts
const rootWithSeparator = ... normalizedRootDirectory + path.sep;
if (
normalizedPathToCheck === normalizedRootDirectory ||
normalizedPathToCheck.startsWith(rootWithSeparator)
) { return true; }
return false;
```
`path.resolve()`/`path.relative()` do not normalize casing on win32, so nothing upstream compensates for this.
Concrete impact sites:
1. **ACP / IDE file system** — `packages/cli/src/acp/acpFileSystemService.ts:31` uses `!isWithinRoot(filePath, this.root)` in `shouldUseFallback()`. When an editor sends the project root with different casing than `process.cwd()` produced (VS Code file URIs frequently lowercase the drive letter, e.g. `file:///c%3A/...`), every in-project file is misclassified and silently routed to the native-FS fallback instead of the IDE-connected file system — breaking IDE-side read/write sync (e.g. edits not appearing in the editor buffer).
2. **Ignore-path normalization** — `getNormalizedRelativePath()` in `packages/core/src/utils/ignorePathUtils.ts` calls `isWithinRoot()` and returns `null` on mismatch, so callers treat the file as invalid/outside-root. Files are then silently excluded from ignore-filtered listings/context (e.g., `@`-mentions or tool outputs referencing differently-cased absolute paths).
3. Same check exists in `packages/cli/src/acp/acpSession.ts`.
This is fail-closed (not a security hole), but it produces confusing "outside project root" behavior and silent exclusions on Windows for paths that are perfectly valid.
### What did you expect to happen?
Path-containment checks should be case-insensitive on case-insensitive platforms. Suggested fix:
```ts
// packages/core/src/utils/fileUtils.ts
const compare = process.platform === 'win32'
? (s: string) => s.toLowerCase()
: (s: string) => s;
// apply compare() to both sides of === / startsWith
```
(Optionally guard with a `win32`-only branch so darwin/linux semantics stay untouched; macOS `/var` vs `/private/var` handling via `canonicalizeMacosPath` is unaffected.)
Add unit tests in `fileUtils.test.ts` covering drive-letter and mid-path casing differences on win32.
### Client information
Source-level finding verified against upstream `main` at commit `5411f113c`. Reproduced the comparison logic directly with Node.js on Windows 11 (win32). Affects all gemini-cli versions currently on main; platform-specific to Windows.
### Login information
Not applicable.
### Anything else we need to know?
Sources:
- `packages/core/src/utils/fileUtils.ts` — `isWithinRoot()` (case-sensitive `startsWith`)
- `packages/cli/src/acp/acpFileSystemService.ts:29-34` — `shouldUseFallback()` misclassification
- `packages/cli/src/acp/acpSession.ts` — same helper used for session path validation
- `packages/core/src/utils/ignorePathUtils.ts:20-24` — `null` return on casing mismatch → silent exclusion
- Related context: closed meta-issue #18251 ("Improvements for Windows Developers") discussed general Windows path friction but did not cover this specific containment-check bug.
- Duplicate check: searched issues for "isWithinRoot" and "path case sensitive windows", and open PRs for "isWithinRoot"/"case-insensitive path" — no existing report or fix.
Contributor guide
Research direction
Start in packages/core/src/utils/fileUtils.ts at isWithinRoot(), then run the existing fileUtils.test.ts tests and inspect the callers in acpFileSystemService.ts, acpSession.ts, and ignorePathUtils.ts. Done means win32 containment accepts drive-letter and mid-path casing differences while non-Windows behavior remains unchanged, with unit tests covering those cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli, operating-systems, testing
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100