namespace ZB.MOM.WW.MxGateway.Client; /// /// Scrubs exact secret substrings out of diagnostic text before it leaves the client on an /// exception path. MXAccess can echo a submitted credential or secured value back inside a /// failure diagnostic (protocol message, MXSTATUS_PROXY diagnostic text, HRESULT description); /// this helper replaces any such verbatim occurrence with <redacted> so the raw /// request payload never reaches a caught exception's message. The marker matches the Go, Rust, /// and Java clients. /// internal static class MxGatewaySecretRedaction { private const string Marker = ""; /// /// Replaces every non-null, non-empty secret in with the /// redaction marker (ordinal comparison). Returns the message unchanged when it is null or /// empty, or when no usable secret is supplied. /// /// The diagnostic message to scrub. /// The secret values to remove from the message. /// The scrubbed message. internal static string Redact(string message, params string?[] secrets) { if (string.IsNullOrEmpty(message) || secrets is null) { return message; } string result = message; foreach (string? secret in secrets) { if (!string.IsNullOrEmpty(secret)) { result = result.Replace(secret, Marker, StringComparison.Ordinal); } } return result; } /// /// Returns an exception equivalent to but with any verbatim secret /// scrubbed from its message. When nothing changes, the original exception is returned /// unchanged; otherwise a new exception of the same concrete runtime type is built and the /// original reply/status context is preserved. The secret-bearing original is deliberately /// not chained as the inner exception — doing so would let its unredacted message /// re-surface through (which logging frameworks call). The /// original's own inner cause (a transport error, never the request payload) is carried /// forward instead. /// /// The exception to redact. /// The secret values to remove from the message. /// The redacted exception, or the original when no change was needed. internal static MxGatewayException Redacted(MxGatewayException ex, params string?[] secrets) { ArgumentNullException.ThrowIfNull(ex); string redacted = Redact(ex.Message, secrets); if (string.Equals(redacted, ex.Message, StringComparison.Ordinal)) { return ex; } Exception? cause = ex.InnerException; return ex switch { MxAccessException access => new MxAccessException(redacted, access.Reply, cause), MxGatewaySessionException => new MxGatewaySessionException( redacted, ex.SessionId, ex.CorrelationId, ex.ProtocolStatus, ex.HResultCode, ex.Statuses, cause), MxGatewayWorkerException => new MxGatewayWorkerException( redacted, ex.SessionId, ex.CorrelationId, ex.ProtocolStatus, ex.HResultCode, ex.Statuses, cause), MxGatewayAuthenticationException => new MxGatewayAuthenticationException( redacted, ex.SessionId, ex.CorrelationId, ex.ProtocolStatus, ex.HResultCode, ex.Statuses, cause), MxGatewayAuthorizationException => new MxGatewayAuthorizationException( redacted, ex.SessionId, ex.CorrelationId, ex.ProtocolStatus, ex.HResultCode, ex.Statuses, cause), MxGatewayMalformedReplyException => new MxGatewayMalformedReplyException( redacted, ex.SessionId, ex.CorrelationId, ex.ProtocolStatus, ex.HResultCode, ex.Statuses, cause), MxGatewayCommandException => new MxGatewayCommandException( redacted, ex.SessionId, ex.CorrelationId, ex.ProtocolStatus, ex.HResultCode, ex.Statuses, cause), _ => new MxGatewayException(redacted, cause), }; } }