---
id: "GHSA-3rxj-6jhw-qfv7"
page: "https://infrastack.ai/live/GHSA-3rxj-6jhw-qfv7"
kind: "vulnerability"
aliases: ["GHSA-3rxj-6jhw-qfv7"]
title: "mcp-server-fetch: IPv6 site-local fec0::/10 classified as public (SSRF guard classifier gap, CWE-918)"
severity: "low"
cvss: 3.1
epss: null
exploited: false
status: "open"
category: "ai-tooling"
products: [{"name": "mcp-server-fetch", "fixed": ["2026.11.11"], "affected": ">= 2026.10.10, < 2026.11.11", "ecosystem": "pypi"}]
ecosystems: ["pypi"]
tags: ["ai-tooling", "cwe-918", "mcp", "mcp-server-fetch", "python"]
not_affected_if: ["default-config", "no-ipv6-route"]
published_at: "2026-11-11T07:25:00+00:00"
first_seen_at: "2026-11-11T07:28:35+00:00"
updated_at: "2026-11-11T07:30:02+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-3rxj-6jhw-qfv7", "first_seen_at": "2026-11-11T07:28:35+00:00"}]
exploit_sources: []
---

# mcp-server-fetch: IPv6 site-local fec0::/10 classified as public (SSRF guard classifier gap, CWE-918)

Link for people: https://infrastack.ai/live/GHSA-3rxj-6jhw-qfv7 (this .md is for agents)

The fetch tool's address check treats the deprecated IPv6 site-local range fec0::/10 as public, so a request to a site-local address passes the guard. An attacker who can drive the fetch tool could reach a non-public IPv6 destination, though the advisory says default hosts usually have no route there.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- pypi:mcp-server-fetch affected >= 2026.10.10, < 2026.11.11 fixed 2026.11.11

## How to check

- Run `pip show mcp-server-fetch` and check whether the version is `>= 2026.10.10` and `< 2026.11.11`.
- Run `python -c "from mcp_server_fetch.server import _is_public_address as f; print(f('fec0::1'))"` and a `True` result means the guard is affected.

## Fix

- `mcp-server-fetch` `2026.11.11`

## Timeline

- 2026-11-11T07:28:35+00:00 ghsa-repo https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-3rxj-6jhw-qfv7

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-3rxj-6jhw-qfv7

## From the sources

### Summary

_is_public_address in mcp-server-fetch omits fec0::/10 (deprecated IPv6 site-local) and ::ffff:0:0/96 from its non-public list and falls back to ip.is_global, which returns True for fec0::1. A fetch to http://[fec0::1]/ passes the SSRF guard. Low real-world reachability on default hosts; this is a classifier-correctness gap, not a proven metadata exploit.

### Details

**Server:** fetch (`mcp-server-fetch`, PyPI)
**Affected versions:** 2026.11.4 and 2026.10.24 (reproduced on both)
**CWE:** CWE-918 (SSRF — guard classifier gap)

### Summary
`_is_public_address` (`src/mcp_server_fetch/server.py`) unwraps IPv4-mapped and well-known NAT64 forms and lists many non-public ranges, but omits `fec0::/10` (deprecated IPv6 site-local, RFC 3879) and `::ffff:0:0/96` (IPv4-translated). It then falls back to `return ip.is_global and not ip.is_multicast`, and Python's `ipaddress` reports `fec0::1` as `is_global == True`. So a `fetch` to `http://[fec0::1]/…` passes the guard that is meant to refuse non-public destinations.

### Scope / honest reachability note
This is a guard-classifier correctness issue — the guard's own threat model includes non-public destinations — **not** a proven metadata-service exploit on default hosts. On a stock modern kernel `fec0::1` usually has no route and the connection times out; the translated loopback form `[::ffff:0:127.0.0.1]` was correctly normalized and blocked. Every other loopback / RFC1918 / link-local / redirect form in a 36-case matrix (2-hop, protocol-relative, 307/308, userinfo, trailing-dot and uppercase-host redirects, `0.0.0.0`, `[::]`, full-octal IPv4, mapped / NAT64) was correctly refused.

### Impact
The SSRF allowlist classifies a non-public (site-local) IPv6 destination as public. Severity is Low given the limited default-host reachability; it is a defense-in-depth / correctness gap in a guard whose purpose is to deny non-public destinations.

### Suggested fix
Explicitly block `fec0::/10` and `::ffff:0:0/96`; deny-by-default on `not ip.is_global` combined with reserved / site-local checks rather than relying on `is_global` alone.

### Disclosure
Reported privately per SECURITY.md. Analysis and drafting were AI-assisted and reviewed by a human researcher at ShieldPi. Credit, if accepted: ShieldPi (support@shieldpi.io).

### Proof of concept

1. Classifier level (2026.11.4 and 2026.10.24):
   python -c "from mcp_server_fetch.server import _is_public_address as f; print(f('fec0::1'))"
   -> prints True (should be False).
2. End-to-end: call fetch {"url":"http://[fec0::1]/admin"} -> the host passes the public-address guard. On a stock host with no route to fec0::/10 the TCP connection then times out; on a host that routes fec0::/10 the request proceeds to the non-public destination.
3. Negative controls (correctly refused): http://[::ffff:0:127.0.0.1]/ , http://0.0.0.0/ , http://[::]/ , and 2-hop / 307 / 308 redirects into loopback or RFC1918.
Reproduced on mcp-server-fetch 2026.11.4 and 2026.10.24.

### Impact

The SSRF guard classifies a non-public IPv6 site-local address (fec0::1) as a public destination, so a fetch to it bypasses the guard. Real-world reachability is low on default hosts (no route to fec0::/10); impact is a classifier-correctness / defense-in-depth gap, not a proven metadata-service read. Affects any deployment relying on mcp-server-fetch's address guard to refuse non-public destinations.

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
