GitHub Security Advisory: GHSA-r48c-v28r-pf6v
Package: github.com/modelcontextprotocol/registry (GO)
Severity: MODERATE (CVSS 0.0)
CWE: CWE-918: Server-Side Request Forgery (SSRF)
Vulnerable versions: < 1.7.7
Patched in: 1.7.7

Summary: MCP Registry has an unauthenticated SSRF: HTTP namespace verification dials 6to4 / NAT64 / site-local IPv6 addresses, bypassing private-address allowlist

Details:
### Summary

The Registry's HTTP-based namespace verification (`POST /v0/auth/http`, `POST /v0.1/auth/http`) uses `safeDialContext` (`internal/api/handlers/v0/auth/http.go:67-110`) to refuse dialling private/internal addresses when fetching the well-known public-key file from a publisher-supplied domain. The blocklist (`isBlockedIP`, lines 125-133) relies entirely on Go stdlib's `IsLoopback / IsPrivate / IsLinkLocalUnicast / IsMulticast / IsUnspecified` plus a manual CGNAT range. **None of these cover IPv6 6to4 (`2002::/16`), NAT64 (`64:ff9b::/96` and `64:ff9b:1::/48` per RFC 8215), or deprecated site-local (`fec0::/10`)** — all of which encode arbitrary IPv4 in the address bits and tunnel to RFC1918 / cloud-metadata services on dual-stack / NAT64-enabled hosts.

This is the same CWE-918 SSRF class fixed in **GHSA-56c3-vfp2-5qqj** on `czlonkowski/n8n-mcp` (CVSS 8.5 HIGH). The remediation pattern is identical: extend the blocklist with the IPv6 prefix families that embed IPv4.

The endpoint is **unauthenticated** — it is the login flow itself — so attack complexity is low aside from the host-level routing dependency.

Affected: latest `main` HEAD `23f4fda` and current production `v1.7.6` deployment at `https://registry.modelcontextprotocol.io/v0/auth/http`.

### Details

#### Vulnerable code

`internal/api/handlers/v0/auth/http.go:125-133`:

```go
func isBlockedIP(ip net.IP) bool {
    if ip == nil {
        return true
    }
    return ip.IsLoopback() || ip.IsPrivate() ||
        ip.IsLinkLocalUnicast() || ip.IsMulticast() ||
        ip.IsUnspecified() ||
        cgnatRange.Contains(ip)
}
```

Per Go source (`src/net/ip.go`), the relevant stdlib helpers cover:

| Helper | IPv6 coverage |
|---|---|
| `IsLoopback` | `::1`, IPv4-mapped of 127/8 (via `To4()` fast-path) |
| `IsPrivate` | ULA `fc00::/7` only — `ip[0]&0xfe == 0xfc` |
| `IsLinkLocalUnicast` | `fe80::/10` only — `ip[1]&0xc0 == 0x80` (NOT `fec0::/10` which is `0xc0`) |
| `IsMulticast` | `ff00::/8` |
| `IsUnspecified` | `::` |

The Registry's blocklist therefore **does not** cover:

| Prefix | Defined in | Why dangerous |
|---|---|---|
| `2002::/16` | RFC 3056 (6to4) | Bits 16-47 embed an arbitrary IPv4 address. `2002:a9fe:a9fe::` is the 6to4 encoding of `169.254.169.254` (AWS / Azure metadata). `2002:0a00:0001::` encodes `10.0.0.1`. On hosts with 6to4 routing or any explicit `2002::/16` route, the dial reaches the embedded IPv4. |
| `64:ff9b::/96` | RFC 6052 (NAT64 well-known prefix) | Low 32 bits embed an IPv4 address. `64:ff9b::a9fe:a9fe` translates to `169.254.169.254` on any NAT64-enabled network — which is the **default** in IPv6-only GKE node pools, AWS IPv6-only EC2, Azure IPv6 VMs with NAT64, and DNS64/NAT64 corporate networks. |
| `64:ff9b:1::/48` | RFC 8215 (local-use NAT64) | Same tunnelling concern, intended for operator-defined NAT64. |
| `fec0::/10` | RFC 3879 (deprecated site-local) | Some BSD / older Linux stacks still honour these for routing into site-local internal netwo