Skip to content

fix(filesystem): resolve Unicode-equivalent paths - #4638

Open
KXHXK wants to merge 1 commit into
modelcontextprotocol:mainfrom
KXHXK:fix/unicode-equivalent-paths
Open

fix(filesystem): resolve Unicode-equivalent paths#4638
KXHXK wants to merge 1 commit into
modelcontextprotocol:mainfrom
KXHXK:fix/unicode-equivalent-paths

Conversation

@KXHXK

@KXHXK KXHXK commented Aug 12, 2026

Copy link
Copy Markdown

Description

Resolve filesystem paths whose requested spelling and on-disk entry differ only by Unicode normalization (for example NFC ? versus NFD e + combining acute).

The normal lookup remains the fast path. After fs.realpath() returns ENOENT, validation now walks from the most specific allowed directory and:

  • prefers exact component matches;
  • falls back to canonically equivalent NFC matches;
  • rejects ambiguous equivalent entries;
  • resolves and revalidates every matched component against the existing allowed-directory boundary;
  • preserves a missing final basename so create and move destinations continue to work.

Server Details

  • Server: filesystem
  • Changes to: shared path validation used by filesystem tools

Motivation and Context

Fixes #4633.

This differs from #3238: normalizing strings used by the security comparison does not resolve an OS lookup when the actual directory entry uses another normalization form. This change locates the real entry and then retains the existing realpath-based security checks.

How Has This Been Tested?

  • npx vitest run src/filesystem/__tests__/unicode-paths.test.ts src/filesystem/__tests__/lib.test.ts --config src/filesystem/vitest.config.ts (49 passed)
  • npm run build --workspace @modelcontextprotocol/server-filesystem
  • Full filesystem suite: 154 passed, 1 pre-existing Windows-only failure in path-validation.test.ts > handles root directory as allowed (D:\other with allowed ['/']).

Added real-filesystem regression coverage for:

  • composed request resolving a decomposed directory and filename;
  • a new destination basename under a Unicode-equivalent parent;
  • rejection of ambiguous canonically equivalent entries.

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Protocol Documentation
  • My changes follows MCP security best practices
  • I have updated the server's README accordingly
  • I have tested this with an LLM client
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have documented all environment variables and configuration options

Additional context

The path containment implementation itself is unchanged. The fallback only runs after ENOENT, starts from an already allowed root, and re-checks each resolved component to prevent a normalization fallback from bypassing symlink boundaries.

Signed-off-by: KXH <shepherdlaurie238@gmail.com>
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.

move_file fails with non-ASCII characters in file path (e.g. French accents, œ, typographic apostrophe)

1 participant