Fix UNC path validation: cover all corruption sites on Windows#3981
Open
quocnam1 wants to merge 1 commit intomodelcontextprotocol:mainfrom
Open
Fix UNC path validation: cover all corruption sites on Windows#3981quocnam1 wants to merge 1 commit intomodelcontextprotocol:mainfrom
quocnam1 wants to merge 1 commit intomodelcontextprotocol:mainfrom
Conversation
PR modelcontextprotocol#3921 fixes isPathWithinAllowedDirectories for UNC paths, but three other sites in the filesystem server also corrupt UNC paths via path.resolve or preserve them incorrectly through path.normalize: 1. path-validation.ts::normalizePossiblyUNCPath path.normalize preserves trailing separators on UNC paths on Windows (unlike path.resolve for drive paths), so 'normalizedDir + path.sep' produces a double separator that startsWith() never matches. Strip the trailing separator to harmonize. 2. lib.ts::validatePath Calls path.resolve(expandedPath) before the allowed-directory check, which on Windows corrupts UNC paths into drive-relative paths (\\\\server\\share -> C:\\server\\share). Skip path.resolve for UNC paths and let normalizePath handle them. 3. index.ts bootstrap Same path.resolve corruption when normalizing allowed directories at server startup, before they are stored via setAllowedDirectories. Same guard applied. All non-UNC paths retain existing behavior (path.resolve + normalize). Tests added: - 4 tests in path-validation.test.ts for the trailing-separator case - 6 tests in lib.test.ts for validatePath end-to-end on UNC, gated behind process.platform === 'win32' and using the existing fs mock pattern - Bug modelcontextprotocol#3 (bootstrap) has no direct unit test since UNC access in CI is not available, but the fix applies the same principle as validatePath and is logically covered. Fixes modelcontextprotocol#3756 Supersedes modelcontextprotocol#3921
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #3756 — Windows UNC path validation fails in multiple sites of the filesystem server, not just
isPathWithinAllowedDirectories.This PR supersedes #3921, which patches only
isPathWithinAllowedDirectories. Three other sites in the filesystem server also corrupt UNC paths, causing every UNC file operation to fail with "Access denied" on Windows even after #3921 is applied. This PR fixes all four sites together and adds tests for the regressions.Root cause
On Windows,
path.normalize('\\\\server\\share')strips a leading backslash (\\server\share→\server\share), andpath.resolvethen interprets the single-backslash result as drive-relative, producingC:\server\share. Any code path that pushes a UNC throughpath.resolvecorrupts it before validation.The four corruption sites
path-validation.tsisPathWithinAllowedDirectoriespath-validation.tsnormalizePossiblyUNCPathtrailing separatorlib.tsvalidatePathpre-checkpath.resolveindex.tsBug #2 — trailing separator
path.normalizepreserves trailing separators on UNC paths on Windows, unlikepath.resolvefor drive paths. After #3921 strips the leading-backslash issue,normalizedDir + path.sepin the startsWith check produces a double separator that never matches a real file path. Any allowed UNC directory configured with a trailing backslash (the common case:\\server\share\) fails every subdirectory match.Fix: strip the trailing separator inside
normalizePossiblyUNCPath, harmonizing withpath.resolvebehavior on non-UNC paths.Bug #3 —
validatePathvalidatePathcallspath.resolve(expandedPath)on the absolute path before the allowed-directory check. On a UNC input, this corrupts the path toC:\server\share\...before the patched check ever sees it. Every UNC write, stat, edit, or rename operation fails with "Access denied" regardless of #3921.Fix: skip
path.resolvefor UNC paths and letnormalizePath(which is already UNC-aware inpath-utils.ts) handle them.Bug #4 — bootstrap
Same
path.resolvecorruption runs at server startup when normalizing every allowed directory beforesetAllowedDirectories. This means every UNC allowed directory stored in the server's state is already corrupted, independently of runtime request handling.Fix: same guard as #3 applied in the bootstrap loop.
Tests
path-validation.test.ts— 4 new tests for the trailing-separator case (bug Create package for each server #2): file inside, subdirectories, exact match, sibling prefix rejection. All gated behindpath.sep === '\\'.lib.test.ts— 6 new tests forvalidatePathend-to-end with UNC (bug Added SQLite Notes To Do Server #3), using the existingvi.mock('fs/promises')pattern andmockFs.realpathreturning the input unchanged. Covers within/exact/nested/outside-directory/different-server/trailing-separator. All gated behindprocess.platform === 'win32'.expanded.startsWith('\\\\')guard asvalidatePath, so the logic is covered by thelib.tstests and by manual validation on a real Windows + SMB setup.All 159 existing tests continue to pass. Non-UNC paths retain existing behavior on all platforms (same
path.resolve(path.normalize(...))chain).Manual validation
Tested on Windows 11 + Node v25 against an SMB share on a Linux server, with the binary built from this branch loaded via
nodeinclaude_desktop_config.json:list_directory,read_text_file,write_file,get_file_info,edit_fileon\\server\share\...all succeed.C:\Users\...) behavior unchanged.Before this PR (even with #3921 applied): every UNC write and stat fails with "Access denied", only
list_directorysucceeds by bypassingvalidatePath.Checklist
\\prefix — zero impact on Unix pathspath.sep === '\\'orprocess.platform === 'win32'(no-op on Unix CI)Happy to split this into multiple commits (one per site + tests) if reviewers prefer that format.