MCP-SERVER-FETCH · GHSA-66qx-2266-pmj6 · Package
FIX → 2026.10.24
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.
first public Oct 25, 2026 · 1 source
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Teams running the mcp-server-fetch server where attacker-influenced URLs can be fetched, on networks with NAT64/DNS64 egress such as some IPv6-only cloud environments. On hosts without NAT64 egress the embedded address is not routable, so exploitability depends on the deployment. Teams that do not run this server can skip it.
Not affected if default-config, no-nat64-egress
Who cares cloud, mcp, python, self-hosted
pip show mcp-server-fetch and confirm it is 2026.10.10 through 2026.10.19.Upgrade to 2026.10.24
mcp-server-fetch fixed in 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.
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 (6to4), and handles , but it omits the well-known NAT64 prefix (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 , returning True.
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
2026.10.242002::/16ipv4_mapped64:ff9b::/96ip.is_global and not ip.is_multicastImpact (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.
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.
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.