From ba8757e546d6f4a674a1e8ee5c243b9e343641fe Mon Sep 17 00:00:00 2001 From: Steven Mertens Date: Tue, 7 Apr 2026 23:16:52 +0200 Subject: [PATCH] fix: reject URL-style client endpoints --- cmd/client_targets_test.go | 9 +++++++++ internal/cli/endpoint.go | 12 ++++++++++++ internal/cli/endpoint_test.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/cmd/client_targets_test.go b/cmd/client_targets_test.go index 67e884d..ba90fc7 100644 --- a/cmd/client_targets_test.go +++ b/cmd/client_targets_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "reflect" + "strings" "testing" "time" @@ -64,6 +65,11 @@ example.org:8443 # inline comment args: []string{"example.com:abc"}, wantErr: true, }, + { + name: "reject URL input", + args: []string{"https://example.com"}, + wantErr: true, + }, } for _, tt := range tests { @@ -73,6 +79,9 @@ example.org:8443 # inline comment if err == nil { t.Fatalf("expected error, got nil") } + if len(tt.args) == 1 && strings.Contains(tt.args[0], "://") && !strings.Contains(err.Error(), "expected host[:port], not a URL") { + t.Fatalf("expected concise URL error, got %v", err) + } return } if err != nil { diff --git a/internal/cli/endpoint.go b/internal/cli/endpoint.go index 3285c39..2ad6bd4 100644 --- a/internal/cli/endpoint.go +++ b/internal/cli/endpoint.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "strconv" + "strings" "github.com/catay/tlsctl/internal/tlsquery" ) @@ -12,6 +13,10 @@ import ( // An optional startTLSProto can be provided to select the default port // for STARTTLS protocols (smtp=587, imap=143, pop3=110, ldap=389). func NormalizeEndpoint(endpoint string, startTLSProto ...string) (string, error) { + if looksLikeURL(endpoint) { + return "", fmt.Errorf("expected host[:port], not a URL") + } + host, port, err := net.SplitHostPort(endpoint) if err != nil { host = endpoint @@ -34,6 +39,13 @@ func NormalizeEndpoint(endpoint string, startTLSProto ...string) (string, error) return net.JoinHostPort(host, port), nil } +func looksLikeURL(endpoint string) bool { + if strings.Contains(endpoint, "://") { + return true + } + return strings.ContainsAny(endpoint, "/?#@") +} + func defaultPort(startTLSProto ...string) string { if len(startTLSProto) > 0 { if port, ok := tlsquery.StartTLSPort(startTLSProto[0]); ok { diff --git a/internal/cli/endpoint_test.go b/internal/cli/endpoint_test.go index a7096af..edc43a7 100644 --- a/internal/cli/endpoint_test.go +++ b/internal/cli/endpoint_test.go @@ -82,6 +82,36 @@ func TestNormalizeEndpoint(t *testing.T) { endpoint: "[2001:db8::1]:8443", want: "[2001:db8::1]:8443", }, + { + name: "reject URL scheme", + endpoint: "https://example.com", + wantError: true, + errorMsg: "expected host[:port], not a URL", + }, + { + name: "reject path", + endpoint: "example.com/path", + wantError: true, + errorMsg: "expected host[:port], not a URL", + }, + { + name: "reject query", + endpoint: "example.com?foo=bar", + wantError: true, + errorMsg: "expected host[:port], not a URL", + }, + { + name: "reject fragment", + endpoint: "example.com#anchor", + wantError: true, + errorMsg: "expected host[:port], not a URL", + }, + { + name: "reject userinfo", + endpoint: "user@example.com", + wantError: true, + errorMsg: "expected host[:port], not a URL", + }, } for _, tt := range tests {