diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index e541b20a2..e651ab060 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -179,6 +179,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } +// validLocalHost allows either localhost or loopback IP hosts on platforms +// that use token security. +var validLocalHost = runtime.GOOS == "darwin" || runtime.GOOS == "ios" || runtime.GOOS == "android" + // validHost reports whether h is a valid Host header value for a LocalAPI request. func validHost(h string) bool { // The client code sends a hostname of "local-tailscaled.sock". @@ -186,7 +190,9 @@ func validHost(h string) bool { case "", apitype.LocalAPIHost: return true } - // Allow either localhost or loopback IP hosts. + if !validLocalHost { + return false + } host, _, err := net.SplitHostPort(h) if err != nil { return false @@ -194,7 +200,7 @@ func validHost(h string) bool { if host == "localhost" { return true } - addr, err := netip.ParseAddr(h) + addr, err := netip.ParseAddr(host) if err != nil { return false } diff --git a/ipn/localapi/localapi_test.go b/ipn/localapi/localapi_test.go new file mode 100644 index 000000000..6f6fe48ce --- /dev/null +++ b/ipn/localapi/localapi_test.go @@ -0,0 +1,34 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package localapi + +import ( + "testing" + + "tailscale.com/client/tailscale/apitype" +) + +func TestValidHost(t *testing.T) { + tests := []struct { + host string + valid bool + }{ + {"", true}, + {apitype.LocalAPIHost, true}, + {"localhost:9109", validLocalHost}, + {"127.0.0.1:9110", validLocalHost}, + {"[::1]:9111", validLocalHost}, + {"100.100.100.100:41112", false}, + {"10.0.0.1:41112", false}, + {"37.16.9.210:41112", false}, + } + + for _, test := range tests { + t.Run(test.host, func(t *testing.T) { + if got := validHost(test.host); got != test.valid { + t.Errorf("validHost(%q)=%v, want %v", test.host, got, test.valid) + } + }) + } +}