Repository: helm/helm
Pull request #31057 (2025-07-11 11:15:17)

Pass credentials when either chart repo or repo dont specify a port but it matches the default port of that scheme
**What this PR does / why we need it**:

As a user I would normally specify my repository without a port if its just on the default one, like: 

```
https://example.com/foo
```

The `index.yaml` might however contain urls with the port (as is the case with Artifactory).

So then you end up with a comparison like

```
u1.Host = "example.com"
u2.Host = "example.com:443"
```

which currently fails. Checking for the default port would allow the credentials to still pass.

**Special notes for your reviewer**:

**If applicable**:
- [ ] this PR contains user facing changes (the `docs needed` label should be applied if so)
- [ ] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility



Patch:
diff --git a/pkg/action/install.go b/pkg/action/install.go
index 440f41baa..d9da2f14f 100644
--- a/pkg/action/install.go
+++ b/pkg/action/install.go
@@ -746,6 +746,25 @@ OUTER:
 	return nil
 }
 
+func portOrDefault(u *url.URL) string {
+	if p := u.Port(); p != "" {
+		return p
+	}
+
+	switch u.Scheme {
+	case "http":
+		return "80"
+	case "https":
+		return "443"
+	default:
+		return ""
+	}
+}
+
+func urlEqual(u1, u2 *url.URL) bool {
+	return u1.Scheme == u2.Scheme && u1.Hostname() == u2.Hostname() && portOrDefault(u1) == portOrDefault(u2)
+}
+
 // LocateChart looks for a chart directory in known places, and returns either the full path or an error.
 //
 // This does not ensure that the chart is well-formed; only that the requested filename exists.
@@ -833,7 +852,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
 		// Host on URL (returned from url.Parse) contains the port if present.
 		// This check ensures credentials are not passed between different
 		// services on different ports.
-		if c.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) {
+		if c.PassCredentialsAll || urlEqual(u1, u2) {
 			dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password))
 		} else {
 			dl.Options = append(dl.Options, getter.WithBasicAuth("", ""))


Test patch:
diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go
index 6c2c91d0a..1882f19e7 100644
--- a/pkg/action/install_test.go
+++ b/pkg/action/install_test.go
@@ -24,6 +24,7 @@ import (
 	"io"
 	"io/fs"
 	"net/http"
+	"net/url"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -933,3 +934,68 @@ func TestInstallWithSystemLabels(t *testing.T) {
 
 	is.Equal(fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err)
 }
+
+func TestUrlEqual(t *testing.T) {
+	is := assert.New(t)
+
+	tests := []struct {
+		name     string
+		url1     string
+		url2     string
+		expected bool
+	}{
+		{
+			name:     "identical URLs",
+			url1:     "https://example.com:443",
+			url2:     "https://example.com:443",
+			expected: true,
+		},
+		{
+			name:     "same host, scheme, default HTTPS port vs explicit",
+			url1:     "https://example.com",
+			url2:     "https://example.com:443",
+			expected: true,
+		},
+		{
+			name:     "same host, scheme, default HTTP port vs explicit",
+			url1:     "http://example.com",
+			url2:     "http://example.com:80",
+			expected: true,
+		},
+		{
+			name:     "different schemes",
+			url1:     "http://example.com",
+			url2:     "https://example.com",
+			expected: false,
+		},
+		{
+			name:     "different hosts",
+			url1:     "https://example.com",
+			url2:     "https://www.example.com",
+			expected: false,
+		},
+		{
+			name:     "different ports",
+			url1:     "https://example.com:8080",
+			url2:     "https://example.com:9090",
+			expected: false,
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			u1, err := url.Parse(tc.url1)
+			if err != nil {
+				t.Fatalf("Failed to parse URL1 %s: %v", tc.url1, err)
+			}
+			u2, err := url.Parse(tc.url2)
+			if err != nil {
+				t.Fatalf("Failed to parse URL2 %s: %v", tc.url2, err)
+			}
+
+			is.Equal(tc.expected, urlEqual(u1, u2))
+		})
+	}
+}