AI

Is your Go MCP server open to DNS rebinding? Check v1.4.0

By · Sat Oct 17 2026 · 9 min read · 0 views

View as a Web Story

AI#mcp#security#go#cve-2026-34742#dns rebinding

Go MCP server on localhost accepting a request with a foreign Host header before v1.4.0

Is your Go MCP server open to DNS rebinding? Check v1.4.0

What is CVE-2026-34742 in the Go MCP SDK?

CVE-2026-34742 is a high-severity flaw in the official Go SDK for the Model Context Protocol. The SDK did not turn on DNS rebinding protection by default for HTTP servers. All versions before 1.4.0 are affected, according to the GitLab advisory entry.

The advisory scores it 8.1 under CVSS 3.1 and classifies it as CWE-1188, an insecure default. GitLab dates it April 1, 2026, updated July 21, 2026. The NIST National Vulnerability Database record shows publication on April 2 and a change on July 24. The Mend vulnerability database lists the same score and the same fixed version.

The Model Context Protocol (MCP) is an open protocol that lets an AI assistant call tools and read resources from a separate server. DNS rebinding is an attack where a web page makes a browser treat an attacker's domain as if it pointed at an internal address. The browser then sends requests to your machine and still believes they are same-origin, a rule defined in RFC 6454.

Here is the exposure in one sentence. A developer runs an HTTP MCP server on 127.0.0.1 with no login, opens a hostile web page, and that page can invoke the server's tools with the developer's privileges. The advisory says such a page could invoke tools or access resources on the user's behalf.

Who is affected and who is not?

You are affected if you run an HTTP-based MCP server built on a Go SDK version below 1.4.0 that listens on localhost without authentication. Stdio servers are not affected, per the advisory. Servers that require a token on every request are not exposed to the same attack, because the page cannot supply the token.

Use this checklist:

Your setup Affected before 1.4.0?
Stdio transport (the assistant launches your binary) No
StreamableHTTPHandler on 127.0.0.1, no auth Yes
SSEHandler on 127.0.0.1, no auth Yes
HTTP server on 0.0.0.0 behind real authentication Not through this path
HTTP server on a remote host, public DNS name Different risks apply; this CVE targets localhost

Source: GitLab advisory for CVE-2026-34742 and our reading of the SDK source.

Which Go MCP server setups are exposed to CVE-2026-34742: stdio, loopback HTTP, authenticated HTTP

However, many developers run local MCP servers for Claude Desktop, Cursor or a coding agent. For example, a database inspector or a file server on port 8080 is a common choice. Those are the servers worth checking first.

How can you test your own server in two minutes?

Send your own server two requests that differ only in the Host header, and compare the status codes. A patched server answers the loopback host with 200 and the foreign host with 403. This tests your server on your machine; nothing leaves it.

Advertisement

We built a minimal Go server with one tool, using mcp.NewStreamableHTTPHandler, and compiled it against three SDK versions. Then we sent the standard MCP initialize request twice with curl:

INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'
for host in 127.0.0.1:8080 attacker.example:8080; do
  curl -s -o /dev/null -w "Host: $host -> HTTP %{http_code}\n" \
    -H "Host: $host" -H 'Content-Type: application/json' \
    -H 'Accept: application/json, text/event-stream' \
    -d "$INIT" http://127.0.0.1:8080/
done

The results were the same on every run:

SDK version Host: 127.0.0.1 Host: attacker.example
v1.3.1 200 200
v1.4.0 200 403
v1.8.0 200 403

Source: our test server and requests, October 16, 2026. We tested the Host header only.

HTTP status codes for loopback and foreign Host headers on Go MCP SDK v1.3.1, v1.4.0 and v1.8.0

A DNS rebinding page cannot choose its Host header freely, because the browser sends the attacker's domain name, as the MDN Host header reference describes. That is exactly what the foreign-host request imitates. If your server answers it with 200, a rebinding page can talk to it.

We did not build or run an attack page. The Host-header check is enough to tell whether the guard exists, and it is the same check the SDK now performs.

How do you fix it?

Upgrade the module to v1.4.0 or later, and prefer the current release. The SDK's GitHub releases page and the Go module proxy list v1.8.0 as the newest stable tag at the time of writing. Run this in the module that imports the SDK:

go get github.com/modelcontextprotocol/go-sdk@v1.8.0
go mod tidy
go build ./...

With the fix in place, servers created with StreamableHTTPHandler or SSEHandler get the protection by default, per the advisory. Then rerun the two-request test. You should see 200 for the loopback host and 403 for the foreign one.

Upgrading closes this hole, but it is not a replacement for authentication. The advisory itself says running HTTP-based MCP servers locally without authentication is not recommended. If your server can delete files, run queries or send messages, require a bearer token on every request.

What does the MCP specification require?

The protocol itself already tells server authors what to do. Its Streamable HTTP section carries a security warning in the MCP transports specification. It says servers must validate the Origin header on all incoming connections to prevent DNS rebinding attacks. It adds that local servers should bind only to 127.0.0.1 and should implement authentication for all connections.

That gives you three controls, and a vulnerable server was missing the first. Our Host-header test and the spec's Origin rule look at different headers. In addition, a hostile page controls neither header, so either check stops it. For a custom handler outside the SDK, add both checks yourself.

What does the protection actually check?

It rejects requests that arrive over a loopback address with a Host header that is not a loopback name. The SDK source for v1.4.0 documents this in the comment on DisableLocalhostProtection. Requests arriving via 127.0.0.1 or [::1] with a non-localhost Host header get 403 Forbidden.

The same comment says the guard applies whether the server listens on localhost specifically or on 0.0.0.0. So a server bound to all interfaces is still protected for loopback traffic. Furthermore, the SDK source (see the mcp package documentation) also contains an escape hatch named disablelocalhostprotection, a compatibility switch that turns the protection off.

Treat both switches as a last resort. The DisableLocalhostProtection option exists in StreamableHTTPOptions. Setting it restores the vulnerable behavior, so a quick fix for a failing proxy can quietly undo the patch.

Why does my MCP server return 403 behind a reverse proxy after upgrading?

Because the proxy sits on the same machine, connects over loopback, and forwards the public hostname in the Host header. The SDK sees a loopback connection with a foreign Host and answers 403. Our test reproduced this: a request over 127.0.0.1 with Host: attacker.example is indistinguishable from a proxy passing Host: mcp.example.com.

There are two clean fixes, in order of preference:

  1. Rewrite the Host header in the proxy. Send Host: 127.0.0.1:8080 upstream. In nginx that is proxy_set_header Host 127.0.0.1:8080;.
  2. Disable the guard only with real authentication in place. Set DisableLocalhostProtection and require a bearer token on every request.

We reproduced the 403 itself with curl and did not test an nginx setup. Test your own proxy before you ship the change. A 403 on the first request after a deploy is the symptom to watch for.

What about the other Go MCP SDK advisories?

Two others show up in the same searches, and they differ in status. Do not treat them as one problem.

CVE-2026-27896 concerns JSON key handling. Before version 1.3.1, the SDK used Go's standard encoding/json unmarshaling for protocol messages, which accepts keys with non-standard casing. Version 1.3.1 switched to a case-sensitive decoder, according to the Tenable advisory listing. One aggregator scores it as critical, which we could not confirm against the maintainers.

A third entry, CVE-2026-33252, appears in aggregator listings as a cross-site tool execution issue published on March 23, 2026. We could not find its affected and fixed versions, so we do not state them. Upgrading to the latest tag covers every fix the maintainers have shipped.

If you read only one line, read this: pin v1.4.0 or later, and verify with the Host-header test. That covers the documented fixes for the two CVEs we could confirm.

How do you add a regression test and a token check?

Put the Host-header check into a Go test, so a future downgrade fails CI. This test starts the handler in-process and sends the same two requests we sent with curl. It needs no open port.

func TestForeignHostRejected(t *testing.T) {
	s := mcp.NewServer(&mcp.Implementation{Name: "t", Version: "1"}, nil)
	h := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return s }, nil)
	srv := httptest.NewServer(h)
	defer srv.Close()

	do := func(host string) int {
		req, _ := http.NewRequest("POST", srv.URL, strings.NewReader(initReq))
		req.Host = host
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Accept", "application/json, text/event-stream")
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		resp.Body.Close()
		return resp.StatusCode
	}
	if got := do("127.0.0.1"); got != 200 {
		t.Fatalf("loopback host: want 200, got %d", got)
	}
	if got := do("attacker.example"); got != 403 {
		t.Fatalf("foreign host: want 403, got %d", got)
	}
}

Here initReq is the same JSON-RPC initialize body shown earlier. We ran this test twice on October 16, 2026. It failed on v1.3.1 with foreign host: want 403, got 200 and passed on v1.8.0. That makes it a real guard, not a decoration.

For authentication, wrap the handler in a small middleware that compares a bearer token in constant time:

func requireToken(token string, next http.Handler) http.Handler {
	want := []byte("Bearer " + token)
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		got := []byte(r.Header.Get("Authorization"))
		if subtle.ConstantTimeCompare(got, want) != 1 {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

Import crypto/subtle for it. A page running in a browser cannot read your token, so this check stops the rebinding path even on an old SDK. Keep both layers: the SDK's Host guard and your own token.

Should you upgrade now or wait?

Upgrade now if you run any HTTP MCP server on Go. The change is small, the fix is a version bump, and the failure we found takes two minutes to check. Wait only if your proxy setup would break and you cannot change the Host header today.

Here is how we would roll it out on a team:

  1. Search every go.mod for modelcontextprotocol/go-sdk and note the version.
  2. Bump anything below v1.4.0, then run the two-request test.
  3. Add the test to CI so a future downgrade or option change fails loudly.
  4. Add a bearer-token check to any server that can change data.
  5. Review which tools your server exposes, and remove any you do not use.

The same advice applies to the assistants on the other side. Our earlier coverage of Postgres MCP Read-Only Mode Can Be Bypassed and of Does Your MCP Client Leak OAuth Secrets? Check the SDK Version shows the same pattern: tool servers trust their local environment too much. Those posts cover the client side. This one covers the Go server side.

If you also move your toolchain to Go 1.27 during this upgrade, read Go 1.27 refuses to build with removed GODEBUG settings first, so one change does not hide the other.

What should you tell your team?

Send three facts and one deadline. First, stdio servers are safe. Second, HTTP servers on SDK versions below 1.4.0 accept any Host header. Third, the fix is a version bump plus a token. The deadline is your next release.

Ask each owner to report the SDK version of every MCP server they run, including experimental prototypes, internal developer utilities and automation that colleagues installed on their workstations during earlier evaluations of agent tooling. A short spreadsheet is enough. Servers you forgot about are the ones that get hit, because nobody patches what nobody remembers.

Advertisement

FAQ

Does CVE-2026-34742 affect stdio MCP servers?

No. The advisory states that the stdio transport is not affected. The flaw lives in the HTTP handlers, `StreamableHTTPHandler` and `SSEHandler`. A server that your assistant launches as a child process and talks to over standard input and output has no Host header to abuse.

Which Go MCP SDK version fixes DNS rebinding?

Version 1.4.0 fixes it. After that, servers created with `StreamableHTTPHandler` or `SSEHandler` have the protection enabled by default when bound to localhost. In our test, v1.4.0 and v1.8.0 both returned HTTP 403 for a foreign Host header.

How do I check if my local MCP server is vulnerable?

Send the `initialize` request twice with `curl`, once with `Host: 127.0.0.1:PORT` and once with `Host: attacker.example:PORT`. A vulnerable server returns 200 to both. A patched server returns 200 and then 403. The test runs on your own machine.

Why does my MCP server return 403 after upgrading?

The guard rejects loopback connections that carry a non-loopback Host header. A same-machine reverse proxy forwarding your public hostname triggers it. Rewrite the Host header to a loopback value in the proxy, or add authentication and set `DisableLocalhostProtection`.

Is upgrading enough, or do I still need authentication?

Upgrade first, and add authentication for anything that can change data. The advisory says running HTTP MCP servers locally without authentication is not recommended. The Host check blocks a browser-based attack path; it does not identify who is calling.

Comments

Loading…

Sign in to join the conversation.

Related posts