Skip to content

fix(windows): handle file paths correctly on Windows - #172

Merged
juliandescottes merged 2 commits into
mainfrom
fix/windows-path-handling
Aug 26, 2026
Merged

fix(windows): handle file paths correctly on Windows#172
juliandescottes merged 2 commits into
mainfrom
fix/windows-path-handling

Conversation

@freema

@freema freema commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Split out of #169, which @juliandescottes asked me to break into smaller pieces. This part is independent of everything else in that PR.

Two fixes around file paths:

  • The saveTo boundary check compared the resolved path against its allowed root with a case-sensitive startsWith. Windows paths are case-insensitive, so c:\Users\me\.firefox-devtools-mcp\out.json was rejected as "outside the allowed location" while the same path with a capital drive letter was accepted. Agents produce either spelling. Ignoring case on Windows only removes false rejections, it cannot let through a path that previously escaped.

  • Both log paths opened their file without ensuring the directory existed, while the auto-generated path right beside one of them already called mkdirSync. An --output-file whose directory was missing threw ENOENT from deep inside connect(), and a --log-file in the same state silently disabled logging through a stream error. Not platform-specific, just easy to hit on Windows where /tmp/foo.log does not exist at all.

Verified on Windows 11, Node 22.22.0, Firefox 154.0. Every case was reproduced against main first:

case main this branch
saveTo with a lowercase drive letter (c:\Users\...) rejected accepted
saveTo with an uppercased segment (\USERS\) rejected accepted
--log-file into a missing directory logging silently disabled directory created, lines written
--output-file into a missing directory ENOENT out of connect() Firefox starts, 374 KB captured

The escapes still fail closed: a path outside the root, the ~/.firefox-devtools-mcp-evil prefix look-alike, a .. traversal, and the same traversal spelled with a lowercase drive letter are all still rejected. Unit suite, typecheck, lint and format:check are green on Windows.

@freema
freema requested a review from juliandescottes August 25, 2026 18:10
@freema
freema marked this pull request as ready for review August 25, 2026 20:26

@juliandescottes juliandescottes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, LGTM, one misplaced comment to fix.

Comment thread src/utils/save-output.ts Outdated
Comment on lines 32 to 37
@@ -35,13 +35,27 @@ async function isDirectory(path: string): Promise<boolean> {
* current working directory; absolute paths must stay within
* ~/.firefox-devtools-mcp.
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This comment should stay close to assertAllowedPath

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Moved isWithinRoot above the docblock and squashed it into the first commit. Thanks.

freema added 2 commits August 26, 2026 16:08
The saveTo boundary check compared the resolved path against its allowed
root with a case-sensitive startsWith. Windows paths are case-insensitive,
so `c:\Users\me\.firefox-devtools-mcp\out.json` was rejected as "outside the
allowed location" while the identical path with a capital drive letter was
accepted. Agents produce either spelling.

Ignoring case on Windows only removes false rejections: the comparison now
matches what the filesystem considers the same directory, so it cannot let
through a path that previously escaped.
Both log paths opened their file without ensuring the directory existed,
while the auto-generated path right beside one of them already called
mkdirSync. A --output-file whose directory was missing therefore threw
ENOENT from deep inside connect(), and a --log-file in the same state
silently disabled logging through a stream error.

Windows makes this easy to hit, since paths like /tmp/foo.log do not exist
there at all, but the gap is not platform-specific. saveOutput already
creates parents for the paths it is given; this brings both log paths in
line.
@freema
freema force-pushed the fix/windows-path-handling branch from 9c56de2 to 55fbccb Compare August 26, 2026 14:10

@juliandescottes juliandescottes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

@juliandescottes
juliandescottes merged commit a6cfa2c into main Aug 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants