diff --git a/runtime/secrets/converter.go b/runtime/secrets/converter.go index b312c55b..1e340bb1 100644 --- a/runtime/secrets/converter.go +++ b/runtime/secrets/converter.go @@ -133,6 +133,8 @@ func TLSConfigFromSecret(ctx context.Context, secret *corev1.Secret, opts ...TLS // proxy URL. Optional "username" and "password" fields can be provided // for proxy authentication. func ProxyURLFromSecret(ctx context.Context, secret *corev1.Secret) (*url.URL, error) { + ref := client.ObjectKeyFromObject(secret) + addressData, exists := secret.Data[KeyAddress] if !exists { return nil, &KeyNotFoundError{Key: KeyAddress, Secret: secret} @@ -140,16 +142,25 @@ func ProxyURLFromSecret(ctx context.Context, secret *corev1.Secret) (*url.URL, e address := string(addressData) if address == "" { - ref := client.ObjectKeyFromObject(secret) return nil, fmt.Errorf("secret '%s': proxy address is empty", ref) } + // Validate length before parsing to avoid parsing large invalid URLs. + // The 2048 character limit matches the validation used in notification-controller's + // spec.address field. + if len(address) > 2048 { + return nil, fmt.Errorf("secret '%s': proxy URL exceeds maximum length of 2048 characters", ref) + } + proxyURL, err := url.Parse(address) if err != nil { - ref := client.ObjectKeyFromObject(secret) return nil, fmt.Errorf("secret '%s': failed to parse proxy address '%s': %w", ref, address, err) } + if proxyURL.Scheme != "http" && proxyURL.Scheme != "https" { + return nil, fmt.Errorf("secret '%s': proxy URL must use http or https scheme, got '%s'", ref, proxyURL.Scheme) + } + username, hasUsername := secret.Data[KeyUsername] password, hasPassword := secret.Data[KeyPassword] diff --git a/runtime/secrets/converter_test.go b/runtime/secrets/converter_test.go index 30f54d00..021d0239 100644 --- a/runtime/secrets/converter_test.go +++ b/runtime/secrets/converter_test.go @@ -752,6 +752,36 @@ func TestProxyURLFromSecret(t *testing.T) { ), errMsg: "secret 'default/proxy-secret': failed to parse proxy address", }, + { + name: "invalid scheme - ftp", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + secrets.KeyAddress: []byte("ftp://ftp.example.com:21"), + }), + ), + errMsg: "secret 'default/proxy-secret': proxy URL must use http or https scheme, got 'ftp'", + }, + { + name: "invalid scheme - no scheme", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + secrets.KeyAddress: []byte("proxy.example.com:8080"), + }), + ), + errMsg: "secret 'default/proxy-secret': proxy URL must use http or https scheme", + }, + { + name: "URL exceeds maximum length", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + secrets.KeyAddress: []byte("http://proxy.example.com/" + string(make([]byte, 2049))), + }), + ), + errMsg: "secret 'default/proxy-secret': proxy URL exceeds maximum length of 2048 characters", + }, } for _, tt := range tests { diff --git a/tests/integration/go.mod b/tests/integration/go.mod index 84e26db3..cd13441d 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -22,7 +22,7 @@ require ( github.com/fluxcd/pkg/cache v0.11.0 github.com/fluxcd/pkg/git v0.36.0 github.com/fluxcd/pkg/git/gogit v0.40.0 - github.com/fluxcd/pkg/runtime v0.86.0 + github.com/fluxcd/pkg/runtime v0.87.0 github.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b github.com/go-git/go-git/v5 v5.16.2 github.com/google/go-containerregistry v0.20.6