gopls/internal/cmd: remove gopls -port=int debugging flag The -port flag is confusingly redundant with -listen=address, as the existing TODO notes, and it may bind INADDR_ANY. Also, change these flags to reject implicit use of INADDR_ANY (e.g. ":12345") and suggest using an explicit host of either 0.0.0.0 or localhost (recommended): gopls serve -listen=address gopls serve -mcp.listen=address gopls mcp -listen=address + relnote Fixes golang/go#79211 Fixes CVE-2026-42503 Change-Id: Ia42202d3239468480bd231eeb0ffea88b238ddc6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/774381 Reviewed-by: Nicholas Husin <nsh@golang.org> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Nicholas Husin <husin@google.com>
diff --git a/gopls/doc/release/v0.22.0.md b/gopls/doc/release/v0.22.0.md index bebb067..ae20a84 100644 --- a/gopls/doc/release/v0.22.0.md +++ b/gopls/doc/release/v0.22.0.md
@@ -58,6 +58,9 @@ - The experimental `renameMovesSubpackages` setting determines whether a package-rename operation should also move subdirectories. - The `importsSource` setting is deprecated and will be removed in the next release. +- The `-port=int` debugging flag (redundant with `-listen`) has been removed. +- The `-listen=address` flag now rejects an implicit host (e.g. `:0`). + You must explicitly specify a host such as `0.0.0.0` or (preferably) `localhost`. ## Navigation features
diff --git a/gopls/internal/cmd/serve.go b/gopls/internal/cmd/serve.go index 0be5aaa..a56fc89 100644 --- a/gopls/internal/cmd/serve.go +++ b/gopls/internal/cmd/serve.go
@@ -12,6 +12,7 @@ "io" "log" "os" + "strings" "time" "golang.org/x/sync/errgroup" @@ -30,7 +31,6 @@ type Serve struct { Logfile string `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"` Mode string `flag:"mode" help:"no effect"` - Port int `flag:"port" help:"port on which to run gopls for debugging purposes"` Address string `flag:"listen" help:"address on which to listen for remote connections. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. Otherwise, TCP is used."` IdleTimeout time.Duration `flag:"listen.timeout" help:"when used with -listen, shut down the server when there are no connected clients for this duration"` Trace bool `flag:"rpc.trace" help:"print the full rpc trace in lsp inspector format"` @@ -87,7 +87,7 @@ } di := debug.GetInstance(ctx) - isDaemon := s.Address != "" || s.Port != 0 + isDaemon := s.Address != "" if di != nil { closeLog, err := di.SetLogFile(s.Logfile, isDaemon) if err != nil { @@ -143,37 +143,17 @@ } }() - var network, addr string if s.Address != "" { - network, addr = lsprpc.ParseAddr(s.Address) - } - if s.Port != 0 { - network = "tcp" - // TODO(adonovan): should gopls ever be listening on network - // sockets, or only local ones? - // - // Ian says this was added in anticipation of - // something related to "VS Code remote" that turned - // out to be unnecessary. So I propose we limit it to - // localhost, if only so that we avoid the macOS - // firewall prompt. - // - // Hana says: "s.Address is for the remote access (LSP) - // and s.Port is for debugging purpose (according to - // the Server type documentation). I am not sure why the - // existing code here is mixing up and overwriting addr. - // For debugging endpoint, I think localhost makes perfect sense." - // - // TODO(adonovan): disentangle Address and Port, - // and use only localhost for the latter. - addr = fmt.Sprintf(":%v", s.Port) - } - - if addr != "" { + // -listen=address + network, addr := lsprpc.ParseAddr(s.Address) + if strings.HasPrefix(addr, ":") { + return fmt.Errorf("-listen=%s implicitly binds all network interfaces; please use an explicit host such as 0.0.0.0 (all interfaces) or localhost (safer)", addr) + } log.Printf("Gopls LSP daemon: listening on %s network, address %s...", network, addr) defer log.Printf("Gopls LSP daemon: exiting") return jsonrpc2.ListenAndServe(ctx, network, addr, ss, s.IdleTimeout) } else { + // communicate over stdin/stdout stream := jsonrpc2.NewHeaderStream(fakenet.NewConn("stdio", os.Stdin, os.Stdout)) if s.Trace && di != nil { stream = protocol.LoggingStream(stream, di.LogWriter)
diff --git a/gopls/internal/cmd/usage/serve.hlp b/gopls/internal/cmd/usage/serve.hlp index c72852d..4f2dc9b 100644 --- a/gopls/internal/cmd/usage/serve.hlp +++ b/gopls/internal/cmd/usage/serve.hlp
@@ -20,8 +20,6 @@ experimental: address on which to listen for model context protocol connections. If port is localhost:0, pick a random port in localhost instead. -mode=string no effect - -port=int - port on which to run gopls for debugging purposes -remote.debug=string when used with -remote=auto, the -debug value used to start the daemon -remote.listen.timeout=duration
diff --git a/gopls/internal/cmd/usage/usage-v.hlp b/gopls/internal/cmd/usage/usage-v.hlp index f9085ff..a1e497c 100644 --- a/gopls/internal/cmd/usage/usage-v.hlp +++ b/gopls/internal/cmd/usage/usage-v.hlp
@@ -66,8 +66,6 @@ no effect -otel=string export telemetry to specified OpenTelemetry collector address (e.g. http://localhost:4318) - -port=int - port on which to run gopls for debugging purposes -profile.alloc=string write alloc profile to this file -profile.block=string
diff --git a/gopls/internal/cmd/usage/usage.hlp b/gopls/internal/cmd/usage/usage.hlp index 465a25e..a3dbbf2 100644 --- a/gopls/internal/cmd/usage/usage.hlp +++ b/gopls/internal/cmd/usage/usage.hlp
@@ -63,8 +63,6 @@ no effect -otel=string export telemetry to specified OpenTelemetry collector address (e.g. http://localhost:4318) - -port=int - port on which to run gopls for debugging purposes -profile.alloc=string write alloc profile to this file -profile.block=string
diff --git a/gopls/internal/lsprpc/lsprpc.go b/gopls/internal/lsprpc/lsprpc.go index 32c466e..c3228c5 100644 --- a/gopls/internal/lsprpc/lsprpc.go +++ b/gopls/internal/lsprpc/lsprpc.go
@@ -583,7 +583,7 @@ } } -// ParseAddr parses the address of a gopls remote. +// ParseAddr parses the address of a gopls remote (e.g. "tcp;localhost:12345"). // TODO(rFindley): further document this syntax, and allow URI-style remote // addresses such as "auto://...". func ParseAddr(listen string) (network string, address string) { @@ -592,8 +592,8 @@ if listen == autoNetwork { return autoNetwork, "" } - if parts := strings.SplitN(listen, ";", 2); len(parts) == 2 { - return parts[0], parts[1] + if network, address, ok := strings.Cut(listen, ";"); ok { + return network, address } return "tcp", listen }
diff --git a/gopls/internal/mcp/mcp.go b/gopls/internal/mcp/mcp.go index baa5d37..988e206 100644 --- a/gopls/internal/mcp/mcp.go +++ b/gopls/internal/mcp/mcp.go
@@ -13,6 +13,7 @@ "net" "net/http" "os" + "strings" "sync" "github.com/google/jsonschema-go/jsonschema" @@ -52,6 +53,9 @@ // It is passed the list roots result returned by the MCP client, or an error // if the roots could not be retrieved. rootsHandler may be called concurrently. func Serve(ctx context.Context, address string, sessions Sessions, isDaemon bool, rootsHandler func(*mcp.ListRootsResult, error)) error { + if strings.HasPrefix(address, ":") { + return fmt.Errorf("address %s implicitly binds all network interfaces; please use an explicit host such as 0.0.0.0 (all interfaces) or localhost (safer)", address) + } log.Printf("Gopls MCP server: starting up on http") listener, err := net.Listen("tcp", address) if err != nil {