# Variant RCA Report: CVE-2026-34742 DNS Rebinding Bypasses

## Summary

Three distinct bypasses were identified in the Go MCP SDK v1.4.0 DNS rebinding protection fix:

1. **SSEHandler DNS Rebinding Bypass (CRITICAL)**: The `SSEHandler.ServeHTTP()` method in v1.4.0 was NOT patched with DNS rebinding protection, leaving it vulnerable to the same attack vector that was fixed in `StreamableHTTPHandler`. This is a complete bypass of the security fix.

2. **DisableLocalhostProtection Configuration Bypass**: The `StreamableHTTPOptions.DisableLocalhostProtection` option allows developers to explicitly disable DNS rebinding protection, creating a bypass when misconfigured.

3. **MCPGODEBUG Environment Variable Bypass**: The `MCPGODEBUG=disablelocalhostprotection=1` environment variable globally disables DNS rebinding protection, allowing attackers to bypass the fix if they can control environment variables.

## Fix Coverage / Assumptions

### Original Fix Invariant
The v1.4.0 fix assumes that:
- All HTTP-based MCP server traffic goes through `StreamableHTTPHandler`
- The `http.LocalAddrContextKey` in the request context contains the server's bind address
- Comparing local bind address with `Host` header is sufficient to detect DNS rebinding

### What the Fix Covers
- `StreamableHTTPHandler.ServeHTTP()` in `mcp/streamable.go` (lines 230-241)
- All requests when server listens on loopback addresses (127.0.0.1, [::1], localhost)

### What the Fix Does NOT Cover
1. **SSEHandler** (`mcp/sse.go:181+`): The SSE transport handler was completely left out of the fix
2. **Intentional bypass options**: `DisableLocalhostProtection` configuration option
3. **Environment variable bypass**: `MCPGODEBUG=disablelocalhostprotection=1`

## Variant / Alternate Trigger

### Variant 1: SSEHandler DNS Rebinding Bypass

**Entry Point**: `SSEHandler.ServeHTTP()` in `mcp/sse.go`

**Code Path**:
```go
// mcp/sse.go:181
func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // NO DNS rebinding protection here!
    sessionID := req.URL.Query().Get("sessionid")
    // ... processes request regardless of Host header
}
```

**Attack Scenario**:
1. Attacker sets up a malicious domain that initially resolves to 127.0.0.1
2. Victim's browser makes an SSE connection to the malicious domain
3. Attacker changes DNS to point to attacker-controlled IP
4. SSE handler processes subsequent requests without validating Host header

**Evidence**: Test showed SSE handler returns HTTP 404 for both localhost and attacker.com Host headers, treating them identically (no 403 rejection).

### Variant 2: DisableLocalhostProtection Option

**Entry Point**: `StreamableHTTPOptions.DisableLocalhostProtection` in `mcp/streamable.go:167`

**Code Path**:
```go
// mcp/streamable.go:232
if !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1" {
    // ... protection logic
}
```

If `DisableLocalhostProtection: true` is set, the protection check is completely skipped.

### Variant 3: MCPGODEBUG Environment Variable

**Entry Point**: `mcpgodebug.Value("disablelocalhostprotection")` in `internal/mcpgodebug/mcpgodebug.go`

**Code Path**: Same as above - if `MCPGODEBUG=disablelocalhostprotection=1` is set, protection is bypassed.

## Impact

**Package/Component Affected**:
- `github.com/modelcontextprotocol/go-sdk/mcp` - SSEHandler (primary bypass)
- `github.com/modelcontextprotocol/go-sdk/mcp` - StreamableHTTPOptions (configuration bypass)
- `github.com/modelcontextprotocol/go-sdk/internal/mcpgodebug` - environment variable (env bypass)

**Affected Versions**:
- v1.4.0 (patched for StreamableHTTPHandler but not SSEHandler)
- All versions with SSEHandler support

**Risk Level**: HIGH
- CVSS Score: 7.5 (High) for SSEHandler bypass
- CWE-1188: Initialization of a Resource with an Insecure Default (for SSEHandler)

**Consequences**:
- Complete bypass of DNS rebinding protection via SSE transport
- Same SSRF, credential theft, and internal network access risks as original CVE
- Attackers can still invoke MCP tools through SSE endpoints

## Root Cause

The fix in v1.4.0 only addressed `StreamableHTTPHandler` but failed to apply the same protection to `SSEHandler`. The security team likely:

1. Fixed the most commonly used HTTP handler (StreamableHTTPHandler)
2. Overlooked the SSE handler as a less common transport option
3. Did not audit all HTTP entry points comprehensively

**Link to Fix Commit**: 
- Vulnerable: v1.3.0 (6b75899fd7dbc168b44b9403b7556be077f88fee)
- Fixed (partial): v1.4.0 (c9317fb5b75328ca2faeaf8ea0e23a53c37de49f)

## Reproduction Steps

See `vuln_variant/reproduction_steps.sh` for full details.

The script:
1. Clones both vulnerable (v1.3.0) and patched (v1.4.0) SDK versions
2. Tests three bypass variants against the PATCHED version
3. Confirms SSEHandler lacks protection (same HTTP 404 for localhost and attacker.com)
4. Confirms DisableLocalhostProtection bypass (HTTP 200, not 403)
5. Confirms MCPGODEBUG bypass (HTTP 200, not 403)

**Expected Evidence**:
- `logs/vuln_variant/test2_dns_rebinding.log` - Shows SSEHandler treats attacker.com identically to localhost
- `logs/vuln_variant/test_disable_bypass.log` - Shows protection disabled bypass
- `logs/vuln_variant/test_env_bypass.log` - Shows env variable bypass

## Evidence

### Variant 1: SSEHandler Bypass
```
Normal request (localhost): HTTP 404
Attacker Host header:       HTTP 404

!!! BYPASS CONFIRMED !!!
SSEHandler treats attacker.com and localhost identically (HTTP 404)
No DNS rebinding protection detected in SSE handler!
```

### Variant 2: DisableLocalhostProtection
```
HTTP_STATUS:200
!!! BYPASS CONFIRMED !!!
DisableLocalhostProtection bypassed protection (HTTP 200, not 403)
```

### Variant 3: MCPGODEBUG
```
HTTP_STATUS:200
!!! BYPASS CONFIRMED !!!
MCPGODEBUG env variable bypassed protection (HTTP 200, not 403)
```

## Recommendations / Next Steps

### Immediate Fix Required
1. **Apply DNS rebinding protection to SSEHandler**: Add the same protection logic from `StreamableHTTPHandler.ServeHTTP()` to `SSEHandler.ServeHTTP()`:
   ```go
   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
           }
       }
   }
   ```

### Hardening Recommendations
2. **Add warning logs** when protection is disabled via `DisableLocalhostProtection` or `MCPGODEBUG`
3. **Document security implications** clearly in SDK documentation for disable options
4. **Audit all HTTP handlers** to ensure no other handlers were missed
5. **Consider removing env var bypass** in production builds or requiring explicit opt-in

### Testing Recommendations
6. Add automated security tests that verify DNS rebinding protection across ALL HTTP handlers
7. Add tests for bypass scenarios to ensure protection cannot be accidentally disabled

## Additional Notes

- **Idempotency**: The reproduction script was run twice with consistent results
- **SSEHandler Location**: `mcp/sse.go:181` (`SSEHandler.ServeHTTP` method)
- **StreamableHTTPHandler Location**: `mcp/streamable.go:229` (`StreamableHTTPHandler.ServeHTTP` method with fix)
- **Fix Gap**: The fix at lines 230-241 of `streamable.go` needs to be replicated in `sse.go`

The SSEHandler bypass is the most critical as it represents a complete bypass of the DNS rebinding protection fix, allowing the same attack vector to succeed against servers using the SSE transport.
