VYPR
Medium severity5.1NVD Advisory· Published May 15, 2024· Updated Apr 15, 2026

CVE-2024-31216

CVE-2024-31216

Description

The source-controller is a Kubernetes operator, specialised in artifacts acquisition from external sources such as Git, OCI, Helm repositories and S3-compatible buckets. The source-controller implements the source.toolkit.fluxcd.io API and is a core component of the GitOps toolkit. Prior to version 1.2.5, when source-controller was configured to use an Azure SAS token when connecting to Azure Blob Storage, the token was logged along with the Azure URL when the controller encountered a connection error. An attacker with access to the source-controller logs could use the token to gain access to the Azure Blob Storage until the token expires. This vulnerability was fixed in source-controller v1.2.5. There is no workaround for this vulnerability except for using a different auth mechanism such as Azure Workload Identity.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/fluxcd/source-controllerGo
< 1.2.51.2.5

Patches

1
915d1a072a4f

Merge pull request #1430 from fluxcd/sanitze-bucker-errors

https://github.com/fluxcd/source-controllerStefan ProdanApr 4, 2024via ghsa
3 files changed · +219 2
  • internal/controller/bucket_controller.go+2 2 modified
    @@ -728,7 +728,7 @@ func fetchEtagIndex(ctx context.Context, provider BucketProvider, obj *bucketv1.
     	path := filepath.Join(tempDir, sourceignore.IgnoreFile)
     	if _, err := provider.FGetObject(ctxTimeout, obj.Spec.BucketName, sourceignore.IgnoreFile, path); err != nil {
     		if !provider.ObjectIsNotFound(err) {
    -			return err
    +			return fmt.Errorf("failed to get Etag for '%s' object: %w", sourceignore.IgnoreFile, serror.SanitizeError(err))
     		}
     	}
     	ps, err := sourceignore.ReadIgnoreFile(path, nil)
    @@ -792,7 +792,7 @@ func fetchIndexFiles(ctx context.Context, provider BucketProvider, obj *bucketv1
     						index.Delete(k)
     						return nil
     					}
    -					return fmt.Errorf("failed to get '%s' object: %w", k, err)
    +					return fmt.Errorf("failed to get '%s' object: %w", k, serror.SanitizeError(err))
     				}
     				if t != etag {
     					index.Add(k, etag)
    
  • internal/error/sanitized.go+76 0 added
    @@ -0,0 +1,76 @@
    +/*
    +Copyright 2024 The Flux authors
    +
    +Licensed under the Apache License, Version 2.0 (the "License");
    +you may not use this file except in compliance with the License.
    +You may obtain a copy of the License at
    +
    +    http://www.apache.org/licenses/LICENSE-2.0
    +
    +Unless required by applicable law or agreed to in writing, software
    +distributed under the License is distributed on an "AS IS" BASIS,
    +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +See the License for the specific language governing permissions and
    +limitations under the License.
    +*/
    +
    +package error
    +
    +import (
    +	"fmt"
    +	"net/url"
    +	"regexp"
    +)
    +
    +type SanitizedError struct {
    +	err string
    +}
    +
    +func (e SanitizedError) Error() string {
    +	return e.err
    +}
    +
    +// SanitizeError extracts all URLs from the error message
    +// and replaces them with the URL without the query string.
    +func SanitizeError(err error) SanitizedError {
    +	errorMessage := err.Error()
    +	for _, u := range extractURLs(errorMessage) {
    +		urlWithoutQueryString, err := removeQueryString(u)
    +		if err == nil {
    +			re, err := regexp.Compile(fmt.Sprintf("%s*", regexp.QuoteMeta(u)))
    +			if err == nil {
    +				errorMessage = re.ReplaceAllString(errorMessage, urlWithoutQueryString)
    +			}
    +		}
    +	}
    +
    +	return SanitizedError{errorMessage}
    +}
    +
    +// removeQueryString takes a URL string as input and returns the URL without the query string.
    +func removeQueryString(urlStr string) (string, error) {
    +	// Parse the URL.
    +	u, err := url.Parse(urlStr)
    +	if err != nil {
    +		return "", err
    +	}
    +
    +	// Rebuild the URL without the query string.
    +	u.RawQuery = ""
    +	return u.String(), nil
    +}
    +
    +// extractURLs takes a log message as input and returns the URLs found.
    +func extractURLs(logMessage string) []string {
    +	// Define a regular expression to match a URL.
    +	// This is a simple pattern and might need to be adjusted depending on the log message format.
    +	urlRegex := regexp.MustCompile(`https?://[^\s]+`)
    +
    +	// Find the first match in the log message.
    +	matches := urlRegex.FindAllString(logMessage, -1)
    +	if len(matches) == 0 {
    +		return []string{}
    +	}
    +
    +	return matches
    +}
    
  • internal/error/sanitized_test.go+141 0 added
    @@ -0,0 +1,141 @@
    +/*
    +Copyright 2024 The Flux authors
    +
    +Licensed under the Apache License, Version 2.0 (the "License");
    +you may not use this file except in compliance with the License.
    +You may obtain a copy of the License at
    +
    +    http://www.apache.org/licenses/LICENSE-2.0
    +
    +Unless required by applicable law or agreed to in writing, software
    +distributed under the License is distributed on an "AS IS" BASIS,
    +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +See the License for the specific language governing permissions and
    +limitations under the License.
    +*/
    +
    +package error
    +
    +import (
    +	"errors"
    +	"testing"
    +
    +	. "github.com/onsi/gomega"
    +)
    +
    +func Test_extractURLs(t *testing.T) {
    +
    +	tests := []struct {
    +		name       string
    +		logMessage string
    +		wantUrls   []string
    +	}{
    +		{
    +			name:       "Log Contains single URL",
    +			logMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02\": dial tcp 20.60.53.129:443: connect: connection refused",
    +			wantUrls:   []string{"https://blobstorage.blob.core.windows.net/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02\":"},
    +		},
    +		{
    +			name:       "Log Contains multiple URL",
    +			logMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml?abc=es  https://blobstorage1.blob.core.windows.net/container/index.yaml?abc=no : dial tcp 20.60.53.129:443: connect: connection refused",
    +			wantUrls: []string{
    +				"https://blobstorage.blob.core.windows.net/container/index.yaml?abc=es",
    +				"https://blobstorage1.blob.core.windows.net/container/index.yaml?abc=no",
    +			},
    +		},
    +		{
    +			name:       "Log Contains No URL",
    +			logMessage: "Log message without URL",
    +			wantUrls:   []string{},
    +		},
    +	}
    +
    +	for _, tt := range tests {
    +		t.Run(tt.name, func(t *testing.T) {
    +			g := NewWithT(t)
    +
    +			urls := extractURLs(tt.logMessage)
    +
    +			g.Expect(len(urls)).To(Equal(len(tt.wantUrls)))
    +			for i := range tt.wantUrls {
    +				g.Expect(urls[i]).To(Equal(tt.wantUrls[i]))
    +			}
    +		})
    +	}
    +}
    +
    +func Test_removeQueryString(t *testing.T) {
    +
    +	tests := []struct {
    +		name    string
    +		urlStr  string
    +		wantUrl string
    +	}{
    +		{
    +			name:    "URL with query string",
    +			urlStr:  "https://blobstorage.blob.core.windows.net/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02",
    +			wantUrl: "https://blobstorage.blob.core.windows.net/container/index.yaml",
    +		},
    +		{
    +			name:    "URL without query string",
    +			urlStr:  "https://blobstorage.blob.core.windows.net/container/index.yaml",
    +			wantUrl: "https://blobstorage.blob.core.windows.net/container/index.yaml",
    +		},
    +		{
    +			name:    "URL with query string and port",
    +			urlStr:  "https://blobstorage.blob.core.windows.net:443/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02",
    +			wantUrl: "https://blobstorage.blob.core.windows.net:443/container/index.yaml",
    +		},
    +		{
    +			name:    "Invalid URL",
    +			urlStr:  "NoUrl",
    +			wantUrl: "NoUrl",
    +		},
    +	}
    +
    +	for _, tt := range tests {
    +		t.Run(tt.name, func(t *testing.T) {
    +			g := NewWithT(t)
    +
    +			urlWithoutQueryString, err := removeQueryString(tt.urlStr)
    +
    +			g.Expect(err).To(BeNil())
    +			g.Expect(urlWithoutQueryString).To(Equal(tt.wantUrl))
    +		})
    +	}
    +}
    +
    +func Test_SanitizeError(t *testing.T) {
    +
    +	tests := []struct {
    +		name           string
    +		errMessage     string
    +		wantErrMessage string
    +	}{
    +		{
    +			name:           "Log message with URL with query string",
    +			errMessage:     "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02\": dial tcp 20.60.53.129:443: connect: connection refused",
    +			wantErrMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml dial tcp 20.60.53.129:443: connect: connection refused",
    +		},
    +		{
    +			name:           "Log message without URL",
    +			errMessage:     "Log message contains no URL",
    +			wantErrMessage: "Log message contains no URL",
    +		},
    +
    +		{
    +			name:           "Log message with multiple Urls",
    +			errMessage:     "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml?abc=es  https://blobstorage1.blob.core.windows.net/container/index.yaml?abc=no dial tcp 20.60.53.129:443: connect: connection refused",
    +			wantErrMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml  https://blobstorage1.blob.core.windows.net/container/index.yaml dial tcp 20.60.53.129:443: connect: connection refused",
    +		},
    +	}
    +
    +	for _, tt := range tests {
    +		t.Run(tt.name, func(t *testing.T) {
    +			g := NewWithT(t)
    +
    +			err := SanitizeError(errors.New(tt.errMessage))
    +			g.Expect(err.Error()).To(Equal(tt.wantErrMessage))
    +		})
    +	}
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.