Safe-Fetch MCP Server
An MCP fetch server built around one unified security pipeline, closing the SSRF gaps the reference implementation ships without.
Pipeline
Highlights
- ▸Every request, including every redirect hop, runs through the same guard code: there is no secondary fetch path to bypass.
- ▸Blocks cloud metadata addresses, RFC-1918 ranges, loopback, and IPv4-mapped IPv6 loopback by default.
- ▸resolveAndPin resolves a hostname once and pins the connection to that IP, closing the DNS-rebinding window most fetch servers leave open.
- ▸Validated against the OWASP MCP Top 10 via independent scanning; 62 unit tests cover the threat matrix.
The gap
The most-used reference MCP fetch server ships with no SSRF protection, by its own README’s admission. That’s not a minor omission. Any agent with fetch access is one crafted URL away from reaching your cloud metadata endpoint, an internal admin panel, or a service that never expected to be reachable from outside its network.
Safe-Fetch exists because getting SSRF defense right is genuinely hard, and doing it right, and proving it, is the whole point of the project.
Architecture
Every request travels through one pipeline, with no alternate code path that could skip a check:
- Zod validation on the input shape before anything else runs.
- URL policy enforcement: http/https only, no embedded credentials.
- resolveAndPin: the hostname resolves once, and the connection is pinned to that resolved IP for the life of the request.
- Redirect re-validation: every redirect the server follows runs back through the same guards, not a lighter secondary check.
That last point is the one most fetch servers get wrong. It’s common to validate the initial URL carefully and then follow redirects with a separate, looser path. Safe-Fetch deliberately has no such path.
Threat matrix
The server blocks, by default:
- Cloud metadata addresses (
169.254.169.254) - RFC-1918 private ranges (configurable via
SAFE_FETCH_ALLOW_LOCAL) - Loopback addresses (
127.0.0.1,::1) - IPv4-mapped IPv6 loopback (
::ffff:127.0.0.1) - DNS rebinding, via connection pinning rather than re-resolving on redirect
- Non-HTTP(S) schemes and URLs with embedded credentials
Responses are also explicitly framed as untrusted data, so an agent consuming fetched content doesn’t treat it as instructions.
Usage
Zero-install via stdio:
{
"mcpServers": {
"safe-fetch": {
"command": "npx",
"args": ["-y", "safe-fetch-mcp-server"]
}
}
}
Configurable via environment variables: SAFE_FETCH_MAX_BYTES (5MB default), SAFE_FETCH_TIMEOUT_MS (10s default), plus allowlisting and rate-limit controls.
Why it matters
Agentic tools that can fetch arbitrary URLs are a growing attack surface, and most of the servers wiring that capability into agents today were not built by security engineers. Safe-Fetch is deliberately narrow: it does one thing, fetch content safely, and tries to do it in a way that survives an actual audit rather than a glance at the README.
What I would change now
The allowlist is currently environment-variable based, which works for single-server setups but doesn’t scale to teams managing many agents. A policy-file format (YAML or JSON) with per-domain rules would make configuration auditable and version-controllable. I’d also add structured logging for blocked requests — right now you know a request was blocked, but not enough about the pattern of blocks to tune the policy without reading test output.