From 24fa143f209b46c27566261c7a3cf5603cb2f9ce Mon Sep 17 00:00:00 2001 From: Carlo Goetz Date: Fri, 2 Oct 2026 13:58:13 +0200 Subject: [PATCH] fix(curl): compare complete top level domain against allowdUrlDomain --- internal/cmd/curl/curl_test.go | 9 ++++ internal/pkg/utils/utils.go | 7 ++- internal/pkg/utils/utils_test.go | 78 ++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/internal/cmd/curl/curl_test.go b/internal/cmd/curl/curl_test.go index 405f8cafc..9ec956904 100644 --- a/internal/cmd/curl/curl_test.go +++ b/internal/cmd/curl/curl_test.go @@ -139,6 +139,15 @@ func TestParseInput(t *testing.T) { model.URL = "https://www.example.website.com/" }), }, + { + description: "hostname suffix without domain boundary", + argValues: []string{ + "https://suspiciousstackit.cloud/", + }, + flagValues: fixtureFlagValues(), + allowedURLDomain: "stackit.cloud", + isValid: false, + }, { description: "invalid method 1", argValues: fixtureArgValues(), diff --git a/internal/pkg/utils/utils.go b/internal/pkg/utils/utils.go index 37c1a5c06..504f37193 100644 --- a/internal/pkg/utils/utils.go +++ b/internal/pkg/utils/utils.go @@ -108,8 +108,13 @@ func ValidateURLDomain(value string) error { } allowedUrlDomain := viper.GetString(config.AllowedUrlDomainKey) + if allowedUrlDomain == "" { + return nil + } - if !strings.HasSuffix(urlHost, allowedUrlDomain) { + urlHost = strings.ToLower(urlHost) + allowedUrlDomain = strings.ToLower(allowedUrlDomain) + if urlHost != allowedUrlDomain && !strings.HasSuffix(urlHost, "."+allowedUrlDomain) { return fmt.Errorf(`only urls belonging to domain %s are allowed`, allowedUrlDomain) } return nil diff --git a/internal/pkg/utils/utils_test.go b/internal/pkg/utils/utils_test.go index 9a1af376c..74f54e185 100644 --- a/internal/pkg/utils/utils_test.go +++ b/internal/pkg/utils/utils_test.go @@ -119,6 +119,60 @@ func TestValidateURLDomain(t *testing.T) { input: "https://example.stackit.cloud", isValid: true, }, + { + name: "apex domain", + allowedUrlDomain: "stackit.cloud", + input: "https://stackit.cloud/path", + isValid: true, + }, + { + name: "multiple subdomains", + allowedUrlDomain: "stackit.cloud", + input: "https://dns.api.stackit.cloud/v1", + isValid: true, + }, + { + name: "hostname suffix without label boundary", + allowedUrlDomain: "stackit.cloud", + input: "https://suspiciousstackit.cloud", + isValid: false, + }, + { + name: "lookalike subdomain", + allowedUrlDomain: "stackit.cloud", + input: "https://api.suspiciousstackit.cloud", + isValid: false, + }, + { + name: "domain followed by extra labels", + allowedUrlDomain: "stackit.cloud", + input: "https://api.stackit.cloud.evil.example", + isValid: false, + }, + { + name: "port is not hostname", + allowedUrlDomain: "stackit.cloud", + input: "https://stackit.cloud:443/v1", + isValid: true, + }, + { + name: "userinfo does not affect hostname", + allowedUrlDomain: "stackit.cloud", + input: "https://stackit.cloud@evil.example/path", + isValid: false, + }, + { + name: "path does not affect hostname", + allowedUrlDomain: "stackit.cloud", + input: "https://evil.example/path/stackit.cloud", + isValid: false, + }, + { + name: "hostname is case insensitive", + allowedUrlDomain: "stackit.cloud", + input: "https://API.STACKIT.CLOUD/path", + isValid: true, + }, { name: "STACKIT URL invalid", allowedUrlDomain: "example.com", @@ -137,6 +191,12 @@ func TestValidateURLDomain(t *testing.T) { input: "https://www.test.example.com/", isValid: true, }, + { + name: "custom domain boundary rejected", + allowedUrlDomain: "example.com", + input: "https://badexample.com", + isValid: false, + }, { name: "every URL valid", allowedUrlDomain: "", @@ -153,6 +213,24 @@ func TestValidateURLDomain(t *testing.T) { input: "http://example.stackit.cloud", isValid: false, }, + { + name: "invalid protocol with allowed domain", + allowedUrlDomain: "stackit.cloud", + input: "http://api.stackit.cloud", + isValid: false, + }, + { + name: "missing host", + allowedUrlDomain: "stackit.cloud", + input: "https:///path", + isValid: false, + }, + { + name: "malformed URL", + allowedUrlDomain: "stackit.cloud", + input: "https://%zz", + isValid: false, + }, { name: "no protocol", input: "example.stackit.cloud",