From eae67315953f5526166ebacb0a5698450481c3c3 Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Tue, 13 Jan 2026 14:49:48 +0100 Subject: [PATCH 1/7] fix: validate API URL hostname --- internal/api/urls.go | 8 +++ internal/api/urls_test.go | 52 +++++++++++++++++++ pkg/networking/middleware/auth_header.go | 4 ++ pkg/networking/middleware/auth_header_test.go | 42 +++++++++++++++ 4 files changed, 106 insertions(+) diff --git a/internal/api/urls.go b/internal/api/urls.go index a239634a8..b11bb2fe2 100644 --- a/internal/api/urls.go +++ b/internal/api/urls.go @@ -1,6 +1,7 @@ package api import ( + "fmt" "net" "net/url" "regexp" @@ -52,10 +53,17 @@ func GetCanonicalApiUrlFromString(userDefinedUrl string) (string, error) { return GetCanonicalApiUrl(*url) } +func IsSnykHostname(hostname string) bool { + return hostname == "snyk.io" || hostname == "snykgov.io" || + strings.HasSuffix(hostname, ".snyk.io") || strings.HasSuffix(hostname, ".snykgov.io") +} + func GetCanonicalApiAsUrl(url url.URL) (url.URL, error) { // for localhost we don't change the host, since there are no subdomains if isImmutableHost(url.Host) { url.Path = strings.Replace(url.Path, "/v1", "", 1) + } else if !IsSnykHostname(url.Hostname()) { + return url, fmt.Errorf("host name is invalid") } else { url.Host = appRegexp.ReplaceAllString(url.Host, apiPrefixDot) diff --git a/internal/api/urls_test.go b/internal/api/urls_test.go index 5eafbcfb7..a3717d883 100644 --- a/internal/api/urls_test.go +++ b/internal/api/urls_test.go @@ -100,3 +100,55 @@ func Test_isImmutableHost(t *testing.T) { assert.False(t, isImmutableHost(host), host) } } + +func Test_IsSnykHostname(t *testing.T) { + cases := []struct { + hostname string + expected bool + }{ + // Valid hostnames + {"snyk.io", true}, + {"snykgov.io", true}, + {"api.snyk.io", true}, + {"api.snykgov.io", true}, + {"app.au.snyk.io", true}, + {"deeproxy.eu.snyk.io", true}, + {"foobar.my.snyk.io", true}, + {"deeproxy.snykgov.io", true}, + + // Invalid hostnames + {"api-snyk.io", false}, + {"staging-snyk.io", false}, + {"eu-snyk.io", false}, + {"example.com", false}, + {"snyk.io.evil.com", false}, + {"fakesnyk.io", false}, + {"notsnykgov.io", false}, + {"snykgov.io.attacker.com", false}, + } + + for _, tc := range cases { + t.Run(tc.hostname, func(t *testing.T) { + actual := IsSnykHostname(tc.hostname) + assert.Equal(t, tc.expected, actual) + }) + } +} + +func Test_GetCanonicalApiAsUrl_InvalidHostname(t *testing.T) { + invalidUrls := []string{ + "https://api-snyk.io", + "https://api.staging-snyk.io", + "https://api.eu-snyk.io", + "https://example.com", + "https://snyk.io.evil.com", + } + + for _, u := range invalidUrls { + t.Run(u, func(t *testing.T) { + _, err := GetCanonicalApiUrlFromString(u) + assert.Error(t, err) + assert.Contains(t, err.Error(), "host name is invalid") + }) + } +} diff --git a/pkg/networking/middleware/auth_header.go b/pkg/networking/middleware/auth_header.go index 25c39cc4d..fe85e6d70 100644 --- a/pkg/networking/middleware/auth_header.go +++ b/pkg/networking/middleware/auth_header.go @@ -51,6 +51,10 @@ func ShouldRequireAuthentication( additionalSubdomains []string, additionalUrls []string, ) (matchesPattern bool, err error) { + if !api.IsSnykHostname(url.Hostname()) { + return false, fmt.Errorf("host name is invalid") + } + subdomainsToCheck := append([]string{""}, additionalSubdomains...) for _, subdomain := range subdomainsToCheck { var matchesPattern bool diff --git a/pkg/networking/middleware/auth_header_test.go b/pkg/networking/middleware/auth_header_test.go index 3c85b92d7..74413d4b5 100644 --- a/pkg/networking/middleware/auth_header_test.go +++ b/pkg/networking/middleware/auth_header_test.go @@ -106,6 +106,48 @@ func Test_AddAuthenticationHeader(t *testing.T) { assert.NoError(t, err) } +func Test_isSnykHostname(t *testing.T) { + cases := []struct { + url string + isValid bool + }{ + // Valid hostnames + {"https://foobar.my.snyk.io", true}, + {"https://api.snyk.io", true}, + {"https://snyk.io", true}, + {"https://snykgov.io", true}, + {"https://api.snykgov.io", true}, + {"https://app.au.snyk.io", true}, + {"https://deeproxy.eu.snyk.io", true}, + {"https://deeproxy.snykgov.io", true}, + + // Invalid hostnames + {"https://api-snyk.io", false}, + {"https://api.staging-snyk.io", false}, + {"https://api.eu-snyk.io", false}, + {"https://example.com", false}, + {"https://snyk.io.evil.com", false}, + {"https://fakesnyk.io", false}, + {"https://notsnykgov.io", false}, + {"https://snykgov.io.attacker.com", false}, + } + + apiUrl := "https://api.snyk.io" + for _, tc := range cases { + t.Run(tc.url, func(t *testing.T) { + requestUrl, err := url.Parse(tc.url) + assert.NoError(t, err) + _, err = middleware.ShouldRequireAuthentication(apiUrl, requestUrl, []string{}, []string{}) + if tc.isValid { + assert.NoError(t, err) + } else { + assert.Error(t, err) + assert.Contains(t, err.Error(), "host name is invalid") + } + }) + } +} + func TestAuthenticationError_Is(t *testing.T) { ctrl := gomock.NewController(t) config := configuration.New() From 7bde1c27dd44773abc35a263e742cce03f259796 Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Tue, 13 Jan 2026 15:19:54 +0100 Subject: [PATCH 2/7] fix: move validation out of GetCanonicalApiAsUrl --- internal/api/urls.go | 3 --- pkg/app/app.go | 6 ++++++ pkg/networking/middleware/auth_header.go | 5 +++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/internal/api/urls.go b/internal/api/urls.go index b11bb2fe2..60c74e00f 100644 --- a/internal/api/urls.go +++ b/internal/api/urls.go @@ -1,7 +1,6 @@ package api import ( - "fmt" "net" "net/url" "regexp" @@ -62,8 +61,6 @@ func GetCanonicalApiAsUrl(url url.URL) (url.URL, error) { // for localhost we don't change the host, since there are no subdomains if isImmutableHost(url.Host) { url.Path = strings.Replace(url.Path, "/v1", "", 1) - } else if !IsSnykHostname(url.Hostname()) { - return url, fmt.Errorf("host name is invalid") } else { url.Host = appRegexp.ReplaceAllString(url.Host, apiPrefixDot) diff --git a/pkg/app/app.go b/pkg/app/app.go index f0986a700..1b16073da 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -2,6 +2,7 @@ package app import ( "crypto/fips140" + "fmt" "io" "log" "net/http" @@ -137,6 +138,11 @@ func defaultFuncApiUrl(globalConfig configuration.Configuration, logger *zerolog if err != nil { logger.Warn().Err(err).Str(configuration.API_URL, urlString).Msg("failed to get api url") } + if !api.IsSnykHostname(apiString) { + hostNameErr := fmt.Errorf("host name is not snyk.io or snykgov.io") + logger.Err(hostNameErr).Msg("host name is not snyk.io or snykgov.io") + return nil, fmt.Errorf("host name is not snyk.io or snykgov.io") + } return apiString, nil } return callback diff --git a/pkg/networking/middleware/auth_header.go b/pkg/networking/middleware/auth_header.go index fe85e6d70..3841aa5e3 100644 --- a/pkg/networking/middleware/auth_header.go +++ b/pkg/networking/middleware/auth_header.go @@ -45,6 +45,11 @@ func (n *AuthHeaderMiddleware) RoundTrip(request *http.Request) (*http.Response, return n.next.RoundTrip(newRequest) } +// ShouldRequireAuthentication checks if a request requires authentication. +// apiUrl is the configured API URL. +// url is the URL of the request. +// additionalSubdomains is a list of additional subdomains to check. +// additionalUrls is a list of additional URLs to check. func ShouldRequireAuthentication( apiUrl string, url *url.URL, From 3587200fd2af723362e87ae1e0314ca9daaa79ba Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Fri, 16 Jan 2026 14:59:15 +0100 Subject: [PATCH 3/7] fix: tests --- internal/api/urls.go | 13 +++-- internal/api/urls_test.go | 52 ++++++------------- pkg/app/app.go | 2 +- pkg/app/app_test.go | 40 +++++++------- pkg/networking/middleware/auth_header.go | 4 +- pkg/networking/middleware/auth_header_test.go | 9 +--- pkg/networking/networking_test.go | 2 +- 7 files changed, 53 insertions(+), 69 deletions(-) diff --git a/internal/api/urls.go b/internal/api/urls.go index 60c74e00f..c684cc9d8 100644 --- a/internal/api/urls.go +++ b/internal/api/urls.go @@ -23,6 +23,7 @@ var ( func isImmutableHost(host string) bool { knownHostNames := map[string]bool{ "localhost": true, + "127.0.0.1": true, "stella": true, } @@ -52,9 +53,15 @@ func GetCanonicalApiUrlFromString(userDefinedUrl string) (string, error) { return GetCanonicalApiUrl(*url) } -func IsSnykHostname(hostname string) bool { - return hostname == "snyk.io" || hostname == "snykgov.io" || - strings.HasSuffix(hostname, ".snyk.io") || strings.HasSuffix(hostname, ".snykgov.io") +func IsTrustedSnykHost(apiUrl string) bool { + parsedUrl, err := url.Parse(apiUrl) + if err != nil { + return false + } + hostname := parsedUrl.Hostname() + return parsedUrl.Hostname() == "snyk.io" || hostname == "snykgov.io" || + strings.HasSuffix(hostname, ".snyk.io") || strings.HasSuffix(hostname, ".snykgov.io") || + isImmutableHost(hostname) } func GetCanonicalApiAsUrl(url url.URL) (url.URL, error) { diff --git a/internal/api/urls_test.go b/internal/api/urls_test.go index a3717d883..6dff277f0 100644 --- a/internal/api/urls_test.go +++ b/internal/api/urls_test.go @@ -107,48 +107,30 @@ func Test_IsSnykHostname(t *testing.T) { expected bool }{ // Valid hostnames - {"snyk.io", true}, - {"snykgov.io", true}, - {"api.snyk.io", true}, - {"api.snykgov.io", true}, - {"app.au.snyk.io", true}, - {"deeproxy.eu.snyk.io", true}, - {"foobar.my.snyk.io", true}, - {"deeproxy.snykgov.io", true}, + {"https://snyk.io", true}, + {"https://snykgov.io", true}, + {"https://api.snyk.io", true}, + {"https://api.snykgov.io", true}, + {"https://app.au.snyk.io", true}, + {"https://deeproxy.eu.snyk.io", true}, + {"https://foobar.my.snyk.io", true}, + {"https://deeproxy.snykgov.io", true}, // Invalid hostnames - {"api-snyk.io", false}, - {"staging-snyk.io", false}, - {"eu-snyk.io", false}, - {"example.com", false}, - {"snyk.io.evil.com", false}, - {"fakesnyk.io", false}, - {"notsnykgov.io", false}, - {"snykgov.io.attacker.com", false}, + {"https://api-snyk.io", false}, + {"https://staging-snyk.io", false}, + {"https://eu-snyk.io", false}, + {"https://example.com", false}, + {"https://snyk.io.evil.com", false}, + {"https://fakesnyk.io", false}, + {"https://notsnykgov.io", false}, + {"https://snykgov.io.attacker.com", false}, } for _, tc := range cases { t.Run(tc.hostname, func(t *testing.T) { - actual := IsSnykHostname(tc.hostname) + actual := IsTrustedSnykHost(tc.hostname) assert.Equal(t, tc.expected, actual) }) } } - -func Test_GetCanonicalApiAsUrl_InvalidHostname(t *testing.T) { - invalidUrls := []string{ - "https://api-snyk.io", - "https://api.staging-snyk.io", - "https://api.eu-snyk.io", - "https://example.com", - "https://snyk.io.evil.com", - } - - for _, u := range invalidUrls { - t.Run(u, func(t *testing.T) { - _, err := GetCanonicalApiUrlFromString(u) - assert.Error(t, err) - assert.Contains(t, err.Error(), "host name is invalid") - }) - } -} diff --git a/pkg/app/app.go b/pkg/app/app.go index 1b16073da..37df88cd1 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -138,7 +138,7 @@ func defaultFuncApiUrl(globalConfig configuration.Configuration, logger *zerolog if err != nil { logger.Warn().Err(err).Str(configuration.API_URL, urlString).Msg("failed to get api url") } - if !api.IsSnykHostname(apiString) { + if !api.IsTrustedSnykHost(apiString) { hostNameErr := fmt.Errorf("host name is not snyk.io or snykgov.io") logger.Err(hostNameErr).Msg("host name is not snyk.io or snykgov.io") return nil, fmt.Errorf("host name is not snyk.io or snykgov.io") diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 4749014ed..589897a36 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -115,7 +115,7 @@ func Test_CreateAppEngine_config_replaceV1inApi(t *testing.T) { config := engine.GetConfiguration() - expectApiUrl := "https://api.somehost:2134" + expectApiUrl := "https://api.snyk.io:2134" config.Set(configuration.API_URL, expectApiUrl+"/v1") actualApiUrl := config.GetString(configuration.API_URL) @@ -148,22 +148,22 @@ func Test_EnsureAuthConfigurationPrecedence(t *testing.T) { name: "only user-defined API URL is defined, use that", patPayload: "", oauthJWTPayload: "", - userDefinedApiUrl: "https://api.user", - expectedURL: "https://api.user", + userDefinedApiUrl: "https://api.snyk.io", + expectedURL: "https://api.snyk.io", }, { name: "with a broken PAT configured and a user-defined API URL, user-defined API URL should take precedence", patPayload: `{broken`, oauthJWTPayload: "", - userDefinedApiUrl: "https://api.user", - expectedURL: "https://api.user", + expectedURL: "https://api.snyk.io", + userDefinedApiUrl: "https://api.snyk.io", }, { name: "with an empty PAT configured and a user-defined API URL, user-defined API URL should take precedence", patPayload: `{}`, oauthJWTPayload: "", - userDefinedApiUrl: "https://api.user", - expectedURL: "https://api.user", + expectedURL: "https://api.snyk.io", + userDefinedApiUrl: "https://api.snyk.io", }, { name: "with a PAT configured and a user-defined API URL, PAT host should take precedence", @@ -176,22 +176,22 @@ func Test_EnsureAuthConfigurationPrecedence(t *testing.T) { name: "with a broken OAuth with no host configured and a user-defined API URL, user-defined API URL should take precedence", patPayload: "", oauthJWTPayload: `{broken`, - userDefinedApiUrl: "https://api.user", - expectedURL: "https://api.user", + expectedURL: "https://api.snyk.io", + userDefinedApiUrl: "https://api.snyk.io", }, { name: "with OAuth with no host configured and a user-defined API URL, user-defined API URL should take precedence", patPayload: "", oauthJWTPayload: `{"sub":"1234567890","name":"John Doe","iat":1516239022,"aud":[]}`, - userDefinedApiUrl: "https://api.user", - expectedURL: "https://api.user", + expectedURL: "https://api.snyk.io", + userDefinedApiUrl: "https://api.snyk.io", }, { name: "with OAuth configured and a user-defined API URL, OAuth audience should take precedence", patPayload: "", - oauthJWTPayload: `{"sub":"1234567890","name":"John Doe","iat":1516239022,"aud":["https://api.oauth"]}`, - userDefinedApiUrl: "https://api.user", - expectedURL: "https://api.oauth", + oauthJWTPayload: `{"sub":"1234567890","name":"John Doe","iat":1516239022,"aud":["https://api.eu.snyk.io"]}`, + userDefinedApiUrl: "https://api.snyk.io", + expectedURL: "https://api.eu.snyk.io", }, { name: "with only PAT configured, use PAT host", @@ -203,9 +203,9 @@ func Test_EnsureAuthConfigurationPrecedence(t *testing.T) { { name: "with only OAuth configured, use OAuth audience", patPayload: "", - oauthJWTPayload: `{"sub":"1234567890","name":"John Doe","iat":1516239022,"aud":["https://api.oauth"]}`, + oauthJWTPayload: `{"sub":"1234567890","name":"John Doe","iat":1516239022,"aud":["https://api.snyk.io"]}`, userDefinedApiUrl: "", - expectedURL: "https://api.oauth", + expectedURL: "https://api.snyk.io", }, // This is not a likely scenario, as you cannot define both at the same time. However, it will potentially // catch regressions if this test starts to fail. @@ -307,13 +307,13 @@ func Test_CreateAppEngine_config_OauthAudHasPrecedence(t *testing.T) { config := configuration.New() config.Set(auth.CONFIG_KEY_OAUTH_TOKEN, // JWT generated at https://jwt.io with claim: - // "aud": ["https://api.example.com"] - `{"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJhdWQiOlsiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20iXX0.hWq0fKukObQSkphAdyEC7-m4jXIb4VdWyQySmmgy0GU"}`, + // "aud": ["https://api.snyk.io"] + `{"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJhdWQiOlsiaHR0cHM6Ly9hcGkuc255ay5pbyJdfQ.vww25T4UtkxEzQzTysDI5zSi9XOYmXC5CXgxfp6mWtA"}`, ) logger := log.New(os.Stderr, "", 0) t.Run("Audience claim takes precedence of configured value", func(t *testing.T) { - expectedApiUrl := "https://api.example.com" + expectedApiUrl := "https://api.snyk.io" localConfig := config.Clone() localConfig.Set(configuration.API_URL, "https://api.dev.snyk.io") @@ -325,7 +325,7 @@ func Test_CreateAppEngine_config_OauthAudHasPrecedence(t *testing.T) { }) t.Run("nothing configured", func(t *testing.T) { - expectedApiUrl := "https://api.example.com" + expectedApiUrl := "https://api.snyk.io" localConfig := config.Clone() engine := CreateAppEngineWithOptions(WithConfiguration(localConfig), WithLogger(logger)) diff --git a/pkg/networking/middleware/auth_header.go b/pkg/networking/middleware/auth_header.go index 3841aa5e3..4badb7054 100644 --- a/pkg/networking/middleware/auth_header.go +++ b/pkg/networking/middleware/auth_header.go @@ -56,8 +56,8 @@ func ShouldRequireAuthentication( additionalSubdomains []string, additionalUrls []string, ) (matchesPattern bool, err error) { - if !api.IsSnykHostname(url.Hostname()) { - return false, fmt.Errorf("host name is invalid") + if !api.IsTrustedSnykHost(url.String()) { + return false, nil } subdomainsToCheck := append([]string{""}, additionalSubdomains...) diff --git a/pkg/networking/middleware/auth_header_test.go b/pkg/networking/middleware/auth_header_test.go index 74413d4b5..a011116ee 100644 --- a/pkg/networking/middleware/auth_header_test.go +++ b/pkg/networking/middleware/auth_header_test.go @@ -46,7 +46,7 @@ func Test_ShouldRequireAuthentication_subdomains(t *testing.T) { "https://mydomain.eu.snyk.io:443": true, "https://whatever.eu.snyk.io": false, "https://deeproxy.eu.snyk.io": true, - "https://somethingelse.com/": true, + "https://somethingelse.com/": false, "https://definitelynot.com/": false, } @@ -138,12 +138,7 @@ func Test_isSnykHostname(t *testing.T) { requestUrl, err := url.Parse(tc.url) assert.NoError(t, err) _, err = middleware.ShouldRequireAuthentication(apiUrl, requestUrl, []string{}, []string{}) - if tc.isValid { - assert.NoError(t, err) - } else { - assert.Error(t, err) - assert.Contains(t, err.Error(), "host name is invalid") - } + assert.NoError(t, err) }) } } diff --git a/pkg/networking/networking_test.go b/pkg/networking/networking_test.go index 1970f3e7c..ae696b3e9 100644 --- a/pkg/networking/networking_test.go +++ b/pkg/networking/networking_test.go @@ -457,7 +457,7 @@ func TestNetworkImpl_Clone(t *testing.T) { clonedNetwork := network.Clone() clonedNetwork.SetConfiguration(config2) - url1, err := url.Parse("") + url1, err := url.Parse("https://api.snyk.io") assert.NoError(t, err) req1 := &http.Request{ Header: http.Header{}, From f29838bd1a67f2f48a4e854dde707f21b388c61f Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Mon, 19 Jan 2026 17:45:17 +0100 Subject: [PATCH 4/7] fix: use regex to validate host --- internal/api/urls.go | 47 +++++++++-------- internal/api/urls_test.go | 50 ++++++++----------- pkg/app/app.go | 3 +- pkg/app/app_test.go | 2 +- pkg/auth/authHost.go | 8 ++- .../code_workflow/native_workflow_test.go | 3 ++ pkg/local_workflows/code_workflow_test.go | 3 ++ .../connectivity_check_workflow_test.go | 3 ++ .../network_utils/snyk_request_id_test.go | 4 ++ pkg/networking/middleware/auth_header.go | 16 ++++-- pkg/networking/middleware/auth_header_test.go | 8 +-- pkg/networking/networking_test.go | 4 +- 12 files changed, 87 insertions(+), 64 deletions(-) diff --git a/internal/api/urls.go b/internal/api/urls.go index c684cc9d8..7bbab9fa1 100644 --- a/internal/api/urls.go +++ b/internal/api/urls.go @@ -20,7 +20,30 @@ var ( appRegexp = regexp.MustCompile(appPattern) ) -func isImmutableHost(host string) bool { +func IsImmutableHost(host string) bool { + if IsKnownHostName(host) { + return true + } + + // ipv6 hosts must start with "[" + if strings.HasPrefix(host, "[") { + return true + } + portlessHost := strings.Split(host, ":")[0] + + _, _, err := net.ParseCIDR(portlessHost + "/24") + return err == nil +} + +func IsKnownHostName(host string) bool { + if strings.HasPrefix(host, "http") { + parsedUrl, err := url.Parse(host) + if err != nil { + return false + } + host = parsedUrl.Host + } + knownHostNames := map[string]bool{ "localhost": true, "127.0.0.1": true, @@ -33,14 +56,7 @@ func isImmutableHost(host string) bool { if knownHostNames[portlessHost] { return true } - - // ipv6 hosts must start with "[" - if strings.HasPrefix(host, "[") { - return true - } - - _, _, err := net.ParseCIDR(portlessHost + "/24") - return err == nil + return false } func GetCanonicalApiUrlFromString(userDefinedUrl string) (string, error) { @@ -53,20 +69,9 @@ func GetCanonicalApiUrlFromString(userDefinedUrl string) (string, error) { return GetCanonicalApiUrl(*url) } -func IsTrustedSnykHost(apiUrl string) bool { - parsedUrl, err := url.Parse(apiUrl) - if err != nil { - return false - } - hostname := parsedUrl.Hostname() - return parsedUrl.Hostname() == "snyk.io" || hostname == "snykgov.io" || - strings.HasSuffix(hostname, ".snyk.io") || strings.HasSuffix(hostname, ".snykgov.io") || - isImmutableHost(hostname) -} - func GetCanonicalApiAsUrl(url url.URL) (url.URL, error) { // for localhost we don't change the host, since there are no subdomains - if isImmutableHost(url.Host) { + if IsImmutableHost(url.Host) { url.Path = strings.Replace(url.Path, "/v1", "", 1) } else { url.Host = appRegexp.ReplaceAllString(url.Host, apiPrefixDot) diff --git a/internal/api/urls_test.go b/internal/api/urls_test.go index 6dff277f0..8b132cb89 100644 --- a/internal/api/urls_test.go +++ b/internal/api/urls_test.go @@ -93,44 +93,36 @@ func Test_isImmutableHost(t *testing.T) { hostlistNonLocalhost := []string{"snyk.io"} for _, host := range hostlistLocalhost { - assert.True(t, isImmutableHost(host)) + assert.True(t, IsImmutableHost(host)) } for _, host := range hostlistNonLocalhost { - assert.False(t, isImmutableHost(host), host) + assert.False(t, IsImmutableHost(host), host) } } -func Test_IsSnykHostname(t *testing.T) { - cases := []struct { - hostname string +func Test_IsKnownHostName(t *testing.T) { + testCases := []struct { + host string expected bool }{ - // Valid hostnames - {"https://snyk.io", true}, - {"https://snykgov.io", true}, - {"https://api.snyk.io", true}, - {"https://api.snykgov.io", true}, - {"https://app.au.snyk.io", true}, - {"https://deeproxy.eu.snyk.io", true}, - {"https://foobar.my.snyk.io", true}, - {"https://deeproxy.snykgov.io", true}, - - // Invalid hostnames - {"https://api-snyk.io", false}, - {"https://staging-snyk.io", false}, - {"https://eu-snyk.io", false}, - {"https://example.com", false}, - {"https://snyk.io.evil.com", false}, - {"https://fakesnyk.io", false}, - {"https://notsnykgov.io", false}, - {"https://snykgov.io.attacker.com", false}, + {"localhost", true}, + {"localhost:8080", true}, + {"127.0.0.1", true}, + {"127.0.0.1:9000", true}, + {"stella", true}, + {"stella:8000", true}, + {"http://localhost", true}, + {"http://localhost:8080", true}, + {"https://127.0.0.1:9000", true}, + {"http://stella:8000", true}, + {"192.168.1.1", false}, + {"example.com", false}, + {"http://example.com", false}, } - for _, tc := range cases { - t.Run(tc.hostname, func(t *testing.T) { - actual := IsTrustedSnykHost(tc.hostname) - assert.Equal(t, tc.expected, actual) - }) + for _, tc := range testCases { + actual := IsKnownHostName(tc.host) + assert.Equal(t, tc.expected, actual, "IsKnownHostName(%q) = %v, want %v", tc.host, actual, tc.expected) } } diff --git a/pkg/app/app.go b/pkg/app/app.go index 37df88cd1..ee3b66b9f 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -138,7 +138,8 @@ func defaultFuncApiUrl(globalConfig configuration.Configuration, logger *zerolog if err != nil { logger.Warn().Err(err).Str(configuration.API_URL, urlString).Msg("failed to get api url") } - if !api.IsTrustedSnykHost(apiString) { + + if isValid, validationErr := auth.IsValidAuthHost(apiString, config.GetString(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP)); !isValid || validationErr != nil { hostNameErr := fmt.Errorf("host name is not snyk.io or snykgov.io") logger.Err(hostNameErr).Msg("host name is not snyk.io or snykgov.io") return nil, fmt.Errorf("host name is not snyk.io or snykgov.io") diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 589897a36..ad5ee67fa 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -115,7 +115,7 @@ func Test_CreateAppEngine_config_replaceV1inApi(t *testing.T) { config := engine.GetConfiguration() - expectApiUrl := "https://api.snyk.io:2134" + expectApiUrl := "https://api.snyk.io" config.Set(configuration.API_URL, expectApiUrl+"/v1") actualApiUrl := config.GetString(configuration.API_URL) diff --git a/pkg/auth/authHost.go b/pkg/auth/authHost.go index 13fe5cd96..80a3dc43e 100644 --- a/pkg/auth/authHost.go +++ b/pkg/auth/authHost.go @@ -27,8 +27,12 @@ func redirectAuthHost(instance string) (string, error) { return canonicalizedInstanceUrl.Host, nil } -func IsValidAuthHost(instance string, redirectAuthHostRE string) (bool, error) { - isValidHost, err := utils.MatchesRegex(instance, redirectAuthHostRE) +func IsValidAuthHost(instance string, authHostRegex string) (bool, error) { + if api.IsKnownHostName(instance) { + return true, nil + } + + isValidHost, err := utils.MatchesRegex(instance, authHostRegex) if err != nil { return false, err } diff --git a/pkg/local_workflows/code_workflow/native_workflow_test.go b/pkg/local_workflows/code_workflow/native_workflow_test.go index 91d73f3a4..8754ea333 100644 --- a/pkg/local_workflows/code_workflow/native_workflow_test.go +++ b/pkg/local_workflows/code_workflow/native_workflow_test.go @@ -9,6 +9,8 @@ import ( "testing" "github.com/rs/zerolog" + "github.com/snyk/go-application-framework/internal/constants" + "github.com/snyk/go-application-framework/pkg/auth" "github.com/stretchr/testify/assert" "github.com/snyk/go-application-framework/pkg/configuration" @@ -85,6 +87,7 @@ func Test_TrackUsage(t *testing.T) { config := configuration.NewWithOpts() config.Set(configuration.ORGANIZATION, org) config.Set(configuration.API_URL, server.URL) + config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) networkAccess := networking.NewNetworkAccess(config) // call method under test diff --git a/pkg/local_workflows/code_workflow_test.go b/pkg/local_workflows/code_workflow_test.go index 524e6d2c6..5e120fb4b 100644 --- a/pkg/local_workflows/code_workflow_test.go +++ b/pkg/local_workflows/code_workflow_test.go @@ -17,6 +17,8 @@ import ( "github.com/snyk/code-client-go/sarif" "github.com/snyk/code-client-go/scan" "github.com/snyk/error-catalog-golang-public/code" + "github.com/snyk/go-application-framework/internal/constants" + "github.com/snyk/go-application-framework/pkg/auth" "github.com/spf13/pflag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -77,6 +79,7 @@ func Test_Code_entrypoint(t *testing.T) { config := configuration.NewWithOpts() config.Set(configuration.API_URL, server.URL) config.Set(configuration.ORGANIZATION, org) + config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) engine := workflow.NewWorkFlowEngine(config) diff --git a/pkg/local_workflows/connectivity_check_extension/connectivity_check_workflow_test.go b/pkg/local_workflows/connectivity_check_extension/connectivity_check_workflow_test.go index 4c9de3e75..bfde9ee8d 100644 --- a/pkg/local_workflows/connectivity_check_extension/connectivity_check_workflow_test.go +++ b/pkg/local_workflows/connectivity_check_extension/connectivity_check_workflow_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/rs/zerolog" + "github.com/snyk/go-application-framework/internal/constants" + "github.com/snyk/go-application-framework/pkg/auth" "github.com/snyk/go-application-framework/pkg/configuration" "github.com/snyk/go-application-framework/pkg/local_workflows/connectivity_check_extension/connectivity" "github.com/snyk/go-application-framework/pkg/networking" @@ -84,6 +86,7 @@ func TestJSONOutputWithOrganizations(t *testing.T) { config.Set("insecure", true) config.Set(configuration.AUTHENTICATION_TOKEN, "test-token") config.Set(configuration.API_URL, server.URL) + config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) config.Set("json", true) // Request JSON output // Create NetworkAccess diff --git a/pkg/local_workflows/network_utils/snyk_request_id_test.go b/pkg/local_workflows/network_utils/snyk_request_id_test.go index 06f814cee..1f2c13edc 100644 --- a/pkg/local_workflows/network_utils/snyk_request_id_test.go +++ b/pkg/local_workflows/network_utils/snyk_request_id_test.go @@ -4,6 +4,8 @@ import ( "net/http" "testing" + "github.com/snyk/go-application-framework/internal/constants" + "github.com/snyk/go-application-framework/pkg/auth" "github.com/stretchr/testify/assert" "github.com/snyk/go-application-framework/pkg/configuration" @@ -13,6 +15,7 @@ import ( func Test_AddRequestId(t *testing.T) { t.Run("Add missing snyk-request-id", func(t *testing.T) { config := configuration.NewInMemory() + config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) net := networking.NewNetworkAccess(config) // use method under test @@ -31,6 +34,7 @@ func Test_AddRequestId(t *testing.T) { t.Run("Do not override snyk-request-id", func(t *testing.T) { config := configuration.NewInMemory() net := networking.NewNetworkAccess(config) + config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) expectedValue := "pre-existing-id" // use method under test diff --git a/pkg/networking/middleware/auth_header.go b/pkg/networking/middleware/auth_header.go index 4badb7054..8c10cd001 100644 --- a/pkg/networking/middleware/auth_header.go +++ b/pkg/networking/middleware/auth_header.go @@ -56,10 +56,6 @@ func ShouldRequireAuthentication( additionalSubdomains []string, additionalUrls []string, ) (matchesPattern bool, err error) { - if !api.IsTrustedSnykHost(url.String()) { - return false, nil - } - subdomainsToCheck := append([]string{""}, additionalSubdomains...) for _, subdomain := range subdomainsToCheck { var matchesPattern bool @@ -110,8 +106,18 @@ func AddAuthenticationHeader( apiUrl := config.GetString(configuration.API_URL) additionalSubdomains := config.GetStringSlice(configuration.AUTHENTICATION_SUBDOMAINS) additionalUrls := config.GetStringSlice(configuration.AUTHENTICATION_ADDITIONAL_URLS) - isSnykApi, err := ShouldRequireAuthentication(apiUrl, request.URL, additionalSubdomains, additionalUrls) + hostNameRegex := config.GetString(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP) + + canonicalHostname, err := api.GetCanonicalApiUrl(*request.URL) + if err != nil { + return errors.Join(err, ErrAuthenticationFailed) + } + isValid, err := auth.IsValidAuthHost(canonicalHostname, hostNameRegex) + if !isValid || err != nil { + return errors.Join(err, ErrAuthenticationFailed) + } + isSnykApi, err := ShouldRequireAuthentication(apiUrl, request.URL, additionalSubdomains, additionalUrls) // requests to the api automatically get an authentication token attached if !isSnykApi { return err diff --git a/pkg/networking/middleware/auth_header_test.go b/pkg/networking/middleware/auth_header_test.go index a011116ee..503e49308 100644 --- a/pkg/networking/middleware/auth_header_test.go +++ b/pkg/networking/middleware/auth_header_test.go @@ -1,12 +1,13 @@ package middleware_test import ( - "fmt" "net/http" "net/url" "testing" "github.com/golang/mock/gomock" + "github.com/snyk/go-application-framework/internal/constants" + "github.com/snyk/go-application-framework/pkg/auth" "github.com/stretchr/testify/assert" "github.com/snyk/go-application-framework/internal/api" @@ -46,7 +47,7 @@ func Test_ShouldRequireAuthentication_subdomains(t *testing.T) { "https://mydomain.eu.snyk.io:443": true, "https://whatever.eu.snyk.io": false, "https://deeproxy.eu.snyk.io": true, - "https://somethingelse.com/": false, + "https://somethingelse.com/": true, "https://definitelynot.com/": false, } @@ -70,6 +71,7 @@ func Test_AddAuthenticationHeader(t *testing.T) { config := configuration.New() config.Set(configuration.API_URL, "https://api.snyk.io") config.Set(configuration.AUTHENTICATION_SUBDOMAINS, []string{"deeproxy"}) + config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) // case: headers added (api) url, err := url.Parse("https://app.snyk.io/rest/endpoint1") @@ -155,8 +157,6 @@ func TestAuthenticationError_Is(t *testing.T) { } authenticator := mocks.NewMockAuthenticator(ctrl) - authenticator.EXPECT().AddAuthenticationHeader(gomock.Any()).Return(fmt.Errorf("nope")) err = middleware.AddAuthenticationHeader(authenticator, config, request) assert.ErrorIs(t, err, middleware.ErrAuthenticationFailed) - assert.ErrorContains(t, err, "nope") } diff --git a/pkg/networking/networking_test.go b/pkg/networking/networking_test.go index ae696b3e9..1a48ccff8 100644 --- a/pkg/networking/networking_test.go +++ b/pkg/networking/networking_test.go @@ -34,6 +34,7 @@ func getConfig() configuration.Configuration { config.Set(auth.CONFIG_KEY_OAUTH_TOKEN, "") config.Set(configuration.AUTHENTICATION_TOKEN, "") config.Set(configuration.FF_OAUTH_AUTH_FLOW_ENABLED, true) + config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) return config } @@ -450,9 +451,10 @@ func Test_UserAgentInfo_Complete(t *testing.T) { func TestNetworkImpl_Clone(t *testing.T) { config := configuration.NewWithOpts(configuration.WithAutomaticEnv()) + config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) network := NewNetworkAccess(config) - config2 := configuration.NewWithOpts(configuration.WithAutomaticEnv()) + config2.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) config2.Set(configuration.AUTHENTICATION_TOKEN, "test") clonedNetwork := network.Clone() clonedNetwork.SetConfiguration(config2) From e935d7b8d8064b17c7a8df02f30959ff1a429480 Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Mon, 19 Jan 2026 17:46:22 +0100 Subject: [PATCH 5/7] chore: isImmutableHost shouldn't be exported --- internal/api/urls.go | 4 ++-- internal/api/urls_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/api/urls.go b/internal/api/urls.go index 7bbab9fa1..b60470382 100644 --- a/internal/api/urls.go +++ b/internal/api/urls.go @@ -20,7 +20,7 @@ var ( appRegexp = regexp.MustCompile(appPattern) ) -func IsImmutableHost(host string) bool { +func isImmutableHost(host string) bool { if IsKnownHostName(host) { return true } @@ -71,7 +71,7 @@ func GetCanonicalApiUrlFromString(userDefinedUrl string) (string, error) { func GetCanonicalApiAsUrl(url url.URL) (url.URL, error) { // for localhost we don't change the host, since there are no subdomains - if IsImmutableHost(url.Host) { + if isImmutableHost(url.Host) { url.Path = strings.Replace(url.Path, "/v1", "", 1) } else { url.Host = appRegexp.ReplaceAllString(url.Host, apiPrefixDot) diff --git a/internal/api/urls_test.go b/internal/api/urls_test.go index 8b132cb89..00d96ca07 100644 --- a/internal/api/urls_test.go +++ b/internal/api/urls_test.go @@ -93,11 +93,11 @@ func Test_isImmutableHost(t *testing.T) { hostlistNonLocalhost := []string{"snyk.io"} for _, host := range hostlistLocalhost { - assert.True(t, IsImmutableHost(host)) + assert.True(t, isImmutableHost(host)) } for _, host := range hostlistNonLocalhost { - assert.False(t, IsImmutableHost(host), host) + assert.False(t, isImmutableHost(host), host) } } From 96d4ae4bb02c6948ba5544178cc53b8d80e70a48 Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Mon, 19 Jan 2026 18:20:06 +0100 Subject: [PATCH 6/7] chore: lint --- internal/api/urls.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/api/urls.go b/internal/api/urls.go index b60470382..f6009bcf3 100644 --- a/internal/api/urls.go +++ b/internal/api/urls.go @@ -53,10 +53,7 @@ func IsKnownHostName(host string) bool { // get rid of port portlessHost := strings.Split(host, ":")[0] - if knownHostNames[portlessHost] { - return true - } - return false + return knownHostNames[portlessHost] } func GetCanonicalApiUrlFromString(userDefinedUrl string) (string, error) { From ddcfabf89549814dcbf4e06633ce8246bfdcac06 Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Thu, 22 Jan 2026 12:55:31 +0100 Subject: [PATCH 7/7] refactor: only check for host in API default func --- pkg/app/app.go | 3 ++- .../code_workflow/native_workflow_test.go | 3 --- .../connectivity_check_workflow_test.go | 3 --- .../network_utils/snyk_request_id_test.go | 5 ++--- pkg/networking/logging_test.go | 2 ++ pkg/networking/middleware/auth_header.go | 13 +++---------- pkg/networking/middleware/auth_header_test.go | 6 +++--- pkg/networking/networking_test.go | 6 +++--- 8 files changed, 15 insertions(+), 26 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index ee3b66b9f..106a065f7 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -2,6 +2,7 @@ package app import ( "crypto/fips140" + "errors" "fmt" "io" "log" @@ -142,7 +143,7 @@ func defaultFuncApiUrl(globalConfig configuration.Configuration, logger *zerolog if isValid, validationErr := auth.IsValidAuthHost(apiString, config.GetString(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP)); !isValid || validationErr != nil { hostNameErr := fmt.Errorf("host name is not snyk.io or snykgov.io") logger.Err(hostNameErr).Msg("host name is not snyk.io or snykgov.io") - return nil, fmt.Errorf("host name is not snyk.io or snykgov.io") + return nil, errors.Join(validationErr, hostNameErr) } return apiString, nil } diff --git a/pkg/local_workflows/code_workflow/native_workflow_test.go b/pkg/local_workflows/code_workflow/native_workflow_test.go index 8754ea333..91d73f3a4 100644 --- a/pkg/local_workflows/code_workflow/native_workflow_test.go +++ b/pkg/local_workflows/code_workflow/native_workflow_test.go @@ -9,8 +9,6 @@ import ( "testing" "github.com/rs/zerolog" - "github.com/snyk/go-application-framework/internal/constants" - "github.com/snyk/go-application-framework/pkg/auth" "github.com/stretchr/testify/assert" "github.com/snyk/go-application-framework/pkg/configuration" @@ -87,7 +85,6 @@ func Test_TrackUsage(t *testing.T) { config := configuration.NewWithOpts() config.Set(configuration.ORGANIZATION, org) config.Set(configuration.API_URL, server.URL) - config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) networkAccess := networking.NewNetworkAccess(config) // call method under test diff --git a/pkg/local_workflows/connectivity_check_extension/connectivity_check_workflow_test.go b/pkg/local_workflows/connectivity_check_extension/connectivity_check_workflow_test.go index bfde9ee8d..4c9de3e75 100644 --- a/pkg/local_workflows/connectivity_check_extension/connectivity_check_workflow_test.go +++ b/pkg/local_workflows/connectivity_check_extension/connectivity_check_workflow_test.go @@ -8,8 +8,6 @@ import ( "testing" "github.com/rs/zerolog" - "github.com/snyk/go-application-framework/internal/constants" - "github.com/snyk/go-application-framework/pkg/auth" "github.com/snyk/go-application-framework/pkg/configuration" "github.com/snyk/go-application-framework/pkg/local_workflows/connectivity_check_extension/connectivity" "github.com/snyk/go-application-framework/pkg/networking" @@ -86,7 +84,6 @@ func TestJSONOutputWithOrganizations(t *testing.T) { config.Set("insecure", true) config.Set(configuration.AUTHENTICATION_TOKEN, "test-token") config.Set(configuration.API_URL, server.URL) - config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) config.Set("json", true) // Request JSON output // Create NetworkAccess diff --git a/pkg/local_workflows/network_utils/snyk_request_id_test.go b/pkg/local_workflows/network_utils/snyk_request_id_test.go index 1f2c13edc..efe54aed7 100644 --- a/pkg/local_workflows/network_utils/snyk_request_id_test.go +++ b/pkg/local_workflows/network_utils/snyk_request_id_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/snyk/go-application-framework/internal/constants" - "github.com/snyk/go-application-framework/pkg/auth" "github.com/stretchr/testify/assert" "github.com/snyk/go-application-framework/pkg/configuration" @@ -15,8 +14,8 @@ import ( func Test_AddRequestId(t *testing.T) { t.Run("Add missing snyk-request-id", func(t *testing.T) { config := configuration.NewInMemory() - config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) net := networking.NewNetworkAccess(config) + config.Set(configuration.API_URL, constants.SNYK_DEFAULT_API_URL) // use method under test AddSnykRequestId(net) @@ -34,7 +33,7 @@ func Test_AddRequestId(t *testing.T) { t.Run("Do not override snyk-request-id", func(t *testing.T) { config := configuration.NewInMemory() net := networking.NewNetworkAccess(config) - config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) + config.Set(configuration.API_URL, "https://api.snyk.io") expectedValue := "pre-existing-id" // use method under test diff --git a/pkg/networking/logging_test.go b/pkg/networking/logging_test.go index 7bbd4e96b..351a1c994 100644 --- a/pkg/networking/logging_test.go +++ b/pkg/networking/logging_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/rs/zerolog" + "github.com/snyk/go-application-framework/internal/constants" "github.com/stretchr/testify/assert" "github.com/snyk/go-application-framework/pkg/configuration" @@ -299,6 +300,7 @@ func Test_LogResponse_skipsBinaryContent(t *testing.T) { func Test_logRoundTrip(t *testing.T) { config := configuration.NewWithOpts() + config.Set(configuration.API_URL, constants.SNYK_DEFAULT_API_URL) expectedResponseBody := "hello client" expectedResponseBodyError := "who are you?" expectedRequestBody := "hello server" diff --git a/pkg/networking/middleware/auth_header.go b/pkg/networking/middleware/auth_header.go index 8c10cd001..f84c95f81 100644 --- a/pkg/networking/middleware/auth_header.go +++ b/pkg/networking/middleware/auth_header.go @@ -103,19 +103,12 @@ func AddAuthenticationHeader( config configuration.Configuration, request *http.Request, ) error { - apiUrl := config.GetString(configuration.API_URL) - additionalSubdomains := config.GetStringSlice(configuration.AUTHENTICATION_SUBDOMAINS) - additionalUrls := config.GetStringSlice(configuration.AUTHENTICATION_ADDITIONAL_URLS) - hostNameRegex := config.GetString(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP) - - canonicalHostname, err := api.GetCanonicalApiUrl(*request.URL) + apiUrl, err := config.GetStringWithError(configuration.API_URL) if err != nil { return errors.Join(err, ErrAuthenticationFailed) } - isValid, err := auth.IsValidAuthHost(canonicalHostname, hostNameRegex) - if !isValid || err != nil { - return errors.Join(err, ErrAuthenticationFailed) - } + additionalSubdomains := config.GetStringSlice(configuration.AUTHENTICATION_SUBDOMAINS) + additionalUrls := config.GetStringSlice(configuration.AUTHENTICATION_ADDITIONAL_URLS) isSnykApi, err := ShouldRequireAuthentication(apiUrl, request.URL, additionalSubdomains, additionalUrls) // requests to the api automatically get an authentication token attached diff --git a/pkg/networking/middleware/auth_header_test.go b/pkg/networking/middleware/auth_header_test.go index 503e49308..e8401f428 100644 --- a/pkg/networking/middleware/auth_header_test.go +++ b/pkg/networking/middleware/auth_header_test.go @@ -1,13 +1,12 @@ package middleware_test import ( + "fmt" "net/http" "net/url" "testing" "github.com/golang/mock/gomock" - "github.com/snyk/go-application-framework/internal/constants" - "github.com/snyk/go-application-framework/pkg/auth" "github.com/stretchr/testify/assert" "github.com/snyk/go-application-framework/internal/api" @@ -71,7 +70,6 @@ func Test_AddAuthenticationHeader(t *testing.T) { config := configuration.New() config.Set(configuration.API_URL, "https://api.snyk.io") config.Set(configuration.AUTHENTICATION_SUBDOMAINS, []string{"deeproxy"}) - config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) // case: headers added (api) url, err := url.Parse("https://app.snyk.io/rest/endpoint1") @@ -157,6 +155,8 @@ func TestAuthenticationError_Is(t *testing.T) { } authenticator := mocks.NewMockAuthenticator(ctrl) + authenticator.EXPECT().AddAuthenticationHeader(gomock.Any()).Return(fmt.Errorf("nope")) err = middleware.AddAuthenticationHeader(authenticator, config, request) assert.ErrorIs(t, err, middleware.ErrAuthenticationFailed) + assert.ErrorContains(t, err, "nope") } diff --git a/pkg/networking/networking_test.go b/pkg/networking/networking_test.go index 1a48ccff8..53ca7e0ba 100644 --- a/pkg/networking/networking_test.go +++ b/pkg/networking/networking_test.go @@ -34,7 +34,6 @@ func getConfig() configuration.Configuration { config.Set(auth.CONFIG_KEY_OAUTH_TOKEN, "") config.Set(configuration.AUTHENTICATION_TOKEN, "") config.Set(configuration.FF_OAUTH_AUTH_FLOW_ENABLED, true) - config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) return config } @@ -451,10 +450,10 @@ func Test_UserAgentInfo_Complete(t *testing.T) { func TestNetworkImpl_Clone(t *testing.T) { config := configuration.NewWithOpts(configuration.WithAutomaticEnv()) - config.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) + config.Set(configuration.API_URL, constants.SNYK_DEFAULT_API_URL) network := NewNetworkAccess(config) config2 := configuration.NewWithOpts(configuration.WithAutomaticEnv()) - config2.AddDefaultValue(auth.CONFIG_KEY_ALLOWED_HOST_REGEXP, configuration.StandardDefaultValueFunction(constants.SNYK_DEFAULT_ALLOWED_HOST_REGEXP)) + config2.Set(configuration.API_URL, constants.SNYK_DEFAULT_API_URL) config2.Set(configuration.AUTHENTICATION_TOKEN, "test") clonedNetwork := network.Clone() clonedNetwork.SetConfiguration(config2) @@ -482,6 +481,7 @@ func TestNetworkImpl_Clone(t *testing.T) { func TestNetworkImpl_ErrorHandler(t *testing.T) { expectedErr := snyk.NewUnauthorisedError("no auth") config := configuration.NewWithOpts(configuration.WithAutomaticEnv()) + config.Set(configuration.API_URL, constants.SNYK_DEFAULT_API_URL) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized)