@MODELCONTEXTPROTOCOL/SERVER-FILESYSTEM · GHSA-7rgq-gccq-qphx · AI agents & MCP
FIX → 2.0.1
The MCP filesystem server's path validation checks a normalized path but operates on the raw path, which strips a trailing quote or whitespace character. A request for a sibling directory such as /allowed/proj' passes the allowlist check, so an attacker who influences tool arguments can read, write, move or stat files outside the configured allowed directories.
first public Oct 25, 2026 · 1 source
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Teams running the MCP filesystem server so an AI agent can read and write files in a sandboxed directory are exposed. The flaw needs a sibling path with a trailing quote or whitespace to exist, which the write tools can create, so exposure is realistic when the agent reads untrusted input. Teams not running this server can skip it.
Not affected if fixed-version-installed, not-using-filesystem-mcp-server
Who cares ai-agents, mcp, node, npm
npm ls @modelcontextprotocol/server-filesystem>= 1.0.0, < 2.0.1 and >= 2025.8.21, <= 2026.8.31validatePath in src/filesystem/lib.ts performs the allowlist containment check on normalizePath(absolute) but runs fs.realpath and returns the result from the raw absolute string. normalizePath strips a trailing single quote, double quote, or whitespace character. Therefore a request for /allowed/proj' passes the containment check (it normalizes to /allowed/proj) while the file operation runs on /allowed/proj' - a sibling of the allowed directory. All 14 filesystem tools route through validatePath and are affected.
The affected function is validatePath in src/filesystem/lib.ts.
The bug is a check/use mismatch between two strings derived from the same input:
normalizePath strips a leading or trailing quote/whitespace character: p = p.trim().replace(/^["']|["']$/g, "");
The allowlist check runs on normalizePath(absolute): const normalizedRequested = normalizePath(absolute); const isAllowed = isPathWithinAllowedDirectories(normalizedRequested, allowedDirectories);
But the file operation and the returned value use the raw absolute string: const realPath = await fs.realpath(absolute); ... return realPath;
Because normalizePath strips a trailing ', ", or whitespace character, a request for /allowed/proj' passes the containment check (normalized to /allowed/proj) while fs.realpath and the returned value operate on /allowed/proj' - a real sibling of the allowed directory.
The post-realpath check runs on normalizePath(realPath), which strips the same character again, so it also passes.
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
Upgrade to 2.0.1
@modelcontextprotocol/server-filesystem 2.0.1 (fixed version per the advisory's product data)Every tool that routes through validatePath in src/filesystem:
read_file, read_text_file, read_multiple_files, read_media_file, write_file, edit_file, create_directory, list_directory, list_directory_with_sizes, directory_tree, move_file, search_files, get_file_info, list_allowed_directories (no path input, not affected in practice)
Read, write, move, and stat operations can be performed on paths outside the configured allowed directories, provided a sibling path with a trailing quote/whitespace exists. An attacker who can create such a sibling via the write tools can chain a write plus a read to escape the sandbox.
The following TypeScript test imports the real validatePath from the server's compiled output and demonstrates the escape. Run it from within src/filesystem after npm install:
import { validatePath } from "./lib.js";
import fs from "fs/promises";
import path from "path";
import os from "os";
(async () => {
const sandbox = await fs.mkdtemp(path.join(os.tmpdir(), "mcp_real_"));
const allowedProj = path.join(sandbox, "allowed/proj");
const siblingEvil = path.join(sandbox, "allowed/proj'");
await fs.mkdir(allowedProj, { recursive: true });
await fs.mkdir(siblingEvil, { recursive: true });
await fs.writeFile(path.join(siblingEvil, "SECRET.txt"), "OUTSIDE");
const validated = await validatePath(sandbox + "/allowed/proj'", [allowedProj]);
// validated === sandbox + "/allowed/proj'" - OUTSIDE the allowlist
const content = await fs.readFile(path.join(validated, "SECRET.txt"), "utf-8");
// content === "OUTSIDE" - the escape succeeded
await fs.rm(sandbox, { recursive: true, force: true });
})();
Run with: npx tsx verify_escape.ts
validatePath returned: /tmp/mcp_real_y5nErv/allowed/proj' 🚨 SECRET READ: This file is OUTSIDE the allowlist
The returned path is outside the allowlist, and reading from it succeeded.
An attacker who can influence the arguments of a filesystem tool (for example via prompt injection in a document, webpage, or codebase the LLM is reading) can cause read, write, move, and stat operations to occur on files outside the configured allowed directories.
This bypasses the boundary the server claims to enforce ("Only works within allowed directories").
@modelcontextprotocol/server-filesystem moved from date versions (2025.x, 2026.x) to semantic versions in 1.0.0. Package managers rank 2.0.1 below every 2025.x and 2026.x version, so they will not offer it as an upgrade and Dependabot will not open one. If you are on a date version, install the fix explicitly: npm install @modelcontextprotocol/server-filesystem@2.0.1 (or @latest).