# Patch Analysis: CVE-2026-34742 DNS Rebinding Fix

## Fix Summary

The fix in Go MCP SDK v1.4.0 adds automatic DNS rebinding protection to the `StreamableHTTPHandler.ServeHTTP()` method in `mcp/streamable.go`. The protection validates that when the server is listening on a loopback address (127.0.0.1, [::1], localhost), incoming requests must also have a loopback `Host` header.

## Fix Implementation Details

### Code Changes (lines 230-241 in patched/streamable.go)

```go
// DNS rebinding protection: auto-enabled for localhost servers.
// See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise
if !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1" {
    if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil {
        if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) {
            http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden)
            return
        }
    }
}
```

### Fix Components

1. **StreamableHTTPOptions.DisableLocalhostProtection** - A boolean option allowing developers to explicitly disable protection
2. **mcpgodebug.Value("disablelocalhostprotection")** - An environment variable `MCPGODEBUG=disablelocalhostprotection=1` that globally disables protection
3. **util.IsLoopback()** - Helper function that checks if an address is loopback by:
   - Checking for `localhost` string match
   - Parsing IP address and checking `ip.IsLoopback()`

## Fix Assumptions

The fix makes these assumptions:

1. **Servers listening on loopback addresses need protection**: The check only applies when `util.IsLoopback(localAddr.String())` returns true
2. **Host header validation is sufficient**: Comparing the local bind address with the Host header prevents DNS rebinding
3. **Attackers cannot bypass IsLoopback() check**: The validation uses standard library functions
4. **All HTTP traffic goes through StreamableHTTPHandler**: Assumes this is the primary/only HTTP handler

## What the Fix Does NOT Cover (Bypass Opportunities)

### 1. SSEHandler Missing Protection (CONFIRMED BYPASS)

**Location**: `mcp/sse.go:181` - `SSEHandler.ServeHTTP()`

The `SSEHandler` in BOTH v1.3.0 and v1.4.0 does NOT implement the DNS rebinding protection. The fix was only applied to `StreamableHTTPHandler`, leaving the SSE transport vulnerable.

**Code in patched/sse.go**:
```go
func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    sessionID := req.URL.Query().Get("sessionid")
    // No DNS rebinding protection here!
    // ...
}
```

**Impact**: Attackers can still perform DNS rebinding attacks against SSE-based MCP servers even after upgrading to v1.4.0.

### 2. MCPGODEBUG Environment Variable Bypass

**Location**: `internal/mcpgodebug/mcpgodebug.go`

The `MCPGODEBUG=disablelocalhostprotection=1` environment variable completely disables the protection globally. This is intended for compatibility but creates a bypass if:
- An attacker can control environment variables
- A developer mistakenly enables this in production

### 3. DisableLocalhostProtection Configuration Bypass

**Location**: `mcp/streamable.go:167`

Developers can explicitly disable protection:
```go
handler := mcp.NewStreamableHTTPHandler(getServer, &mcp.StreamableHTTPOptions{
    DisableLocalhostProtection: true,  // Bypass!
})
```

This is a legitimate option for specific use cases but represents a potential bypass if misused.

### 4. Non-Loopback Binding Bypass

The protection only triggers when the server listens on a loopback address. If a server is bound to `0.0.0.0` or a public interface, the protection is not applied, potentially leaving the server vulnerable if:
- The server is behind a reverse proxy
- Network segmentation is relied upon for security

## IsLoopback() Implementation Details

**Location**: `internal/util/net.go`

```go
func IsLoopback(addr string) bool {
    host, _, err := net.SplitHostPort(addr)
    if err != nil {
        host = strings.Trim(addr, "[]")
    }
    if host == "localhost" {
        return true
    }
    ip, err := netip.ParseAddr(host)
    if err != nil {
        return false
    }
    return ip.IsLoopback()
}
```

The function handles:
- Host:port format
- IPv6 bracket notation
- `localhost` string literal
- Standard Go `netip.ParseAddr()` and `IsLoopback()`

Potential edge cases:
- IPv4-mapped IPv6 addresses (::ffff:127.0.0.1) - handled by `netip.ParseAddr()`
- Null bytes or Unicode in hostname - handled by Go standard library

## Conclusion

The fix in v1.4.0 successfully addresses the DNS rebinding vulnerability for `StreamableHTTPHandler` but leaves a **critical bypass in `SSEHandler`**. The SSE transport endpoint remains vulnerable to the same DNS rebinding attack vector.

**Recommendation**: Apply the same DNS rebinding protection to `SSEHandler.ServeHTTP()` that was added to `StreamableHTTPHandler.ServeHTTP()`.
