Repository navigation
Description
Summary
While auditing and hardening local MCP server usage, I reviewed the current mcp-server-fetch behavior and built a local hardened wrapper for operational use. I am opening this issue to share concrete findings and implementation ideas that may be useful upstream.
This is not a report about a known exploit in a specific deployment. It is a defense-in-depth report about the default risk profile of a fetch tool exposed to autonomous agents.
Package/runtime reviewed
- Package:
[email protected] - Runtime shape observed locally:
uvx mcp-server-fetch - Source evidence reviewed locally:
server.pyusesAnyUrl, checksrobots.txtby default, callshttpx.AsyncClient().get(..., follow_redirects=True, timeout=30), then readsresponse.textand applies output slicing. - Audit check:
pip-auditover auvx --with mcp-server-fetchenvironment returned no known package CVEs at the time of testing.
Main findings
-
Private/internal-network access is not blocked by default.
A URL fetch tool can become an SSRF-style primitive when made available to an LLM/agent. The official docs already warn that the server can access local/internal IPs, but safer defaults would reduce accidental exposure. -
Redirects need the same policy checks as initial URLs.
Withfollow_redirects=True, a public URL can redirect to loopback, RFC1918, link-local, metadata, or other reserved ranges unless every hop is validated. -
Output
max_lengthis not a transport byte cap.
The observed implementation readsresponse.textbefore slicing output. Large responses can still consume memory/CPU/network before truncation. -
DNS rebinding / TOCTOU risk.
If a hostname is checked separately from the actual connection target, DNS answers can change between validation and connect. At minimum, the implementation should resolve and reject unsafe answers before connecting; stronger implementations should pin or otherwise constrain the connection target. -
Structured metadata would help agents reason safely.
Current outputs are easier to use if they include final URL, status, content type, bytes read, truncation status, robots decision, and redirect chain. -
Windows extraction/runtime clarity.
On Windows, I observed areadabilipywarning that Node/NPM was not found in theuvxenvironment, so it fell back to Python extraction. This is not fatal, but it is useful to document because it affects extraction behavior and user expectations.
Local hardening behavior that worked well
For our local wrapper, we kept the expected fetch tool name and added these guards:
- allow only
http:andhttps:schemes - reject embedded URL credentials
- optional allowlist and denylist host controls
- DNS-resolve before requests and reject loopback, RFC1918, link-local, carrier-grade NAT, documentation/test ranges, multicast/reserved ranges, and IPv6 local/link-local equivalents
- validate every redirect target before following it
- enforce a pre-read byte cap during streaming, not only output slicing
- retain
robots.txtchecks by default - return structured metadata:
url,final_url,status,content_type,bytes_read,truncated,robots,redirects
Validation from local wrapper
Smoke output from the hardened local wrapper:
fetch tools: fetch
fetch example.com status=200 bytes=528 final=https://example.com/
fetch file block: Blocked unsupported URL scheme: file:
fetch loopback block: Blocked private or reserved IP address: 127.0.0.1
Suggested upstream changes
- Add default private/reserved IP blocking with an explicit opt-out env var.
- Re-validate every redirect hop before following it.
- Add a streaming byte cap before decoding text.
- Reject embedded URL credentials.
- Add optional
FETCH_ALLOW_HOSTS/FETCH_DENY_HOSTSstyle controls. - Return structured metadata alongside text content.
- Document Windows extraction behavior and recommended
PYTHONIOENCODING=utf-8/PATH considerations.
Compatibility note
These changes can be mostly additive if implemented behind conservative config names and with a clear opt-out for users who intentionally need local/private-network access.
Activity
policylayer-dan commentedon May 6, 2026
The SSRF framing is correct and the finding is well-scoped. A few things worth separating:
Redirect and DNS rebinding are particularly hard to fix purely at the server layer because the dangerous resolution happens after your initial check. A URL that passes an allowlist check at request time can resolve to a private IP after a DNS change or mid-redirect. Server-side fixes help but aren't fully reliable on their own.
The layering that actually closes the gap: an allowlist enforced at the tool-call layer (before the request is even dispatched to the fetch server) combined with scoped credentials so the agent can't reach internal services even if a URL slips through. That's distinct from what the server itself can enforce.
For the response-size finding, a hard cap in the server is the right place for that - it's a pure server concern. The private-network access issue is more naturally handled as a URL allowlist enforced by the consumer before the call is made. If you're running mcp-server-fetch alongside other servers you don't control, that consumer-layer allowlist is what PolicyLayer enforces deterministically across the fleet.
sophymarine commentedon May 7, 2026
The hardening direction here is right, especially the private-network and redirect boundaries. One thing that tends to get missed is how the failure mode should surface to the client: a clear, structured error for blocked targets plus a separate timeout class makes retries much easier to reason about. If you add a lightweight allowlist/denylist test matrix alongside the URL validation tests, you can lock in the security behavior without making the happy path brittle. That also helps downstream server implementers avoid reintroducing SSRF regressions when they adapt the fetch tool.
lcv-leo commentedon May 7, 2026
Thanks for the thoughtful notes. I agree with the layering distinction: consumer-side tool-call policy/allowlisting is complementary and important, but this issue is intentionally scoped to safer defaults in the reference fetch server itself.
For this server-side layer, I think the actionable pieces remain:
Consumer-layer enforcement is valuable defense in depth, but I would treat it as complementary rather than a replacement for server-side hardening here.
SyedAnas01 commentedon May 22, 2026
mcp-safeguard SSRF rules (SS-001–SS-003) now detect this
Dropping a note that mcp-safeguard (
pip install mcp-safeguard) v0.3.0 added SSRF static analysis rules that catch the vulnerability described here:These rules fire on
mcp-server-fetch,playwright-mcp, and any MCP tool accepting unconstrained URL parameters. Useful as a CI gate to prevent SSRF-vulnerable tools from shipping.One edge case relevant to this issue: when
get_promptis invoked (rather than thefetchtool),fetch_url()is called directly without any URL validation — the robots.txt check and any URL blocklist would need to be applied at thefetch_url()level (not just incall_tool) to cover this path.— Syed Anas Mohiuddin
norika1207-lab commentedon Jun 2, 2026
lcv-leo commentedon Jun 4, 2026
Thanks @norika1207-lab, I agree with framing this at the protocol boundary as well as the implementation boundary.
A concrete acceptance-test matrix that would be useful here:
For each case, I would want the fixture to assert three signals together: the JSON-RPC/HTTP error sent to the peer, the local exception or cancellation reason visible to the server/host, and a compact trace/log field that ties the two together. That would complement the earlier allow/deny test matrix and make regressions much easier to diagnose downstream.
localden commentedon Jul 30, 2026
Closing as a duplicate of #3741; let's track it there.