Rightshold™ Sign up

Answers · MCP security

How do I block SSRF and path traversal when an MCP tool takes a URL or file path argument, or a resource template like file:///{path}?

Last updated

TL;DR: Treat every URL and path the model hands your server as attacker input, because it can be: the value comes from a model that may have read injected text. The MCP spec (2026-07-28) says servers MUST validate all tool inputs and all resource URIs, and MUST sanitize file paths to prevent directory traversal when serving file:// resources. For URLs, don't fetch whatever arrives: take a host (or a choice from a list), match it against an allowlist, and build the request yourself with a fixed scheme, port and path, as OWASP's SSRF cheat sheet advises. Where you must accept arbitrary public URLs, resolve the name, reject private, loopback, link-local (169.254.169.254) and other reserved addresses with a vetted IP library rather than your own parser, connect to the address you checked, and re-check every redirect hop. For paths, resolve symlinks and compare the real path to the allowed directory on a path-separator boundary, never with startsWith: that exact mistake let the official Filesystem MCP server reach /private/tmp/allow_dir_sensitive_credentials when only /private/tmp/allow_dir was allowed (CVE-2025-53110), and a symlink fallback bug let it reach any file (CVE-2025-53109); both were fixed in 2025.7.1.

What should I check, step by step?

1. List every tool argument and resource template variable that becomes a URL, hostname, file path or filename. Those are the inputs to lock down; template variables such as {path} in file:///{path} are filled in by the client, not by you.

  1. Prefer an id over a location: let the tool take a report id, a repo name or an index (OWASP's path traversal advice is to use indexes rather than file names) and map it to the real URL or path on the server.
  2. Fix the allowed set in server configuration, not in the call. The 2026-07-28 spec deprecates Roots and points to tool parameters, resource URIs or server configuration instead; the allowed hosts and directories belong in config the model can't change.
  3. For outbound requests, allow https only, check the host against an allowlist with an exact match, and build the URL from your own scheme, port and path; don't copy the caller's path or query through.
  4. If the host can be any public site, resolve it, reject every address that isn't public (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16, 0.0.0.0/8, fc00::/7, fe80::/10, ::1, multicast) using a vetted library, then connect to that same address so a second DNS lookup can't swap it (the DNS-rebinding TOCTOU the MCP spec warns about).
  5. Turn off automatic redirects and validate each hop with the same rules, or send all fetches through an egress proxy such as Smokescreen that blocks internal destinations by design.
  6. For files: decode the input once, reject NUL bytes and absolute paths, join it to the allowed directory, resolve the real path (and the real parent for files that don't exist yet), and accept only if the real path equals the allowed directory or sits under it followed by a separator. If any step errors, deny.
  7. Test with known-bad inputs before release: ../ and %2e%2e%2f, a sibling directory that shares the allowed prefix, a symlink pointing outside, http://169.254.169.254/, http://localhost:6379/, a redirect to an internal address, and http://example.com\@evil.com.

Why are MCP tool URL and file path arguments an SSRF and path traversal risk?

The model picks the arguments, and the model reads untrusted text: web pages, tickets, files. A sentence in a fetched page can steer a fetch_url tool to a cloud metadata address or a read_file tool to ~/.ssh. Your server is the last place that can say no, which is why the tools section of the spec puts input validation on servers as a MUST, alongside access controls and rate limits.

How should an MCP server validate a URL argument before fetching it?

When the tool only needs a few known services, OWASP's cheat sheet says not to accept full URLs at all: accept a host or a choice, compare it against an explicit allowlist, and build the request from the matching entry plus a scheme, port and path your code fixes. That removes the parser games entirely.

How should an MCP server check a file path argument stays inside the allowed directory?

CVE-2025-53110 (GHSA-hc55-p739-j48w) is the lesson in one line: the official @modelcontextprotocol/server-filesystem checked normalizedRequested.startsWith(dir), so with /private/tmp/allow_dir allowed, Cymulate's researcher listed and read /private/tmp/allow_dir_sensitive_credentials. Compare on a boundary: allowed only if the resolved path equals the directory or starts with the directory plus the path separator, or use a relative-path check that rejects results starting with .. or absolute.

How do I ask Rightshold about this (free, no key)?

Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “Record the destinations my MCP fetch and file tools are allowed (the exact API hosts and the one folder) as a declared intent, so a reviewer can later compare any URL or path a tool touched against what was allowed.”

One-click setup

{
  "tool": "declare_intent",
  "arguments": {
    "agent_id": "<one of your agents>",
    "purpose": "other",
    "description": "MCP server 'docs-fetch': fetch_url may reach only api.example.com and docs.example.com over https; read_file may read only /srv/mcp/docs (read-only). No redirects followed, no other hosts or paths.",
    "operations": [
      "record_ai_run"
    ],
    "ttl_hours": 720,
    "targets": {
      "domains": [
        "api.example.com",
        "docs.example.com"
      ],
      "resources": [
        "mcp-server:docs-fetch",
        "folder:/srv/mcp/docs:ro"
      ]
    }
  }
}

Rightshold records declared intents and checks remote MCP servers you ask about. It does not validate URLs or paths inside your MCP server, proxy its outbound requests, or scan your code for SSRF or traversal bugs. Not legal advice.

What are the sources?

Related

Not legal advice. JSON · Markdown · All answers

Get a free API key in one call

No card and no email needed. 100 free credits; the license and policy checks stay free without a key.

curl -s -X POST https://www.rightshold.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export RIGHTSHOLD_API_KEY=rs_...   # the api_key from the reply (shown once)
curl -s https://www.rightshold.com/v1/licenses/check -H "Authorization: Bearer $RIGHTSHOLD_API_KEY" \
  -H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'