From d367ead352cf00af26df87ba6d5ec5e0284391e1 Mon Sep 17 00:00:00 2001 From: Declan Smith Date: Wed, 11 Feb 2026 09:10:04 +1000 Subject: [PATCH 1/5] feat(util): add `SanitizeSecrets` method and refactor secret masking Added the `SanitizeSecrets` method to `StringUtil` for improved secret masking logic, allowing for better handling of multi-line secrets and whitespace. Replaced existing `MaskMatchingSecrets` calls to use the new method. Updated related logic in `ParamService` and logging output for consistency. --- ...eTests.VerifyPublicApiSurface.verified.txt | 3 +++ .../Logging/MaskingAnsiConsoleOutput.cs | 7 +---- DecSm.Atom/Params/ParamService.cs | 5 +--- DecSm.Atom/Util/StringUtil.cs | 26 +++++++++++++++++++ 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/DecSm.Atom.Tests/ApiSurfaceTests/PublicApiSurfaceTests.VerifyPublicApiSurface.verified.txt b/DecSm.Atom.Tests/ApiSurfaceTests/PublicApiSurfaceTests.VerifyPublicApiSurface.verified.txt index 9da7b44a..956e11bf 100644 --- a/DecSm.Atom.Tests/ApiSurfaceTests/PublicApiSurfaceTests.VerifyPublicApiSurface.verified.txt +++ b/DecSm.Atom.Tests/ApiSurfaceTests/PublicApiSurfaceTests.VerifyPublicApiSurface.verified.txt @@ -1913,6 +1913,9 @@ }, { Name: SanitizeForLogging + }, + { + Name: SanitizeSecrets } ] }, diff --git a/DecSm.Atom/Logging/MaskingAnsiConsoleOutput.cs b/DecSm.Atom/Logging/MaskingAnsiConsoleOutput.cs index 26a6168d..f7c45000 100644 --- a/DecSm.Atom/Logging/MaskingAnsiConsoleOutput.cs +++ b/DecSm.Atom/Logging/MaskingAnsiConsoleOutput.cs @@ -63,12 +63,7 @@ public override void Write(char value) => public override void Write(string? value) { if (value is { Length: > 0 }) - { - var masker = ServiceStaticAccessor.Service; - - if (masker is not null) - value = masker.MaskMatchingSecrets(value); - } + value = ServiceStaticAccessor.Service?.MaskMatchingSecrets(value) ?? value; _innerWriter.Write(value); } diff --git a/DecSm.Atom/Params/ParamService.cs b/DecSm.Atom/Params/ParamService.cs index 935a1482..32fa7ab7 100644 --- a/DecSm.Atom/Params/ParamService.cs +++ b/DecSm.Atom/Params/ParamService.cs @@ -157,10 +157,7 @@ public IDisposable CreateOverrideSourcesScope(ParamSource sources) => /// public string MaskMatchingSecrets(string text) => - _knownSecrets.Aggregate(text, - (current, knownSecret) => knownSecret is { Length: > 0 } - ? current.Replace(knownSecret, "*****", StringComparison.OrdinalIgnoreCase) - : current); + text.SanitizeSecrets(_knownSecrets); /// public T? GetParam<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( diff --git a/DecSm.Atom/Util/StringUtil.cs b/DecSm.Atom/Util/StringUtil.cs index 60eac13a..48836d06 100644 --- a/DecSm.Atom/Util/StringUtil.cs +++ b/DecSm.Atom/Util/StringUtil.cs @@ -91,5 +91,31 @@ public int GetLevenshteinDistance(string? compareTo) return @string[..maxLength] + "..."; } + + /// + /// Sanitizes a string by masking any occurrences of specified secrets with asterisks. The method handles secrets that + /// may span across line boundaries and preserves whitespace. + /// + /// An enumerable of secrets to be masked. Secrets that are null or empty will be ignored. + /// + /// The sanitized string with secrets masked, or null if the input was null or empty. If the input is + /// empty, it will be returned as is. + /// + [return: NotNullIfNotNull(nameof(@string))] + public string? SanitizeSecrets(List secrets) + { + var validSecrets = secrets + .Where(s => !string.IsNullOrEmpty(s)) + .ToList(); + + return @string is null or "" || validSecrets.Count is 0 || @string.Length < validSecrets.Min(s => s.Length) + ? @string + : validSecrets.Aggregate(@string, + static (current, secret) => current.Replace(secret, + secret.Length < 5 + ? new('*', secret.Length) + : "*****", + StringComparison.OrdinalIgnoreCase)); + } } } From b60f3cabab79d1d95263ff985e8dc95f59682573 Mon Sep 17 00:00:00 2001 From: Declan Smith Date: Wed, 11 Feb 2026 09:10:09 +1000 Subject: [PATCH 2/5] fix(logging): mask secrets in SpectreLogger log messages Ensured log messages are masked for secrets using `MaskMatchingSecrets` before processing. Added a safeguard to protect sensitive data in logging output. --- DecSm.Atom/Logging/SpectreLogger.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DecSm.Atom/Logging/SpectreLogger.cs b/DecSm.Atom/Logging/SpectreLogger.cs index 8efefa88..5daf8412 100644 --- a/DecSm.Atom/Logging/SpectreLogger.cs +++ b/DecSm.Atom/Logging/SpectreLogger.cs @@ -31,7 +31,6 @@ public IDisposable BeginScope(TState state) /// /// This method formats log messages based on their level, with colors and prefixes. It filters out Trace and Debug /// messages unless verbose logging is enabled. It also handles special formatting for process output and exceptions. - /// Secrets are not masked here; the custom Spectre console output handles masking. /// public void Log( LogLevel logLevel, @@ -121,6 +120,9 @@ public void Log( if (message is "(null)") return; + // If the message contains any secrets, we want to mask them + message = ServiceStaticAccessor.Service?.MaskMatchingSecrets(message) ?? message; + message = message.EscapeMarkup(); if (processOutput) From 289ef7a2d0198813d3deed328f0218bfa042aeb8 Mon Sep 17 00:00:00 2001 From: Declan Smith Date: Wed, 11 Feb 2026 09:41:01 +1000 Subject: [PATCH 3/5] fix(util): enhance `SanitizeSecrets` documentation for clarity Updated XML comments in `StringUtil` to improve clarity and accuracy. Enhanced descriptions for parameters, return values, and overall method behavior, ensuring consistency and better developer understanding. --- DecSm.Atom/Util/StringUtil.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/DecSm.Atom/Util/StringUtil.cs b/DecSm.Atom/Util/StringUtil.cs index 48836d06..b0b418a1 100644 --- a/DecSm.Atom/Util/StringUtil.cs +++ b/DecSm.Atom/Util/StringUtil.cs @@ -93,13 +93,17 @@ public int GetLevenshteinDistance(string? compareTo) } /// - /// Sanitizes a string by masking any occurrences of specified secrets with asterisks. The method handles secrets that - /// may span across line boundaries and preserves whitespace. + /// Sanitizes a string by replacing specified secret substrings with asterisks or a fixed mask, ensuring that the + /// secrets are not exposed in logs or outputs. /// - /// An enumerable of secrets to be masked. Secrets that are null or empty will be ignored. + /// + /// A list of secret substrings to be sanitized from the input string. Secrets that are null or empty + /// will be ignored. + /// /// - /// The sanitized string with secrets masked, or null if the input was null or empty. If the input is - /// empty, it will be returned as is. + /// The sanitized string with secrets replaced, or null if the input string was null or empty, or if no + /// valid secrets were provided. If the input string is shorter than the shortest valid secret, it will be returned + /// unchanged. /// [return: NotNullIfNotNull(nameof(@string))] public string? SanitizeSecrets(List secrets) From 4a279169edf4e66a679d8dc0e5ef52b50105983b Mon Sep 17 00:00:00 2001 From: Declan Smith Date: Wed, 11 Feb 2026 09:42:11 +1000 Subject: [PATCH 4/5] docs(params): clarify `ReplaceSecrets` XML comments for examples Updated XML comments in `ParamService` to improve clarity by including an example of the mask used for secret replacement. --- DecSm.Atom/Params/ParamService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DecSm.Atom/Params/ParamService.cs b/DecSm.Atom/Params/ParamService.cs index 32fa7ab7..8fbb6a1e 100644 --- a/DecSm.Atom/Params/ParamService.cs +++ b/DecSm.Atom/Params/ParamService.cs @@ -77,7 +77,7 @@ public interface IParamService GetParam(paramName, defaultValue); /// - /// Replaces known secret values in the provided text with a mask ("*****"). + /// Replaces known secret values in the provided text with a mask (e.g. "*****"). /// /// The text to scan and mask. /// The text with any resolved secret values replaced by a mask. From 4e6916ab123688607927926f1d4547ee0ee4072b Mon Sep 17 00:00:00 2001 From: Declan Smith Date: Wed, 11 Feb 2026 09:43:13 +1000 Subject: [PATCH 5/5] fix(logging): remove unnecessary markup escaping in SpectreLogger Removed redundant `EscapeMarkup` usage in `SpectreLogger`. This ensures the markup is properly rendered without escaping, improving the readability of log output. --- DecSm.Atom/Logging/SpectreLogger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DecSm.Atom/Logging/SpectreLogger.cs b/DecSm.Atom/Logging/SpectreLogger.cs index 5daf8412..1f065502 100644 --- a/DecSm.Atom/Logging/SpectreLogger.cs +++ b/DecSm.Atom/Logging/SpectreLogger.cs @@ -133,7 +133,7 @@ public void Log( : "dim"; var columns = new Columns(new Text(" "), - new Markup($"[{messageStyle}]{message.EscapeMarkup()}[/]").LeftJustified()).Collapse(); + new Markup($"[{messageStyle}]{message}[/]").LeftJustified()).Collapse(); ServiceStaticAccessor.Service?.Write(columns);