diff --git a/Directory.Build.props b/Directory.Build.props index 120f5a8..cfbe36c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ 1.7.4.0 - Sander van Vliet, Huibert Jan Nieuwkamer, Scott Toberman + Sander van Vliet, Huibert Jan Nieuwkamer, Scott Toberman, sunnamed434 Codenizer BV 2023 Sander van Vliet diff --git a/README.md b/README.md index 3f182d4..0da8c91 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ By default the enricher uses the following masking operators: - EmailAddressMaskingOperator - IbanMaskingOperator - CreditCardMaskingOperator +- PathMaskingOperator It's good practice to only configure the masking operators that are applicable for your application. For example: diff --git a/src/Serilog.Enrichers.Sensitive.Demo/Program.cs b/src/Serilog.Enrichers.Sensitive.Demo/Program.cs index fcafa5a..b97b7dd 100644 --- a/src/Serilog.Enrichers.Sensitive.Demo/Program.cs +++ b/src/Serilog.Enrichers.Sensitive.Demo/Program.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Threading.Tasks; using Serilog.Core; @@ -13,7 +14,8 @@ static async Task Main(string[] args) { new EmailAddressMaskingOperator(), new IbanMaskingOperator(), - new CreditCardMaskingOperator(false) + new CreditCardMaskingOperator(false), + new PathMaskingOperator() }) .WriteTo.Console() .CreateLogger(); @@ -43,16 +45,24 @@ static async Task Main(string[] args) Key = 12345, XmlValue = "4111111111111111" }; logger.Information("Object dump with embedded credit card: {x}", x); + + // Path to the file is also masked + logger.Information("This is path to the file: {0}", + @"E:\SuperSecretPath\test.txt"); + + // Path to the directory is also masked + logger.Information("This is path to the directory: {0}", @"C:\Admin\"); } // But outside the sensitive area nothing is masked logger.Information("Felix can be reached at felix@cia.gov"); + logger.Information("This is your path to the file: {file}", new FileInfo("test.txt").FullName); // Now, show that this works for async contexts too logger.Information("Now, show the Async works"); - + var t1 = LogAsSensitiveAsync(logger); var t2 = LogAsUnsensitiveAsync(logger); diff --git a/src/Serilog.Enrichers.Sensitive/PathMaskingOperator.cs b/src/Serilog.Enrichers.Sensitive/PathMaskingOperator.cs new file mode 100644 index 0000000..ff23b91 --- /dev/null +++ b/src/Serilog.Enrichers.Sensitive/PathMaskingOperator.cs @@ -0,0 +1,38 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text.RegularExpressions; + +namespace Serilog.Enrichers.Sensitive; + +/// +/// Represents a masking operator for path names. +/// Supports path names for Windows, Linux, and macOS. +/// +public class PathMaskingOperator : RegexMaskingOperator +{ + private readonly bool _keepLastPartOfPath; + private const string PathPattern = + @"^(?:[a-zA-Z]\:|\\\\[\w-]+\\[\w-]+\$?|[\/][^\/\0]+)+(\\[^\\/:*?""<>|]*)*(\\?)?$"; + + /// + /// Initializes a new instance of the class. + /// + /// If set to then the mask will keep together with file name or its directory name. + public PathMaskingOperator(bool keepLastPartOfPath = true) : base(PathPattern) + { + _keepLastPartOfPath = keepLastPartOfPath; + } + + [SuppressMessage("ReSharper", "InvertIf")] + protected override string PreprocessMask(string mask, Match match) + { + if (_keepLastPartOfPath) + { + var value = match.Value; + return Path.GetExtension(value) == string.Empty + ? mask + new DirectoryInfo(value).Name + : mask + Path.GetFileName(value); + } + return mask; + } +} \ No newline at end of file diff --git a/src/Serilog.Enrichers.Sensitive/SensitiveDataEnricher.cs b/src/Serilog.Enrichers.Sensitive/SensitiveDataEnricher.cs index 75176f3..0763e8a 100644 --- a/src/Serilog.Enrichers.Sensitive/SensitiveDataEnricher.cs +++ b/src/Serilog.Enrichers.Sensitive/SensitiveDataEnricher.cs @@ -344,7 +344,8 @@ private string MaskWithOptions(string maskValue, MaskOptions options, string inp { new EmailAddressMaskingOperator(), new IbanMaskingOperator(), - new CreditCardMaskingOperator() + new CreditCardMaskingOperator(), + new PathMaskingOperator() }; } diff --git a/src/Serilog.Enrichers.Sensitive/Serilog.Enrichers.Sensitive.csproj b/src/Serilog.Enrichers.Sensitive/Serilog.Enrichers.Sensitive.csproj index 6179cda..b208223 100644 --- a/src/Serilog.Enrichers.Sensitive/Serilog.Enrichers.Sensitive.csproj +++ b/src/Serilog.Enrichers.Sensitive/Serilog.Enrichers.Sensitive.csproj @@ -12,7 +12,7 @@ Serilog enricher to mask sensitive data An enricher to be used for masking sensitive (PII) data using Serilog 2023 Sander van Vliet - Sander van Vliet, Huibert Jan Nieuwkamer, Scott Toberman + Sander van Vliet, Huibert Jan Nieuwkamer, Scott Toberman, sunnamed434 https://github.com/serilog-contrib/Serilog.Enrichers.Sensitive/ MIT https://github.com/serilog-contrib/Serilog.Enrichers.Sensitive/ diff --git a/test/Serilog.Enrichers.Sensitive.Tests.Unit/WhenMaskingPaths.cs b/test/Serilog.Enrichers.Sensitive.Tests.Unit/WhenMaskingPaths.cs new file mode 100644 index 0000000..4380acd --- /dev/null +++ b/test/Serilog.Enrichers.Sensitive.Tests.Unit/WhenMaskingPaths.cs @@ -0,0 +1,39 @@ +using FluentAssertions; +using Xunit; + +namespace Serilog.Enrichers.Sensitive.Tests.Unit; + +public class WhenMaskingPaths +{ + private const string Mask = @"***\"; + + [Theory] + [InlineData(@"C:\Users\Admin\Secret\File.dll", @"***\", false)] + [InlineData(@"C:\Users\Admin\Secret\File.dll", @"***\File.dll", true)] + [InlineData(@"C:\Users\Admin\Secret\Hidden\File.dll", @"***\File.dll", true)] + [InlineData(@"C:\Users\Admin\Secret\Hidden", @"***\", false)] + [InlineData(@"C:\Users\Admin\Secret\Hidden", @"***\Hidden", true)] + [InlineData(@"C:\Users\Admin\Secret", @"***\Secret", true)] + [InlineData(@"C:\Users\", @"***\Users", true)] + [InlineData(@"/home/i_use_arch_linux_btw", @"***\i_use_arch_linux_btw", true)] + [InlineData(@"/home/i_use_arch_linux_btw", @"***\", false)] + [InlineData(@"C:\", @"***\C:\", true)] + [InlineData(@"C:\", @"***\", false)] + [InlineData("File.txt", "File.txt", false)] + [InlineData(@"This is not a path", "This is not a path", false)] + public void GivenPaths_ReturnsExpectedResult(string path, string result, bool combineMaskWithPath) + { + TheMaskedResultOf(path, combineMaskWithPath) + .Should() + .Be(result); + } + + private static string TheMaskedResultOf(string input, bool combineMaskWithPath) + { + var maskingResult = new PathMaskingOperator(combineMaskWithPath) + .Mask(input, Mask); + return maskingResult.Match + ? maskingResult.Result + : input; + } +} \ No newline at end of file