---
id: "GHSA-66qx-2266-pmj6"
page: "https://infrastack.ai/live/GHSA-66qx-2266-pmj6"
kind: "vulnerability"
aliases: ["GHSA-66qx-2266-pmj6"]
title: "fetch server: SSRF guard treats the well-known NAT64 prefix 64:ff9b::/96 as public, so a metadata address is reachable on NAT64/DNS64 egress"
severity: "low"
cvss: 3.1
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "mcp-server-fetch", "fixed": ["2026.10.24"], "affected": ">= 2026.10.10, <= 2026.10.19", "ecosystem": "pypi"}]
ecosystems: ["pypi"]
tags: ["cloud", "cwe-918", "mcp", "package", "python", "self-hosted"]
not_affected_if: ["default-config", "no-nat64-egress"]
published_at: "2026-10-25T00:18:52+00:00"
first_seen_at: "2026-11-09T22:53:25+00:00"
updated_at: "2026-11-09T22:58:22+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-66qx-2266-pmj6", "first_seen_at": "2026-11-09T22:53:25+00:00"}]
exploit_sources: []
---

# fetch server: SSRF guard treats the well-known NAT64 prefix 64:ff9b::/96 as public, so a metadata address is reachable on NAT64/DNS64 egress

Link for people: https://infrastack.ai/live/GHSA-66qx-2266-pmj6 (this .md is for agents)

The mcp-server-fetch SSRF guard treats addresses in the well-known NAT64 prefix 64:ff9b::/96 as public, without checking the embedded IPv4. An attacker who controls a fetched URL can reach internal or cloud metadata addresses such as 169.254.169.254 on hosts whose network uses NAT64/DNS64 egress.

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

## Affected

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

## How to check

- Check the installed version: `pip show mcp-server-fetch` and confirm it is `2026.10.10` through `2026.10.19`.
- Check whether the host has IPv6-only or NAT64/DNS64 egress, since the bypass needs that routing.

## Fix

- `mcp-server-fetch` fixed in `2026.10.24`

## Timeline

- 2026-11-09T22:53:25+00:00 ghsa-repo https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-66qx-2266-pmj6

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-66qx-2266-pmj6

## From the sources

### Summary

In mcp-server-fetch, `_is_public_address()` (src/fetch/src/mcp_server_fetch/server.py) decides whether a resolved address is a public destination. Its `_NON_PUBLIC_NETWORKS` list refuses the local-use NAT64 prefix `64:ff9b:1::/48` (RFC 8215) and the 6to4 prefix `2002::/16`, but it does not list the well-known NAT64 prefix `64:ff9b::/96` (RFC 6052) and does not decode the IPv4 embedded in its low 32 bits. An address `64:ff9b::<embedded-IPv4>` is therefore classified as public even when the embedded IPv4 is non-public, e.g. the cloud metadata endpoint 169.254.169.254. The `_refuse_private_destination` hook is correctly applied on every request and redirect hop; the gap is in the classifier itself.

### Details

Affected code: src/fetch/src/mcp_server_fetch/server.py, function `_is_public_address` and the module-level `_NON_PUBLIC_NETWORKS` tuple.

The tuple lists `64:ff9b:1::/48` (local-use NAT64) and `2002::/16` (6to4), and handles `ipv4_mapped`, but it omits the well-known NAT64 prefix `64:ff9b::/96` (RFC 6052). For an address in that prefix the function does not decode the embedded IPv4, and because the address is globally-scoped it falls through to `ip.is_global and not ip.is_multicast`, returning True.

Impact (CWE-918): on a host whose egress uses NAT64/DNS64 (RFC 6146/6147) — common on IPv6-only networks, including some cloud environments — `64:ff9b::a9fe:a9fe` routes to 169.254.169.254, so a fetch the server believes is public reaches the metadata endpoint the guard is documented to refuse. Where the server is exposed to attacker-influenced URLs (its purpose), this is an SSRF-guard bypass to internal/metadata addresses on such networks. On a host without NAT64 egress the address is not routable, so, like the DNS-rebinding caveat the README already states, exploitability depends on the deployment.

This is the same class as several advisories published in the last week (IPv6-transition / embedded-IPv4 blocklist gaps), so it is a known, recurring pattern.

Suggested fix: before the network checks, decode embedded-IPv4 forms and classify the embedded address (for `64:ff9b::/96` take the low 32 bits, as the code already does for `ipv4_mapped`); or simply add `64:ff9b::/96` to `_NON_PUBLIC_NETWORKS`, since a NAT64-wrapped public IPv4 is reachable directly anyway. I'm happy to open a PR with tests once this is acknowledged, following CONTRIBUTING.

AI disclosure (per your CONTRIBUTING's AI-assisted workflow): this report was prepared with AI assistance (Claude Code) — a source review plus a unit-level evaluation of the installed classifier. The behaviour is confirmed by that evaluation, not assumed; I reviewed it.

### Proof of concept

Unit-level reproduction against the project's own classifier (no network, no third-party system). Import the real function from the installed package and classify addresses that each encode 169.254.169.254 through different IPv6 representations:

    import mcp_server_fetch.server as s
    s._is_public_address("169.254.169.254")        # -> False  (plain link-local, correct)
    s._is_public_address("::ffff:169.254.169.254") # -> False  (IPv4-mapped, correct)
    s._is_public_address("64:ff9b:1::a9fe:a9fe")    # -> False  (local-use NAT64, correct)
    s._is_public_address("2002:a9fe:a9fe::")        # -> False  (6to4, correct)
    s._is_public_address("64:ff9b::a9fe:a9fe")      # -> True   (well-known NAT64 -> treated as PUBLIC)
    s._is_public_address("8.8.8.8")                 # -> True   (real public, control)

The 64:ff9b::a9fe:a9fe case embeds 169.254.169.254 in the low 32 bits of the well-known NAT64 prefix and is classified as public. No request was sent and no cloud or third-party system was touched; this evaluates the same surface the project's tests cover. On a NAT64/DNS64 network that address resolves/routes to the metadata endpoint.

### Impact

CWE-918 (SSRF). On a host whose egress uses NAT64/DNS64 (common on IPv6-only networks, including some cloud environments), an attacker who can influence a fetched URL can reach internal-only services and the cloud metadata endpoint 169.254.169.254 by using an address in the well-known NAT64 prefix 64:ff9b::/96 that embeds the target IPv4, because _is_public_address classifies it as public. This defeats the SSRF guard the server documents as refusing metadata/private addresses, and the response is returned to the caller. On hosts without NAT64 egress the address is not routable, so exploitability depends on the deployment, like the DNS-rebinding caveat already noted in the README.

---

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