Skip to content

fetch: harden URL fetching against private-network access, oversized responses, and redirect/DNS gaps #4116

@lcv-leo

Description

@lcv-leo

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: mcp-server-fetch@2025.4.7
  • Runtime shape observed locally: uvx mcp-server-fetch
  • Source evidence reviewed locally: server.py uses AnyUrl, checks robots.txt by default, calls httpx.AsyncClient().get(..., follow_redirects=True, timeout=30), then reads response.text and applies output slicing.
  • Audit check: pip-audit over a uvx --with mcp-server-fetch environment returned no known package CVEs at the time of testing.

Main findings

  1. 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.

  2. Redirects need the same policy checks as initial URLs.
    With follow_redirects=True, a public URL can redirect to loopback, RFC1918, link-local, metadata, or other reserved ranges unless every hop is validated.

  3. Output max_length is not a transport byte cap.
    The observed implementation reads response.text before slicing output. Large responses can still consume memory/CPU/network before truncation.

  4. 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.

  5. 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.

  6. Windows extraction/runtime clarity.
    On Windows, I observed a readabilipy warning that Node/NPM was not found in the uvx environment, 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: and https: 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.txt checks 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_HOSTS style 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions