CVE-2024-40636
Description
Steeltoe is an open source project that provides a collection of libraries that helps users build production-grade cloud-native applications using externalized configuration, service discovery, distributed tracing, application management, and more. When utilizing multiple Eureka server service URLs with basic auth and encountering an issue with fetching the service registry, an error is logged with the Eureka server service URLs but only the first URL is masked. The code in question is _logger.LogError(e, "FetchRegistry Failed for Eureka service urls: {EurekaServerServiceUrls}", new Uri(ClientConfig.EurekaServerServiceUrls).ToMaskedString()); in the DiscoveryClient.cs file which may leak credentials into logs. This issue has been addressed in version 3.2.8 of the Steeltoe.Discovery.Eureka nuget package.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
Steeltoe.Discovery.EurekaNuGet | < 3.2.8 | 3.2.8 |
Steeltoe.Discovery.EurekaBaseNuGet | <= 2.5.5 | — |
Steeltoe.Discovery.ClientCoreNuGet | >= 0 | — |
Steeltoe.Discovery.ClientAutofacNuGet | <= 2.5.5 | — |
Patches
1c5d4a94e90ccFix Eureka URI masking
4 files changed · +38 −60
src/Common/src/Common/Extensions/UriExtensions.cs+24 −25 modified@@ -4,29 +4,48 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; namespace Steeltoe.Common.Extensions; public static class UriExtensions { - public static string ToMaskedString(this Uri source) + private static readonly char[] _uriSeparatorChar = { ',' }; + + /// <summary> + /// Parse a querystring into a dictionary of key value pairs + /// </summary> + /// <param name="querystring">The querystring to parse</param> + /// <returns>Pairs of keys and values</returns> + public static Dictionary<string, string> ParseQuerystring(string querystring) { - if (source == null) + var result = new Dictionary<string, string>(); + foreach (var pair in querystring.Split('&')) { - throw new ArgumentNullException(nameof(source)); + if (!string.IsNullOrEmpty(pair)) + { + var kvp = pair.Split('='); + result.Add(WebUtility.UrlDecode(kvp[0]), WebUtility.UrlDecode(kvp[1])); + } } - return source.ToMaskedUri().ToString(); + return result; } - public static Uri ToMaskedUri(this Uri source) + public static string ToMaskedString(this Uri source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } + var uris = source.ToString(); + return string.Join(",", uris.Split(_uriSeparatorChar, StringSplitOptions.RemoveEmptyEntries).Select(uri => new Uri(uri).ToMaskedUri().ToString())); + } + + private static Uri ToMaskedUri(this Uri source) + { if (string.IsNullOrEmpty(source.UserInfo)) { return source; @@ -42,24 +61,4 @@ public static Uri ToMaskedUri(this Uri source) return builder.Uri; } - - /// <summary> - /// Parse a querystring into a dictionary of key value pairs - /// </summary> - /// <param name="querystring">The querystring to parse</param> - /// <returns>Pairs of keys and values</returns> - public static Dictionary<string, string> ParseQuerystring(string querystring) - { - var result = new Dictionary<string, string>(); - foreach (var pair in querystring.Split('&')) - { - if (!string.IsNullOrEmpty(pair)) - { - var kvp = pair.Split('='); - result.Add(WebUtility.UrlDecode(kvp[0]), WebUtility.UrlDecode(kvp[1])); - } - } - - return result; - } } \ No newline at end of file
src/Common/test/Common.Test/Extensions/UriExtensionsTest.cs+4 −26 modified@@ -10,30 +10,19 @@ namespace Steeltoe.Common.Test.Extensions; public class UriExtensionsTest { - [Fact] - public void MaskExistingBasicAuthenticationToString() - { - var uri = new Uri("http://username:password@www.example.com/"); - var expected = "http://****:****@www.example.com/"; - - var masked = uri.ToMaskedString(); - - Assert.Equal(expected, masked); - } - [Fact] public void MaskExistingBasicAuthentication() { - var uri = new Uri("http://username:password@www.example.com/"); - var expected = new Uri("http://****:****@www.example.com/"); + var uri = new Uri("http://username:password@www.example.com/,http://user2:pass2@www.other.com/"); + var expected = "http://****:****@www.example.com/,http://****:****@www.other.com/"; - var masked = uri.ToMaskedUri(); + var masked = uri.ToMaskedString(); Assert.Equal(expected, masked); } [Fact] - public void DontMaskStringIfNotBasicAuthenticationExists() + public void DontMaskIfNotBasicAuthenticationExists() { var uri = new Uri("http://www.example.com/"); var expected = uri.ToString(); @@ -42,15 +31,4 @@ public void DontMaskStringIfNotBasicAuthenticationExists() Assert.Equal(expected, masked); } - - [Fact] - public void DontMaskUriIfNotBasicAuthenticationExists() - { - var uri = new Uri("http://www.example.com/"); - var expected = new Uri(uri.ToString()); - - var masked = uri.ToMaskedUri(); - - Assert.Equal(expected, masked); - } } \ No newline at end of file
src/Discovery/src/Eureka/DiscoveryClient.cs+2 −1 modified@@ -322,7 +322,8 @@ protected internal async T.Task<bool> FetchRegistryAsync(bool fullUpdate) catch (Exception e) { // Log - _logger.LogError(e, "FetchRegistry Failed for Eureka service urls: {EurekaServerServiceUrls}", new Uri(ClientConfig.EurekaServerServiceUrls).ToMaskedString()); + var masked = new Uri(ClientConfig.EurekaServerServiceUrls).ToMaskedString(); + _logger.LogError(e, "FetchRegistry Failed for Eureka service urls: {EurekaServerServiceUrls}", masked); return false; }
src/Discovery/src/Eureka/Transport/EurekaHttpClient.cs+8 −8 modified@@ -132,7 +132,7 @@ private async Task<EurekaHttpResponse> RegisterAsyncInternal(InstanceInfo info) } catch (Exception e) { - _logger?.LogError(e, "RegisterAsync Failed, request was made to {requestUri}, retry: {retry}", requestUri.ToMaskedUri(), retry); + _logger?.LogError(e, "RegisterAsync Failed, request was made to {requestUri}, retry: {retry}", requestUri.ToMaskedString(), retry); } finally { @@ -250,7 +250,7 @@ private async Task<EurekaHttpResponse<InstanceInfo>> SendHeartBeatAsyncInternal( } catch (Exception e) { - _logger?.LogError(e, "SendHeartBeatAsync Failed, request was made to {requestUri}", requestUri.ToMaskedUri()); + _logger?.LogError(e, "SendHeartBeatAsync Failed, request was made to {requestUri}", requestUri.ToMaskedString()); } finally { @@ -363,7 +363,7 @@ private async Task<EurekaHttpResponse<Application>> GetApplicationAsyncInternal( } catch (Exception e) { - _logger?.LogError(e, "GetApplicationAsync Failed, request was made to {requestUri}", requestUri.ToMaskedUri()); + _logger?.LogError(e, "GetApplicationAsync Failed, request was made to {requestUri}", requestUri.ToMaskedString()); } finally { @@ -460,7 +460,7 @@ private async Task<EurekaHttpResponse> CancelAsyncInternal(string appName, strin } catch (Exception e) { - _logger?.LogError(e, "CancelAsync Failed, request was made to {requestUri}", requestUri.ToMaskedUri()); + _logger?.LogError(e, "CancelAsync Failed, request was made to {requestUri}", requestUri.ToMaskedString()); } finally { @@ -536,7 +536,7 @@ private async Task<EurekaHttpResponse> DeleteStatusOverrideAsyncInternal(string } catch (Exception e) { - _logger?.LogError(e, "DeleteStatusOverrideAsync Failed, request was made to {requestUri}", requestUri.ToMaskedUri()); + _logger?.LogError(e, "DeleteStatusOverrideAsync Failed, request was made to {requestUri}", requestUri.ToMaskedString()); } finally { @@ -613,7 +613,7 @@ private async Task<EurekaHttpResponse> StatusUpdateAsyncInternal(string appName, } catch (Exception e) { - _logger?.LogError(e, "StatusUpdateAsync Failed, request was made to {requestUri}", requestUri.ToMaskedUri()); + _logger?.LogError(e, "StatusUpdateAsync Failed, request was made to {requestUri}", requestUri.ToMaskedString()); } finally { @@ -865,7 +865,7 @@ protected virtual async Task<EurekaHttpResponse<InstanceInfo>> DoGetInstanceAsyn } catch (Exception e) { - _logger?.LogError(e, "DoGetInstanceAsync Failed, request was made to {requestUri}", requestUri.ToMaskedUri()); + _logger?.LogError(e, "DoGetInstanceAsync Failed, request was made to {requestUri}", requestUri.ToMaskedString()); } finally { @@ -945,7 +945,7 @@ protected virtual async Task<EurekaHttpResponse<Applications>> DoGetApplications } catch (Exception e) { - _logger?.LogError(e, "DoGetApplicationsAsync Failed, request was made to {requestUri}", requestUri.ToMaskedUri()); + _logger?.LogError(e, "DoGetApplicationsAsync Failed, request was made to {requestUri}", requestUri.ToMaskedString()); } finally {
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
4News mentions
0No linked articles in our index yet.