Skip to content

filesystem: Windows path rejected when request casing matches neither the configured nor on-disk spelling #470

Open

Description

@robotdad

When using a local version of the filesystem server on Windows I am getting access denied for paths that are allowed directories on Windows. I was moving to this to get off of my own fork sine a prior PR was merged but it is not in release.

To Reproduce
Steps to reproduce the behavior:

  1. Setup some allowed directories on Windows
  2. Try and access file with them using a local build of filesystem server

Logs
There are no errors in the logs.

Additional context
Here is an example output of read_file showing the issue.

{
  `path`: `C:\\source\\servers\\src\\fetch\\README.md`
}
Error: Access denied - path outside allowed directories: C:\source\servers\src\fetch\README.md not in c:\source, C:\Users\mgoodner

Activity

  1. civicBaby commented on Jan 23, 2025

    @civicBaby

    Hello, may I ask how the final solution was resolved? Please let me know. Thank you

  2. bartlomiejwolk commented on Mar 26, 2025

    @bartlomiejwolk

    I'm still having the same problem (Windows).

    EDIT: The issue is that the path validation logic is incorrectly denying access to paths that should be allowed, because of
    inconsistencies in path normalization and case sensitivity (e.g. "C:\source" is not the same as "c:\source"). I made some changes to index.ts, built with npm run build and copied the resulting index.js to AppData\Roaming\npm\node_modules@modelcontextprotocol\server-filesystem\dist\index.js

  3. chienchien6 commented on Apr 18, 2025

    @chienchien6

    Metoo.I have the same problem

  4. added
    server-filesystemReference implementation for the Filesystem MCP server - src/filesystem
    on Apr 26, 2025
  5. domdomegg commented on Aug 9, 2025

    @domdomegg
    Member

    There's a PR up in #2111 that's a step towards fixing this. One problem is that Windows actually can have case-sensitive paths, even if it's not the norm. This makes things a bunch harder.

    It would be great to get some clever eyes over there, exploring how we can solve this problem in an effective and secure way :)

  6. laozhangzzz commented on Jun 25, 2026

    @laozhangzzz
  7. olaservo commented on Aug 28, 2026

    @olaservo
    Member

    Re-tested this against current main (post-#4638/#4631) on Windows 11, driving validatePath directly with the same startup resolution index.ts uses.

    The repro in the report is fixed. normalizePath upper-cases the drive letter, and startup keeps both the as-typed spelling and the fs.realpath result of each allowed directory, so c:\source configured and C:\source\... requested now match:

    allowed = [ 'C:\\...\\casetest', 'C:\\...\\CaseTest' ]   (configured as c:\...\casetest)
      OK   C:\...\CaseTest\Sub\File.txt
      OK   c:\...\casetest\sub\file.txt
      OK   C:\...\CaseTest\Sub\New.txt      (new file)
      OK   c:\...\casetest\sub\new2.txt     (new file)
    

    What still fails is narrower than the title: a request whose non-drive segments are spelled in a case that matches neither the configured spelling nor the on-disk spelling, e.g. C:\...\CASETEST\SUB\FILE.TXT. That is rejected with Access denied - path outside allowed directories because isPathWithinAllowedDirectories compares with exact startsWith before realpath runs. Clients normally echo a path they were given or got from list_directory, so this is an edge, but it is real.

    A blanket case-insensitive compare on win32 is not safe, as @domdomegg said: NTFS directories can be marked case-sensitive (fsutil file setCaseSensitiveInfo), and a case-folded prefix check would then admit a sibling. A per-segment check that asks the filesystem (the way resolveUnicodeEquivalentPath already does for NFC/NFD) would be correct on both kinds of directory. That is the shape a fix should take; PRs welcome.

    Retitling to the remaining gap and leaving open.

  8. changed the title [-]Filesystem server access denied for allowed paths on Windows[/-] [+]filesystem: Windows path rejected when request casing matches neither the configured nor on-disk spelling[/+] on Aug 28, 2026
  9. youlemei2022-web commented on Sep 21, 2026

    @youlemei2022-web

    Windows path comparisons are case-insensitive, so a configured root of c:\source should still match C:\source\servers... The error text shows both spellings side by side, which points at a plain string prefix comparison rather than a filesystem-aware one.Two things worth doing in the check:1. Normalise before comparing - resolve the requested path against the root using the platform's own path handling, and compare case-insensitively on Windows and macOS (POSIX stays case-sensitive). Comparing raw strings will keep producing this class of report.2. While you are in there, handle the Windows path variants that refer to the same file but compare as different strings: long-path prefixes, forward slashes, trailing dots or spaces, and 8.3 short names. Resolving the real path first and only then checking containment also closes the symlink/junction escape that a literal prefix check cannot.

  10. cliffhall commented on Oct 19, 2026

    @cliffhall
    Member

    Triage: Priority Medium (total 7)

    • Severity 3: On case-insensitive filesystems (Windows NTFS, default macOS APFS) a request is still refused when the allowed directory's own segments are spelled in a case matching neither the configured nor the on-disk spelling, because the allow-list prefix check compares strings exactly before the path is resolved; the drive-letter case from the original report is fixed.
    • Urgency 2: wanted eventually
    • Bonuses: +1 bug label, +1 high engagement

    Still present on v2 (checked by a local reproduction against the current code).

    Board: Servers V2 (#43), Status Incoming, awaiting maintainer review (no milestone yet). Part of the backlog triage in #4875.

  11. cliffhall commented on Oct 20, 2026

    @cliffhall
    Member

    Prior work, from the PR backlog triage (#4877). These outside pull requests addressed this issue against the pre-refactor code:

    They are being closed as part of the backlog triage in #4875, since the repository now takes issues rather than outside PRs and their diffs predate the v2 refactor. They stay readable and serve as prototypes for the fix here. Thank you to their authors.

  12. youlemei2022-web commented on Oct 22, 2026

    @youlemei2022-web

    On the prefix check, folding case on both sides is the obvious fix but it fails two cases that show up on real installs: per-directory case sensitivity on NTFS, and 8.3 short names like PROGRA~1. A fold accepts the first case when it should not and still rejects the second.

    A shape that holds:

    • Resolve both sides with fs.realpath.native when the target exists, so short names and junctions collapse to one canonical string; fall back to path.resolve only for paths that do not exist yet, which is the create-new-file case.
    • Compare via path.relative(allowedDir, target) and reject when the result is .. or starts with .. plus the separator. That removes the trailing-separator and \\?\ prefix edge cases that the string prefix check mishandles.
    • Apply case folding only on win32 and darwin, and only to the relative result — not to the path you return to the client.
    • Keep the not-yet-existing branch separate: creation targets legitimately have no realpath, so they need the lexical comparison and nothing else.

    Two regressions worth pinning: a short-name path inside an allowed directory must pass, and a .. escape from the same directory must fail on every platform. The current code passes the second and fails the first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingserver-filesystemReference implementation for the Filesystem MCP server - src/filesystemv2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions