From 0a0e9fe413c3680bea2f2a1b42fd6f0bcb3cd45c Mon Sep 17 00:00:00 2001 From: Dennis Oosterkamp Date: Wed, 22 Jul 2026 09:54:37 +0200 Subject: [PATCH 1/9] Add possibility to use different passwords for sentinel and redis (#1698) --- docs/Configuration.md | 2 + .../ConfigurationOptions.cs | 38 ++++++++++++++- .../ConnectionMultiplexer.Sentinel.cs | 14 +++++- .../PublicAPI/PublicAPI.SentinelUnshipped.txt | 5 ++ .../PublicAPI/PublicAPI.Shipped.txt | 4 ++ .../SentinelConfigTests.cs | 47 +++++++++++++++++++ 6 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 src/StackExchange.Redis/PublicAPI/PublicAPI.SentinelUnshipped.txt create mode 100644 tests/StackExchange.Redis.Tests/SentinelConfigTests.cs diff --git a/docs/Configuration.md b/docs/Configuration.md index ce3ab93f6..ffe2a6bf8 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -86,7 +86,9 @@ The `ConfigurationOptions` object has a wide range of properties, all of which a | tcpKeepAlive={bool} | `TcpKeepAlive` | `true` | Enables TCP keep-alive when appropriate (endpoint- and platform-dependent) | | name={string} | `ClientName` | `null` | Identification for the connection within redis | | password={string} | `Password` | `null` | Password for the redis server | +| sentinelPassword={string} | `SentinelPassword` | `null` | Optional password to authenticate with Sentinel servers (falls back to `password` if not provided) | | user={string} | `User` | `null` | User for the redis server (for use with ACLs on redis 6 and above) | +| sentinelUser={string} | `SentinelUser` | `null` | Optional username to authenticate with Sentinel servers (falls back to `user` if not provided) | | proxy={proxy type} | `Proxy` | `Proxy.None` | Type of proxy in use (if any); for example "twemproxy/envoyproxy" | | resolveDns={bool} | `ResolveDns` | `false` | Specifies that DNS resolution should be explicit and eager, rather than implicit | | serviceName={string} | `ServiceName` | `null` | Used for connecting to a sentinel primary service | diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index eef0bde81..e11550d65 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -98,7 +98,9 @@ internal const string KeepAlive = "keepAlive", ClientName = "name", User = "user", + SentinelUser = "sentinelUser", Password = "password", + SentinelPassword = "sentinelPassword", PreserveAsyncOrder = "preserveAsyncOrder", Proxy = "proxy", ResolveDns = "resolveDns", @@ -133,7 +135,9 @@ internal const string HighPrioritySocketThreads, KeepAlive, User, + SentinelUser, Password, + SentinelPassword, PreserveAsyncOrder, Proxy, ResolveDns, @@ -209,7 +213,7 @@ private enum OptionFlags : ulong private OptionFlags optionFlags; - private string? tieBreaker, sslHost, configChannel, user, password; + private string? tieBreaker, sslHost, configChannel, user, sentinelUser, password, sentinelPassword; private TimeSpan heartbeatInterval; @@ -738,6 +742,16 @@ public string? User set => user = value; } + /// + /// The username to use to authenticate with Sentinel servers, only when different from the Redis server password (optional). + /// If not specified, is used when communicating with Sentinels. + /// + public string? SentinelUser + { + get => sentinelUser ?? user ?? Defaults.User; + set => sentinelUser = value; + } + /// /// The password to use to authenticate with the server. /// @@ -747,6 +761,16 @@ public string? Password set => password = value; } + /// + /// The password to use to authenticate with Sentinel servers, only when different from the Redis server password (optional). + /// If not specified, is used when communicating with Sentinels. + /// + public string? SentinelPassword + { + get => sentinelPassword ?? password ?? Defaults.Password; + set => sentinelPassword = value; + } + /// /// Specifies whether asynchronous operations should be invoked in a way that guarantees their original delivery order. /// @@ -932,7 +956,7 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow public ConfigurationOptions Clone() => new ConfigurationOptions { defaultOptions = defaultOptions, - optionFlags = this.optionFlags, + optionFlags = optionFlags, ClientName = ClientName, ServiceName = ServiceName, keepAlive = keepAlive, @@ -941,7 +965,9 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow defaultVersion = defaultVersion, connectTimeout = connectTimeout, user = user, + SentinelUser = SentinelUser, password = password, + SentinelPassword = SentinelPassword, tieBreaker = tieBreaker, sslHost = sslHost, configChannel = configChannel, @@ -1040,7 +1066,9 @@ public string ToString(bool includePassword) Append(sb, OptionKeys.Version, defaultVersion); Append(sb, OptionKeys.ConnectTimeout, OptionFlags.ConnectTimeoutHasValue, in connectTimeout); Append(sb, OptionKeys.User, user); + Append(sb, OptionKeys.SentinelUser, SentinelUser); Append(sb, OptionKeys.Password, (includePassword || string.IsNullOrEmpty(password)) ? password : "*****"); + Append(sb, OptionKeys.SentinelPassword, (includePassword || string.IsNullOrEmpty(SentinelPassword)) ? SentinelPassword : "*****"); Append(sb, OptionKeys.TieBreaker, tieBreaker); Append(sb, OptionKeys.Ssl, OptionFlags.SslHasValue, OptionFlags.SslValue); if (HasValue(OptionFlags.SslProtocolsHasValue)) Append(sb, OptionKeys.SslProtocols, sslProtocols.ToString().Replace(',', '|')); @@ -1261,9 +1289,15 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown) case OptionKeys.User: user = value; break; + case OptionKeys.SentinelUser: + SentinelUser = value; + break; case OptionKeys.Password: password = value; break; + case OptionKeys.SentinelPassword: + SentinelPassword = value; + break; case OptionKeys.TieBreaker: TieBreaker = value; break; diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs index 6f11fa2f3..cdc042938 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs @@ -132,7 +132,12 @@ public static Task SentinelConnectAsync(ConfigurationOpti /// The to log to. private static ConnectionMultiplexer SentinelPrimaryConnect(ConfigurationOptions configuration, TextWriter? log = null) { - var sentinelConnection = SentinelConnect(configuration, log); + // Use separate sentinel credentials when provided on the configuration + var sentinelConfig = configuration.Clone(); + sentinelConfig.User = configuration.SentinelUser; + sentinelConfig.Password = configuration.SentinelPassword; + + var sentinelConnection = SentinelConnect(sentinelConfig, log); var muxer = sentinelConnection.GetSentinelMasterConnection(configuration, log); // Set reference to sentinel connection so that we can dispose it @@ -149,7 +154,12 @@ private static ConnectionMultiplexer SentinelPrimaryConnect(ConfigurationOptions /// The to log to. private static async Task SentinelPrimaryConnectAsync(ConfigurationOptions configuration, TextWriter? writer = null) { - var sentinelConnection = await SentinelConnectAsync(configuration, writer).ForAwait(); + // Use separate sentinel credentials when provided on the configuration + var sentinelConfig = configuration.Clone(); + sentinelConfig.User = configuration.SentinelUser; + sentinelConfig.Password = configuration.SentinelPassword; + + var sentinelConnection = await SentinelConnectAsync(sentinelConfig, writer).ForAwait(); var muxer = sentinelConnection.GetSentinelMasterConnection(configuration, writer); // Set reference to sentinel connection so that we can dispose it diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.SentinelUnshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.SentinelUnshipped.txt new file mode 100644 index 000000000..1dcb6da67 --- /dev/null +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.SentinelUnshipped.txt @@ -0,0 +1,5 @@ +#nullable enable +StackExchange.Redis.ConfigurationOptions.SentinelUser.get -> string? +StackExchange.Redis.ConfigurationOptions.SentinelUser.set -> void +StackExchange.Redis.ConfigurationOptions.SentinelPassword.get -> string? +StackExchange.Redis.ConfigurationOptions.SentinelPassword.set -> void diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 7e8381c6e..57e9f8195 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -287,6 +287,10 @@ StackExchange.Redis.ConfigurationOptions.ResolveDns.get -> bool StackExchange.Redis.ConfigurationOptions.ResolveDns.set -> void StackExchange.Redis.ConfigurationOptions.ResponseTimeout.get -> int StackExchange.Redis.ConfigurationOptions.ResponseTimeout.set -> void +StackExchange.Redis.ConfigurationOptions.SentinelUser.get -> string? +StackExchange.Redis.ConfigurationOptions.SentinelUser.set -> void +StackExchange.Redis.ConfigurationOptions.SentinelPassword.get -> string? +StackExchange.Redis.ConfigurationOptions.SentinelPassword.set -> void StackExchange.Redis.ConfigurationOptions.ServiceName.get -> string? StackExchange.Redis.ConfigurationOptions.ServiceName.set -> void StackExchange.Redis.ConfigurationOptions.SetClientLibrary.get -> bool diff --git a/tests/StackExchange.Redis.Tests/SentinelConfigTests.cs b/tests/StackExchange.Redis.Tests/SentinelConfigTests.cs new file mode 100644 index 000000000..2e659eea6 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/SentinelConfigTests.cs @@ -0,0 +1,47 @@ +using System; +using Xunit; + +namespace StackExchange.Redis.Tests; + +public class SentinelConfigTests +{ + [Fact] + public void Parse_SentinelCredentials_FromConnectionString() + { + var cs = "localhost:26379,serviceName=myprimary,sentinelUser=su,sentinelPassword=sp"; + var options = ConfigurationOptions.Parse(cs); + + Assert.Equal("su", options.SentinelUser); + Assert.Equal("sp", options.SentinelPassword); + Assert.Equal("myprimary", options.ServiceName); + } + + [Fact] + public void ToString_Masks_SentinelPassword_WhenExcluded() + { + var options = new ConfigurationOptions(); + options.EndPoints.Add("localhost", 26379); + options.ServiceName = "myprimary"; + options.SentinelUser = "su"; + options.SentinelPassword = "secret"; + + var repr = options.ToString(includePassword: false); + + Assert.Contains("sentinelUser=su", repr); + Assert.Contains("sentinelPassword=*****", repr); + Assert.DoesNotContain("secret", repr); + } + + [Fact] + public void Clone_Preserves_SentinelCredentials() + { + var options = new ConfigurationOptions(); + options.SentinelUser = "su"; + options.SentinelPassword = "sp"; + + var clone = options.Clone(); + + Assert.Equal(options.SentinelUser, clone.SentinelUser); + Assert.Equal(options.SentinelPassword, clone.SentinelPassword); + } +} From 262b459f33d7f20789c540ef84f63d05edde4725 Mon Sep 17 00:00:00 2001 From: Dennis Oosterkamp Date: Wed, 29 Jul 2026 09:28:14 +0200 Subject: [PATCH 2/9] Use correct Sentinel properties in ConfigurationOptions.ToString() --- src/StackExchange.Redis/ConfigurationOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index e11550d65..7145e8bc9 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -1066,9 +1066,9 @@ public string ToString(bool includePassword) Append(sb, OptionKeys.Version, defaultVersion); Append(sb, OptionKeys.ConnectTimeout, OptionFlags.ConnectTimeoutHasValue, in connectTimeout); Append(sb, OptionKeys.User, user); - Append(sb, OptionKeys.SentinelUser, SentinelUser); + Append(sb, OptionKeys.SentinelUser, sentinelUser); Append(sb, OptionKeys.Password, (includePassword || string.IsNullOrEmpty(password)) ? password : "*****"); - Append(sb, OptionKeys.SentinelPassword, (includePassword || string.IsNullOrEmpty(SentinelPassword)) ? SentinelPassword : "*****"); + Append(sb, OptionKeys.SentinelPassword, (includePassword || string.IsNullOrEmpty(SentinelPassword)) ? sentinelPassword : "*****"); Append(sb, OptionKeys.TieBreaker, tieBreaker); Append(sb, OptionKeys.Ssl, OptionFlags.SslHasValue, OptionFlags.SslValue); if (HasValue(OptionFlags.SslProtocolsHasValue)) Append(sb, OptionKeys.SslProtocols, sslProtocols.ToString().Replace(',', '|')); From b88180e9f53a5368af8c22061427c6a84afb6c78 Mon Sep 17 00:00:00 2001 From: Dennis Oosterkamp Date: Wed, 29 Jul 2026 09:29:42 +0200 Subject: [PATCH 3/9] Removed unused usings --- src/StackExchange.Redis/ConfigurationOptions.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index 7145e8bc9..6d7b95b4f 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -2,7 +2,6 @@ using System.Buffers; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.Net.Security; @@ -14,8 +13,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using RESPite; -using RESPite.Buffers; using StackExchange.Redis.Configuration; namespace StackExchange.Redis From 982095e4a10df9d3a758d3440fce98c9078dde69 Mon Sep 17 00:00:00 2001 From: Dennis Oosterkamp Date: Wed, 29 Jul 2026 12:04:47 +0200 Subject: [PATCH 4/9] - added sentinel user/pw fields to unit test that checks ConfigurationOptions - fixed typos, so fields, not properties, are used on the correct places --- src/StackExchange.Redis/ConfigurationOptions.cs | 8 +++++--- tests/StackExchange.Redis.Tests/ConfigTests.cs | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index 6d7b95b4f..7e34a3a21 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -962,9 +962,9 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow defaultVersion = defaultVersion, connectTimeout = connectTimeout, user = user, - SentinelUser = SentinelUser, + SentinelUser = sentinelUser, password = password, - SentinelPassword = SentinelPassword, + SentinelPassword = sentinelPassword, tieBreaker = tieBreaker, sslHost = sslHost, configChannel = configChannel, @@ -1065,7 +1065,7 @@ public string ToString(bool includePassword) Append(sb, OptionKeys.User, user); Append(sb, OptionKeys.SentinelUser, sentinelUser); Append(sb, OptionKeys.Password, (includePassword || string.IsNullOrEmpty(password)) ? password : "*****"); - Append(sb, OptionKeys.SentinelPassword, (includePassword || string.IsNullOrEmpty(SentinelPassword)) ? sentinelPassword : "*****"); + Append(sb, OptionKeys.SentinelPassword, (includePassword || string.IsNullOrEmpty(sentinelPassword)) ? sentinelPassword : "*****"); Append(sb, OptionKeys.TieBreaker, tieBreaker); Append(sb, OptionKeys.Ssl, OptionFlags.SslHasValue, OptionFlags.SslValue); if (HasValue(OptionFlags.SslProtocolsHasValue)) Append(sb, OptionKeys.SslProtocols, sslProtocols.ToString().Replace(',', '|')); @@ -1202,6 +1202,8 @@ private void Clear() #if DEBUG OutputLog = null; #endif + sentinelUser = null; + sentinelPassword = null; } object ICloneable.Clone() => Clone(); diff --git a/tests/StackExchange.Redis.Tests/ConfigTests.cs b/tests/StackExchange.Redis.Tests/ConfigTests.cs index 89ffd851e..65d972b41 100644 --- a/tests/StackExchange.Redis.Tests/ConfigTests.cs +++ b/tests/StackExchange.Redis.Tests/ConfigTests.cs @@ -88,6 +88,7 @@ orderby name "OutputLog", #endif "password", + "sentinelPassword", "proxy", "reconnectRetryPolicy", "RequestBufferPool", @@ -104,6 +105,7 @@ orderby name "tieBreaker", "Tunnel", "user", + "sentinelUser", }, fields); } From 42ff65c2daf0a08c07c028fc1efe35f5226748e4 Mon Sep 17 00:00:00 2001 From: Dennis Oosterkamp Date: Wed, 29 Jul 2026 13:22:52 +0200 Subject: [PATCH 5/9] Fix test --- tests/StackExchange.Redis.Tests/ConfigTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/StackExchange.Redis.Tests/ConfigTests.cs b/tests/StackExchange.Redis.Tests/ConfigTests.cs index 65d972b41..edde62cb7 100644 --- a/tests/StackExchange.Redis.Tests/ConfigTests.cs +++ b/tests/StackExchange.Redis.Tests/ConfigTests.cs @@ -88,12 +88,13 @@ orderby name "OutputLog", #endif "password", - "sentinelPassword", "proxy", "reconnectRetryPolicy", "RequestBufferPool", "ResponseBufferPool", "responseTimeout", + "sentinelPassword", + "sentinelUser", "ServiceName", "SocketManager", #if !NETFRAMEWORK @@ -105,7 +106,6 @@ orderby name "tieBreaker", "Tunnel", "user", - "sentinelUser", }, fields); } From 96dcc8adda83ced70acb072717cd48351c6d8bee Mon Sep 17 00:00:00 2001 From: Dennis Oosterkamp Date: Wed, 29 Jul 2026 13:57:15 +0200 Subject: [PATCH 6/9] Use a private _isSentinel instead of serverType --- src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs | 2 +- src/StackExchange.Redis/ConnectionMultiplexer.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs index 832922d15..078586608 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs @@ -22,7 +22,7 @@ public partial class ConnectionMultiplexer /// The to log to, if any. internal void InitializeSentinel(ILogger? log) { - if (ServerSelectionStrategy.ServerType != ServerType.Sentinel) + if (!_isSentinel) { return; } diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs index d8e328987..19503c8dd 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs @@ -44,6 +44,8 @@ public sealed partial class ConnectionMultiplexer : IInternalConnectionMultiplex internal bool IsDisposed => _isDisposed; internal ILogger? Logger { get; } + private readonly bool _isSentinel; + internal CommandMap CommandMap { get; } internal EndPointCollection EndPoints { get; } internal ConfigurationOptions RawConfig { get; } @@ -134,6 +136,8 @@ private ConnectionMultiplexer(ConfigurationOptions configuration, ServerType? se EndPoints.SetDefaultPorts(serverType, ssl: RawConfig.Ssl); Logger = configuration.LoggerFactory?.CreateLogger(); + _isSentinel = serverType == ServerType.Sentinel; + var map = CommandMap = configuration.GetCommandMap(serverType); if (!string.IsNullOrWhiteSpace(configuration.Password) && !configuration.TryResp3()) // RESP3 doesn't need AUTH (can issue as part of HELLO) { From b151f992f368fef78f6a350f038ed1ee164e7659 Mon Sep 17 00:00:00 2001 From: Dennis Oosterkamp Date: Wed, 29 Jul 2026 14:48:26 +0200 Subject: [PATCH 7/9] Moved Sentinel credentials usage to HandshakeAsync --- .../ConnectionMultiplexer.Sentinel.cs | 16 ++++------------ src/StackExchange.Redis/ServerEndPoint.cs | 10 ++++++---- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs index 078586608..3914dca3b 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs @@ -144,12 +144,8 @@ public static Task SentinelConnectAsync(ConfigurationOpti /// The to log to. private static ConnectionMultiplexer SentinelPrimaryConnect(ConfigurationOptions configuration, TextWriter? log = null) { - // Use separate sentinel credentials when provided on the configuration - var sentinelConfig = configuration.Clone(); - sentinelConfig.User = configuration.SentinelUser; - sentinelConfig.Password = configuration.SentinelPassword; - - var sentinelConnection = SentinelConnect(sentinelConfig, log); + // Sentinel credentials are selected during handshake based on ServerType + var sentinelConnection = SentinelConnect(configuration, log); var muxer = sentinelConnection.GetSentinelMasterConnection(configuration, log); // Set reference to sentinel connection so that we can dispose it @@ -166,12 +162,8 @@ private static ConnectionMultiplexer SentinelPrimaryConnect(ConfigurationOptions /// The to log to. private static async Task SentinelPrimaryConnectAsync(ConfigurationOptions configuration, TextWriter? writer = null) { - // Use separate sentinel credentials when provided on the configuration - var sentinelConfig = configuration.Clone(); - sentinelConfig.User = configuration.SentinelUser; - sentinelConfig.Password = configuration.SentinelPassword; - - var sentinelConnection = await SentinelConnectAsync(sentinelConfig, writer).ForAwait(); + // Sentinel credentials are selected during handshake based on ServerType + var sentinelConnection = await SentinelConnectAsync(configuration, writer).ForAwait(); var muxer = sentinelConnection.GetSentinelMasterConnection(configuration, writer); // Set reference to sentinel connection so that we can dispose it diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs index f191c1c3b..d716e188f 100644 --- a/src/StackExchange.Redis/ServerEndPoint.cs +++ b/src/StackExchange.Redis/ServerEndPoint.cs @@ -957,13 +957,15 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log) Multiplexer.Trace("No connection!?"); return; } + Message msg; - // Note that we need "" (not null) for password in the case of 'nopass' logins var config = Multiplexer.RawConfig; - string? user = config.User; - string password = config.Password ?? ""; + var isSentinelConnection = Multiplexer.ServerSelectionStrategy.ServerType == ServerType.Sentinel; + var user = isSentinelConnection ? config.SentinelUser : config.User; + // Note that we need "" (not null) for password in the case of 'nopass' logins + var password = (isSentinelConnection ? config.SentinelPassword : config.Password) ?? ""; + var clientName = Multiplexer.ClientName; - string clientName = Multiplexer.ClientName; if (!string.IsNullOrWhiteSpace(clientName)) { clientName = nameSanitizer.Replace(clientName, ""); From 12e3d37d480f6ce27488ffd3f84a4b3bf37dc560 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 30 Jul 2026 12:37:16 +0200 Subject: [PATCH 8/9] another property <-> field mixup fixed Co-authored-by: Marc Gravell --- src/StackExchange.Redis/ConfigurationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index 7e34a3a21..7fcf203fa 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -962,7 +962,7 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow defaultVersion = defaultVersion, connectTimeout = connectTimeout, user = user, - SentinelUser = sentinelUser, + sentinelUser = sentinelUser, password = password, SentinelPassword = sentinelPassword, tieBreaker = tieBreaker, From 4030837975b79e2e61a21c3e1b3bc4afca89c00b Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 30 Jul 2026 12:37:33 +0200 Subject: [PATCH 9/9] another property <-> field mixup fixed Co-authored-by: Marc Gravell --- src/StackExchange.Redis/ConfigurationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index 7fcf203fa..37b115eb1 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -964,7 +964,7 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow user = user, sentinelUser = sentinelUser, password = password, - SentinelPassword = sentinelPassword, + sentinelPassword = sentinelPassword, tieBreaker = tieBreaker, sslHost = sslHost, configChannel = configChannel,