package config import ( "fmt" "net" ) // ValidateBindAddr enforces loopback-by-default binding. It returns an error // when addr resolves to a non-loopback interface and externalAllowed is false, // so the server fails loud rather than silently exposing itself. Loopback // addresses (127.0.0.1, ::1, localhost) always pass. func ValidateBindAddr(addr string, externalAllowed bool) error { host, _, err := net.SplitHostPort(addr) if err != nil { // No port (or malformed): treat the whole string as the host. host = addr } if isLoopbackHost(host) { return nil } if externalAllowed { return nil } return fmt.Errorf( "refusing to bind non-loopback address %q: set external_bind_allowed = true to override, "+ "but prefer fronting external access with a reverse proxy (Tailscale / Cloudflare Access / Caddy)", addr, ) } // isLoopbackHost reports whether host is a loopback target. An empty host // (e.g. ":8484") or 0.0.0.0/:: binds all interfaces and is NOT loopback. func isLoopbackHost(host string) bool { switch host { case "localhost": return true case "": return false } if ip := net.ParseIP(host); ip != nil { return ip.IsLoopback() } return false }