VYPR
Moderate severityNVD Advisory· Published Jun 24, 2024· Updated Aug 1, 2024

go-retryablehttp can leak basic auth credentials to log files

CVE-2024-6104

Description

go-retryablehttp prior to 0.7.7 did not sanitize urls when writing them to its log file. This could lead to go-retryablehttp writing sensitive HTTP basic auth credentials to its log file. This vulnerability, CVE-2024-6104, was fixed in go-retryablehttp 0.7.7.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

go-retryablehttp prior to 0.7.7 logs unsanitized URLs, potentially exposing HTTP Basic Auth credentials in log files.

Vulnerability

Overview CVE-2024-6104 affects go-retryablehttp versions before 0.7.7. The library did not sanitize URLs when writing them to log output, meaning that if a URL contained sensitive information such as HTTP Basic Auth credentials (e.g., https://user:password@host/path), those credentials would be written in plaintext to the log file [2]. The root cause is the absence of a redaction function before logging the URL in the client's debugging and error messages.

Exploitation

An attacker who gains access to the log files (e.g., through a separate vulnerability, misconfigured permissions, or log shipping to a less secure system) can retrieve HTTP Basic Auth credentials. The attack requires the application using go-retryablehttp to make requests with embedded credentials in the URL; the library itself does not require authentication to exploit this flaw. Because the credentials are logged during normal retry and error-handling operations, the exposure occurs even during legitimate use [1][3].

Impact

Successful exploitation leads to the disclosure of HTTP Basic Auth credentials. An attacker could then use these credentials to authenticate to the target service, potentially gaining unauthorized access to resources or performing actions on behalf of the legitimate user. The severity is heightened because go-retryablehttp is commonly used in automation and infrastructure tooling (e.g., HashiCorp products), where leaked credentials may grant broad access [1][4].

Mitigation

The vulnerability is fixed in go-retryablehttp version 0.7.7. Users should upgrade immediately. The fix introduces a redactURL function that sanitizes URLs before logging them, ensuring that sensitive information—specifically Basic Auth credentials—is stripped or obscured [3]. No workarounds are provided for older versions. The advisory is published on HashiCorp's security discussion forum [1] and the NVD [2].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/hashicorp/go-retryablehttpGo
< 0.7.70.7.7

Affected products

791

Patches

1
a99f07beb3c5

Merge pull request #158 from dany74q/danny/redacted-url-in-logs

https://github.com/hashicorp/go-retryablehttpTom BamfordMay 30, 2024via ghsa
2 files changed · +38 9
  • client.go+21 7 modified
    @@ -658,9 +658,9 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
     	if logger != nil {
     		switch v := logger.(type) {
     		case LeveledLogger:
    -			v.Debug("performing request", "method", req.Method, "url", req.URL)
    +			v.Debug("performing request", "method", req.Method, "url", redactURL(req.URL))
     		case Logger:
    -			v.Printf("[DEBUG] %s %s", req.Method, req.URL)
    +			v.Printf("[DEBUG] %s %s", req.Method, redactURL(req.URL))
     		}
     	}
     
    @@ -715,9 +715,9 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
     		if err != nil {
     			switch v := logger.(type) {
     			case LeveledLogger:
    -				v.Error("request failed", "error", err, "method", req.Method, "url", req.URL)
    +				v.Error("request failed", "error", err, "method", req.Method, "url", redactURL(req.URL))
     			case Logger:
    -				v.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err)
    +				v.Printf("[ERR] %s %s request failed: %v", req.Method, redactURL(req.URL), err)
     			}
     		} else {
     			// Call this here to maintain the behavior of logging all requests,
    @@ -753,7 +753,7 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
     
     		wait := c.Backoff(c.RetryWaitMin, c.RetryWaitMax, i, resp)
     		if logger != nil {
    -			desc := fmt.Sprintf("%s %s", req.Method, req.URL)
    +			desc := fmt.Sprintf("%s %s", req.Method, redactURL(req.URL))
     			if resp != nil {
     				desc = fmt.Sprintf("%s (status: %d)", desc, resp.StatusCode)
     			}
    @@ -818,11 +818,11 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
     	// communicate why
     	if err == nil {
     		return nil, fmt.Errorf("%s %s giving up after %d attempt(s)",
    -			req.Method, req.URL, attempt)
    +			req.Method, redactURL(req.URL), attempt)
     	}
     
     	return nil, fmt.Errorf("%s %s giving up after %d attempt(s): %w",
    -		req.Method, req.URL, attempt, err)
    +		req.Method, redactURL(req.URL), attempt, err)
     }
     
     // Try to read the response body so we can reuse this connection.
    @@ -903,3 +903,17 @@ func (c *Client) StandardClient() *http.Client {
     		Transport: &RoundTripper{Client: c},
     	}
     }
    +
    +// Taken from url.URL#Redacted() which was introduced in go 1.15.
    +// We can switch to using it directly if we'll bump the minimum required go version.
    +func redactURL(u *url.URL) string {
    +	if u == nil {
    +		return ""
    +	}
    +
    +	ru := *u
    +	if _, has := ru.User.Password(); has {
    +		ru.User = url.UserPassword(ru.User.Username(), "xxxxx")
    +	}
    +	return ru.String()
    +}
    
  • client_test.go+17 2 modified
    @@ -481,17 +481,32 @@ func TestClient_Do_fails(t *testing.T) {
     	}))
     	defer ts.Close()
     
    +	serverUrlWithBasicAuth, err := url.Parse(ts.URL)
    +	if err != nil {
    +		t.Fatalf("failed parsing test server url: %s", ts.URL)
    +	}
    +	serverUrlWithBasicAuth.User = url.UserPassword("user", "pasten")
    +
     	tests := []struct {
    +		url  string
     		name string
     		cr   CheckRetry
     		err  string
     	}{
     		{
    +			url:  ts.URL,
     			name: "default_retry_policy",
     			cr:   DefaultRetryPolicy,
     			err:  "giving up after 3 attempt(s)",
     		},
     		{
    +			url:  serverUrlWithBasicAuth.String(),
    +			name: "default_retry_policy_url_with_basic_auth",
    +			cr:   DefaultRetryPolicy,
    +			err:  redactURL(serverUrlWithBasicAuth) + " giving up after 3 attempt(s)",
    +		},
    +		{
    +			url:  ts.URL,
     			name: "error_propagated_retry_policy",
     			cr:   ErrorPropagatedRetryPolicy,
     			err:  "giving up after 3 attempt(s): unexpected HTTP status 500 Internal Server Error",
    @@ -508,15 +523,15 @@ func TestClient_Do_fails(t *testing.T) {
     			client.RetryMax = 2
     
     			// Create the request
    -			req, err := NewRequest("POST", ts.URL, nil)
    +			req, err := NewRequest("POST", tt.url, nil)
     			if err != nil {
     				t.Fatalf("err: %v", err)
     			}
     
     			// Send the request.
     			_, err = client.Do(req)
     			if err == nil || !strings.HasSuffix(err.Error(), tt.err) {
    -				t.Fatalf("expected giving up error, got: %#v", err)
    +				t.Fatalf("expected %#v, got: %#v", tt.err, err)
     			}
     		})
     	}
    

Vulnerability mechanics

Generated 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.