Skip to content

server: default --allowed-hosts to loopback for local (non-remote) deployments#3342

Open
adilburaksen wants to merge 1 commit into
googleapis:mainfrom
adilburaksen:harden/allowed-hosts-loopback-default
Open

server: default --allowed-hosts to loopback for local (non-remote) deployments#3342
adilburaksen wants to merge 1 commit into
googleapis:mainfrom
adilburaksen:harden/allowed-hosts-loopback-default

Conversation

@adilburaksen

Copy link
Copy Markdown

Description

--allowed-hosts and --allowed-origins default to ["*"]. The server defaults to the HTTP transport bound to 127.0.0.1, so out of the box for local development the Host check is a no-op (wildcard). As the existing startup warning already notes, a wildcard --allowed-hosts leaves the server "vulnerable to DNS rebinding attacks" — a web page open in the developer's browser can rebind an attacker domain to 127.0.0.1 (requests then become same-origin, so CORS does not apply) and reach the MCP/tool endpoints, which execute against the developer's configured data sources.

This makes that the default rather than something the user must remember to harden.

Change

--allowed-hosts now defaults to a loopback allowlist (127.0.0.1, localhost, ::1) only when the server is bound to a loopback address and the user did not pass --allowed-hosts explicitly.

  • Local (loopback bind), no flag → loopback allowlist (DNS-rebinding blocked by default). An INFO line is logged.
  • Non-loopback bind (0.0.0.0, ::, a specific host — i.e. remote/Cloud Run/containers) → existing ["*"] behavior is preserved, so remote deployments are unaffected.
  • Explicit --allowed-hosts (including *) → always respected unchanged.

Detection of "explicitly set" uses Cobra's flags.Changed("allowed-hosts"). The existing wildcard DNS-rebinding warning is preserved and now only fires when the wildcard actually survives.

--allowed-origins is intentionally left unchanged (scope kept to the rebinding-relevant Host defense).

Testing

go build ./... and go test ./cmd/... ./internal/server/... pass; gofmt clean. Added TestAllowedHostsContextAwareDefault covering: loopback bind + no flag → evil.com 403 / localhost 200; non-loopback bind → wildcard preserved (evil.com 200); explicit flag respected.

@adilburaksen adilburaksen requested a review from a team as a code owner June 4, 2026 00:07

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a secure-by-default behavior for local development by defaulting the --allowed-hosts flag to a loopback-only allowlist (127.0.0.1, localhost, ::1) when the server binds to a loopback address and the flag is not explicitly set. This change effectively mitigates DNS rebinding attacks. The review feedback points out a potential issue in the isLoopbackAddress helper, where the fallback prefix check strings.HasPrefix(host, "127.") could incorrectly match domain names starting with 127. (e.g., 127.example.com), and provides a code suggestion to refine this check.

Comment thread internal/server/server.go
Comment on lines +339 to +340
// Fall back to a 127.* prefix check for non-canonical IPv4 inputs.
return strings.HasPrefix(host, "127.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback prefix check strings.HasPrefix(host, "127.") can incorrectly match domain names that start with 127. (for example, 127.example.com or 127-server.local if resolved/configured as the address). If a remote deployment binds to such a domain, it would be incorrectly classified as a loopback address, causing the server to restrict --allowed-hosts to loopback only and breaking remote access.

To prevent this, we should ensure that the remaining characters after 127. only contain digits and dots (which are characteristic of non-canonical IPv4 representations like 127.1 or 127.0.1).

	// Fall back to a 127.* prefix check for non-canonical IPv4 inputs.
	if strings.HasPrefix(host, "127.") {
		for i := 4; i < len(host); i++ {
			if (host[i] < '0' || host[i] > '9') && host[i] != '.' {
				return false
			}
		}
		return len(host) > 4
	}
	return false

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants