fetch
modelcontextprotocol/servers/src/fetch
- Type
- MCP server
- Scanned
- Oct 29, 2026
- Version
- 194cc86e3ae6
- Files read
- 25
This MCP server allows an AI agent to fetch web pages and convert their HTML contents into Markdown. The main risks are server-side request forgery (SSRF) via DNS rebinding or proxy resolution, and unsafe temporary file handling and unbounded subprocess execution when readabilipy invokes Node.js.
What it can reach
- Network
- Makes outbound HTTP and HTTPS requests (with optional HTTP or SOCKS5 proxy support) to any public URL requested by the agent or user.
- Shell commands
- By default, if Node.js is on the system PATH, the underlying readabilipy library spawns a Node.js subprocess (and potentially npm) to run Readability.js.
- Files
- When using the default Readability.js mode with Node.js installed, readabilipy reads and writes temporary files in the system temporary directory.
Findings
-
MediumPrivate IP guard can be bypassed via DNS rebinding or proxy-only hostnames
src/fetch/src/mcp_server_fetch/server.py:157
The private-address guard resolves the target hostname in an httpx request hook before the request is sent, but httpx resolves the hostname a second time when opening the socket. An attacker controlling a domain with a short TTL can return a public IP during the check and a loopback or cloud metadata IP (such as 127.0.0.1 or 169.254.169.254) when httpx connects. Additionally, if local DNS resolution fails with socket.gaierror, the hook returns without error, allowing internal hostnames resolvable only by a configured corporate proxy to be fetched.
Fix: Pin the resolved IP address at the custom transport/socket layer so the connection uses the exact IP address that passed validation, and fail closed on unresolvable hostnames unless explicitly configured otherwise.
-
MediumDefault Readability.js integration uses predictable temp files and runs Node.js without a timeout
src/fetch/src/mcp_server_fetch/server.py:174
By default (use_readability_js=True), the server delegates HTML extraction to readabilipy with use_readability=True. As noted in the project's own test harness and documentation, readabilipy writes the fetched HTML and extracted output to fixed filenames (full.html and article.json) in the shared system temporary directory and spawns Node.js (and potentially npm) with no timeout. Concurrent fetches or another local user can overwrite or read these shared temporary files, and a hung Node.js process will block the server indefinitely.
Fix: Set a per-instance isolated temporary directory using tempfile.TemporaryDirectory in production (as done in tests/harness.py), run the synchronous extraction in a worker thread with a timeout, or default to --no-readability-js.
-
LowUnhiding hidden DOM elements exposes the agent to invisible prompt injection
src/fetch/src/mcp_server_fetch/server.py:292
To support server-side rendered pages that hide content until client hydration, _recover_hidden_content strips hidden, aria-hidden, display:none, and visibility:hidden attributes from any hidden container whose text is longer than the visible extracted text. Attackers commonly hide prompt injection payloads inside display:none or hidden blocks so human users cannot see them; this recovery logic causes those hidden blocks to be extracted and sent to the model.
Fix: Make hidden-content recovery opt-in or restrict it to known hydration markers rather than unhiding arbitrary large display:none or hidden elements.
-
LowEntire response body is downloaded into memory before length truncation
src/fetch/src/mcp_server_fetch/server.py:477
Although the fetch tool truncates the returned text to max_length (default 5000 characters), client.get() buffers the entire HTTP response body in memory and passes the full document through HTML parsing and Markdown conversion before slicing. A malicious URL serving a very large payload within the 30-second timeout can cause high memory and CPU usage.
Fix: Use streaming responses (client.stream) and enforce a maximum response byte limit while reading the body.
-
LowDockerfile creates a non-root user but runs the container as root
The Dockerfile creates an unprivileged 'app' user and sets ownership of /app/.venv to app:app, but omits a USER app instruction before the ENTRYPOINT, causing the server process (and any Node.js subprocesses it spawns) to run as root inside the container.
Fix: Add 'USER app' before the ENTRYPOINT instruction in the Dockerfile.