DEV Community

Cover image for MCP servers v2.1.0 fixes 8 vulnerabilities: filesystem writes could escape allowed folders
Piekwerk
Piekwerk

Posted on

MCP servers v2.1.0 fixes 8 vulnerabilities: filesystem writes could escape allowed folders

The reference MCP servers repository (modelcontextprotocol/servers) shipped a security release overnight. v2.1.0, published October 25 at 00:03 UTC, patches eight vulnerabilities across three servers: the filesystem server, the everything server, and the Python fetch server. The fixed npm packages landed at 00:05 UTC, and PyPI has mcp-server-fetch 2026.10.24.

I read the release notes and the advisory summaries before writing this, plus the full text of the filesystem advisory. The details are better than the headline. One fix closes a race that let a write land outside the directories you allowed. Another stops any web page from calling your server's tools. And the release notes contain an upgrade trap that will quietly keep a lot of installs on vulnerable versions.

What shipped, in one table

Package Affected Fixed in
@modelcontextprotocol/server-filesystem 1.0.0 through 2.0.0, and date versions through 2026.8.31 2.0.1 (npm)
@modelcontextprotocol/server-everything 1.0.0 through 2.0.0, and date versions 2025.9.24 through 2026.8.31 2.0.1 (npm)
mcp-server-fetch through 2026.10.19 2026.10.24 (PyPI)

The other reference servers (memory, sequential-thinking, git, time) ship unchanged in this release. The release notes carry the full list of advisories, each with its own GHSA entry.

The filesystem race: the check ran on a different path than the write

GHSA-53mr-w429-c597 describes a TOCTOU bug in validatePath(), the function that enforces allowedDirectories. When you write a new file, the target does not exist yet, so the validator takes an ENOENT fallback: it resolves the parent directory with fs.realpath(), checks the parent against the allowlist, and then returns the original caller-supplied path unchanged instead of the canonicalised path.

That gap is the bug. Between the check and the syscall, any process running as the same user can replace the parent directory with a symlink pointing outside the allowed tree. The kernel resolves the symlink when the write, mkdir, or rename finally executes, and the operation lands at the attacker-chosen destination. The advisory names four affected operations: write_file, edit_file, create_directory, and the destination of move_file. edit_file is the worst case because its read-modify-write cycle stretches the race window wide open.

The CVSS score is 2.2, low, because the attack needs a local process racing you as the same user. I'd still call it worth patching immediately, for a specific reason: the whole point of allowedDirectories is to be the boundary between your agent and the rest of the disk. A boundary that holds except when another process is fast is not a boundary, it's a latency-dependent coincidence. This is also the third round of fixes in the same area, after CVE-2025-53109 and CVE-2025-53110 (both fixed in 2025.7.1) addressed symlink target validation and prefix-match containment. Neither of those patches re-resolved the parent after the ENOENT branch, and neither covered a symlink that appears after validation.

On Linux and macOS, 2.0.1 fixes this by returning the canonical join of the resolved parent and the basename, so the syscall operates on the path that was actually validated. On Windows the fix is only partial; the advisory documents what remains exposed and how to configure around it.

Three quieter filesystem fixes

The other three filesystem advisories need no race, just the right path shape:

  • GHSA-7rgq-gccq-qphx (CVSS 4.2): the access check stripped a trailing quote or whitespace that the file operation kept. A sibling directory named after an allowed one plus that suffix was reachable.
  • GHSA-4gxg-m5jx-9j57 (CVSS 5.3): a symlink spelled in different letter case passed the symlink check on case-insensitive volumes, like macOS's default. This was a regression introduced in 2026.8.31, and it allowed creating files outside the allowed directories.
  • GHSA-hhwq-wxfx-cj7g (CVSS 3.1): list_directory_with_sizes followed symlinks when reading entry metadata, leaking the size and modification time of files outside the allowed tree. No write needed, just a listing.

That middle one matters if you pinned "the latest" any time between August 31 and today: the case regression shipped in a version many installs grabbed as an upgrade.

The highest score sits in the everything server

GHSA-mvf5-2qqv-rg3c scores 6.5, the highest of the eight. The everything server's HTTP and SSE transports never checked the Origin header and always sent Access-Control-Allow-Origin: *. Any web page you had open could call the server's tools, including get-env, and read the results back.

Two honest limits on the scare factor: it only applies if you ran the server over HTTP or SSE, not over stdio, and the everything server is a demo and test server. But demo servers get copied into real configs, and 2026.8.31 through 2.0.0 all carried it. The fix makes the transports refuse cross-origin requests and stops the wildcard CORS header.

Three boundary bugs in the fetch server

The Python mcp-server-fetch server got three fixes, all about the boundary between public and internal addresses:

  • GHSA-v38p-wf49-hx2j: for internationalized (IDNA) host names, the server checked one form of the name and connected to another, which could reach an internal address the guard meant to refuse.
  • GHSA-66qx-2266-pmj6 (CVSS 3.1): an address in the NAT64 prefix 64:ff9b::/96 wrapping a private, loopback, or link-local IPv4 address was treated as public. Now it is judged by the IPv4 address it carries.
  • GHSA-3jqg-r5pp-rmp2 (CVSS 3.1): robots.txt was checked only for the URL first requested, not again after redirects. It is now rechecked after every hop.

The upgrade trap: your package manager thinks 2.0.1 is a downgrade

This is the part I expect to bite people. The TypeScript servers moved from date versions (2025.x, 2026.x) to semantic versions at 1.0.0. Semver ranks 2.0.1 below every 2025.x and 2026.x version, so npm and friends will not offer 2.0.1 as an upgrade. If your lockfile or global install says 2026.8.31, you are vulnerable to all four filesystem issues and your tooling reports you as up to date.

Install the fixed version explicitly:

npm install @modelcontextprotocol/server-filesystem@2.0.1
npm install @modelcontextprotocol/server-everything@2.0.1
Enter fullscreen mode Exit fullscreen mode

If you launch servers from a client config, pin the version in the args so npx cannot drift back to a date version:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem@2.0.1", "/home/you/project"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

For the fetch server, same idea on the Python side: uvx mcp-server-fetch@2026.10.24, or pip install --upgrade mcp-server-fetch. And restart the client afterwards; MCP servers start once per client session, so an upgraded package on disk keeps serving from the old process until you do.

To see what you actually run today:

npm ls @modelcontextprotocol/server-filesystem 2>/dev/null
grep -r "server-filesystem\|server-everything\|mcp-server-fetch" ~/.claude.json ~/.cursor/mcp.json 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

What I'm changing in my agent setups

Three things, none of them dramatic:

The allowlist is a security boundary, so the server enforcing it is a dependency, and I'll treat its version like one: pinned in the config, checked against advisories, upgraded deliberately. That is the same discipline I already apply to the instruction files themselves, for the reasons in versioning your agent configs.

Check/use mismatch is the class worth remembering. A validator that checks one form of a path and hands a different form to the syscall will keep producing this bug family, in any local tool, not just this server. When I write hooks or guards around agent file access, I'll resolve once and use the resolved value everywhere. That is closer to enforcement territory than documentation, which is exactly the split config files versus hooks talks about.

Scope still does most of the work. The race needed a same-user process, but the other three filesystem bugs only needed a crafted path against a wide allowlist. One project directory per server beats $HOME, and where a server sits in your stack decides what it can touch, which is the same layering question as AGENTS.md versus CLAUDE.md versus .cursorrules for instructions.

Credit where due: community reporters (0xCoDSnet, cybercraftsolutionsllc, dizconnectz) found these by trying to break the server rather than reading its code, and the fixes are specific where they could have been vague. Read the advisories yourself; every ID above links straight to its write-up.

Top comments (0)