pdf-server: robust path validation + folder/file root support#497
Merged
pdf-server: robust path validation + folder/file root support#497
Conversation
@modelcontextprotocol/ext-apps
@modelcontextprotocol/server-basic-preact
@modelcontextprotocol/server-basic-react
@modelcontextprotocol/server-basic-solid
@modelcontextprotocol/server-basic-svelte
@modelcontextprotocol/server-basic-vanillajs
@modelcontextprotocol/server-basic-vue
@modelcontextprotocol/server-budget-allocator
@modelcontextprotocol/server-cohort-heatmap
@modelcontextprotocol/server-customer-segmentation
@modelcontextprotocol/server-debug
@modelcontextprotocol/server-map
@modelcontextprotocol/server-pdf
@modelcontextprotocol/server-scenario-modeler
@modelcontextprotocol/server-shadertoy
@modelcontextprotocol/server-sheet-music
@modelcontextprotocol/server-system-monitor
@modelcontextprotocol/server-threejs
@modelcontextprotocol/server-transcript
@modelcontextprotocol/server-video-resource
@modelcontextprotocol/server-wiki-explorer
commit: |
- Replace string prefix matching (startsWith) with path.relative-based isAncestorDir() which correctly handles trailing slashes, path normalization, and prevents both .. traversal and prefix attacks. - Use path.resolve consistently when adding paths to allowedLocalFiles and allowedLocalDirs (main.ts was missing this). - Use resolved path for exact file match check in validateUrl. - Improve error message formatting for easier debugging. - Add tests for trailing slashes, grandparent dirs, and isAncestorDir.
Remove the withLogging transport wrapper, logInteraction helper, InteractionLogger type, and all log parameter threading that were added for debugging.
Some clients (e.g. Claude Code) use computer:// instead of file:// for local file references. Handle both schemes in isFileUrl and fileUrlToPath.
Clients like Claude Desktop may pass raw absolute paths (e.g. /Users/.../file.pdf) instead of file:// URLs. Handle these in validateUrl and readPdfRange.
Use fs.realpathSync to resolve symlinks/bind mounts when comparing file paths against allowed directories. This fixes the path namespace mismatch where Claude Desktop sends container paths (e.g. /sessions/...) while MCP roots use host paths (e.g. /Users/...). Both the file path and directory paths are resolved through symlinks before comparison, so either side can be a symlink. Also stores realpath of roots in allowedLocalDirs/Files so matching works in both directions. Removes verbose hex diagnostics (no longer needed).
Clients may send bare file paths with percent-encoded characters (e.g., Application%20Support). Apply decodeURIComponent to bare paths before resolving, matching the behavior already used for file:// URLs.
Include url, resolved path, realpath, allowedFiles, and per-directory path.relative results in the error returned to the client, so we can diagnose why isAncestorDir fails when paths look identical.
path.relative returns 10 levels of .. despite paths looking identical, meaning they differ at the byte level. Add hex dump of first 30 chars of both paths to catch invisible Unicode or encoding differences.
The cowork VM remaps paths via bind mounts: the tool input uses VM-internal paths (/sessions/...) while MCP roots use host paths (/Users/...). Since path.relative can't match across bind mounts, compare directory inodes instead (dev+ino from fs.statSync). Walks up the file's parent directories checking each against allowed dirs by inode, which works regardless of path namespace differences.
…ching When the MCP server runs on the host but receives unrewritten VM paths (e.g., /sessions/name/mnt/uploads/file.pdf), the tool call url argument isn't rewritten by the cowork VM proxy because it doesn't know which arguments are file paths. Fix: when path validation fails, try to match the file by finding the directory basename (e.g., 'uploads') in the VM path and looking for the relative path suffix under each allowed directory on the host. Also returns the resolved host path so readPdfRange reads the correct file on the host filesystem.
The VM path mismatch (VM-internal paths not rewritten in tool call arguments) should be fixed in the cowork VM proxy layer, not worked around in the MCP server. Removes: - resolveVmPath() function - resolvedPath field from validateUrl return type - effectiveUrl remapping in tool handlers - VM path resolution test
antonpk1
approved these changes
Feb 24, 2026
ochafik
added a commit
that referenced
this pull request
Feb 25, 2026
* chore: bump ext-apps to 1.1.2 Changes since 1.1.1: - feat: add ui/download-file method for host-mediated file downloads (#475) - pdf-server: robust path validation + folder/file root support (#497) - ci: auto-fix prettier formatting in pre-commit hook and CI (#498) * fix: sync local SDK build into node_modules for examples npm workspaces hoists example dependencies to the root node_modules, but installs the published registry copy of @modelcontextprotocol/ext-apps instead of linking to the local source. This causes type-check failures when examples use features not yet published to npm. Add scripts/link-self.mjs which copies the freshly-built dist/ and package.json into the hoisted node_modules copy after each SDK build, ensuring examples always type-check against the latest local types. See: npm/feedback#774 * Revert "fix: sync local SDK build into node_modules for examples" This reverts commit e3387e8.
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
Robust path validation and file access control for pdf-server.
Changes
computer://URL scheme support: Some clients usecomputer://instead offile://for local file references. Both schemes are now handled inisFileUrl,fileUrlToPath, and root processing.Robust ancestor directory matching: Replaced
String.startsWith(dir + path.sep)withpath.relative-basedisAncestorDir(). The old approach failed on trailing slashes and path normalization edge cases. The new check also triesrealpathSyncfor both file and directory to handle symlinks/bind-mounts.Folders in CLI + files in roots: CLI args can now be directories (added to
allowedLocalDirs). MCP roots that are files get added toallowedLocalFiles.Bare path support: Accept raw filesystem paths (e.g.
/Users/.../file.pdf) in addition tofile://URLs. Bare paths are percent-decoded in case the client sends%20for spaces.Removed temporary VM path workaround: The
resolveVmPathfunction was a temporary workaround for clients that send untranslated internal paths. This has been removed — clients are expected to send valid host paths or file URIs.Net change
5 files changed, 265 insertions, 30 deletions (mostly tests).
Test Plan
27 pdf-server tests pass, including new tests for
computer://, trailing slashes, grandparent dirs, bare paths, percent-encoding, andisAncestorDiredge cases.