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

Open
#29,000 2 comments 0 reactions 0 assignees View on GitHub
area/core effort/medium status/bot-triaged
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.