Serilog.Enrichers.Sensitive 1.7.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package Serilog.Enrichers.Sensitive --version 1.7.2
NuGet\Install-Package Serilog.Enrichers.Sensitive -Version 1.7.2
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Serilog.Enrichers.Sensitive" Version="1.7.2" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Serilog.Enrichers.Sensitive --version 1.7.2
#r "nuget: Serilog.Enrichers.Sensitive, 1.7.2"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Serilog.Enrichers.Sensitive as a Cake Addin
#addin nuget:?package=Serilog.Enrichers.Sensitive&version=1.7.2

// Install Serilog.Enrichers.Sensitive as a Cake Tool
#tool nuget:?package=Serilog.Enrichers.Sensitive&version=1.7.2

Serilog.Enrichers.Sensitive

build-and-test release NuGet Serilog.Enrichers.Sensitive

This is a Serilog enricher that can mask sensitive data from a LogEvent message template and its properties. Currently this supports e-mail addresses and IBAN numbers but could easily be extended to other types of data.

There are two ways of using this enricher:

  • Always mask sensitive data (default behaviour)
  • Mask data in sensitive areas only

See Usage below on how to configure this.

Possible use case

Let's say you have written a request/response logging middleware for ASP.Net Core that outputs:

Request start {method} {url} End {method} {status_code} {url} {duration}

Here you have the potential that the url property contains sensitive data because someone might do GET /api/users/?email=james.bond@universalexports.co.uk.

Of course you can write your logging middleware to capture this and that may be the best place in this situation. However there might be cases where you don't know this is likely to happen and then you end up with the e-mail address in your logging platform.

When using this enricher what you will get is that the log message that used to be:

Request start GET /api/users/?email=james.bond@universalexports.co.uk

will be:

Request start GET /api/users/?email=***MASKED***

It does not end here

Even though that you know the sensitive data will be masked, it is good practice to not log sensitive data at all.

The good thing is that with the masking applied you can add an alert to your logging platform that scans for ***MASKED*** and gives you feedback when sensitive data has been detected. That allows you to fix the problem where it originates (the logging middleware).

Usage

Always mask sensitive data

Configure your logger with the enricher:

var logger = new LoggerConfiguration()
    .Enrich.WithSensitiveDataMasking()
    .WriteTo.Console()
    .CreateLogger();

If you then have a log message that contains sensitive data:

logger.Information("This is a sensitive {Email}", "james.bond@universalexports.co.uk");

the rendered message will be logged as:

This is a sensitive ***MASKED***

the structured log event will look like (abbreviated):

{
    "RenderedMessage": "This is a sensitive ***MASKED***",
    "message": "This is a sensitive {Email}",
    "Properties.Email": "***MASKED***"
}

Mask in sensitive areas only

Configure your logger with the enricher:

var logger = new LoggerConfiguration()
    .Enrich.WithSensitiveDataMaskingInArea()
    .WriteTo.Console()
    .CreateLogger();

in your application you can then define a sensitive area:

using(logger.EnterSensitiveArea())
{
    logger.Information("This is a sensitive {Email}", "james.bond@universalexports.co.uk");
}

The effect is that the log message will be rendered as:

This is a sensitive ***MASKED***

See the Serilog.Enrichers.Sensitive.Demo app for a code example of the above.

Configuring masking operators to use

By default the enricher uses the following masking operators:

  • EmailAddressMaskingOperator
  • IbanMaskingOperator
  • CreditCardMaskingOperator

It's good practice to only configure the masking operators that are applicable for your application. For example:

new LoggerConfiguration()
    .Enrich
    .WithSensitiveDataMasking(
        options =>
        {
            options.MaskingOperators = new List<IMaskingOperator> 
            {
                new EmailAddressMaskingOperator(),
                new IbanMaskingOperator()
                // etc etc
            };
        });

It is also possible to not use any masking operators but instead mask based on property names. In that case you can configure the enricher to not use any masking operators at all:

new LoggerConfiguration()
    .Enrich
    .WithSensitiveDataMasking(
        options =>
        {
            options.MaskingOperators.Clear();
        });

Using a custom mask value

In case the default mask value ***MASKED*** is not what you want, you can supply your own mask value:

var logger = new LoggerConfiguration()
    .Enrich.WithSensitiveDataMasking(options => options.MaskValue = "**")
    .WriteTo.Console()
    .CreateLogger();

A example rendered message would then look like:

This is a sensitive value: **

You can specify any mask string as long as it's non-null or an empty string.

Customising the mask value based on the matched value

In situations where you want to change the mask and have it include parts of the matched value you can override the PreprocessMask method that takes both mask and match parameters. This allows you to perform more masks that are more dynamic.

For example: mask only the "user" part of an e-mail address.

public class CustomizedEmailAddressMaskingOperator : EmailAddressMaskingOperator
{
    protected override string PreprocessMask(string mask, Match match)
    {
        var parts = match.Value.Split('@');

        return mask + "@" + parts[1];
    }
}

When the mask is ***MASKED*** and we pass in james.bond@universalexports.co.uk the result will be ***MASKED***@universalexports.co.uk.

Note that this example uses EmailAddressMaskingOperator which has a fairly complex regular expression. If possible change your regular expression to have match groups so you can more easily access them through the match parameter.

Always mask a property

It may be that you always want to mask the value of a property regardless of whether it matches a pattern for any of the masking operators. In that case you can specify that the property is always masked:

var logger = new LoggerConfiguration()
    .Enrich.WithSensitiveDataMasking(options => options.MaskProperties.Add("email"))
    .WriteTo.Console()
    .CreateLogger();

Note: The property names are treated case-insensitive. If you specify EMAIL and the property name is eMaIL it will still be masked.

When you log any message with an email property it will be masked:

logger.Information("This is a sensitive {Email}", "this doesn't match the regex at all");

the rendered log message comes out as: "This is a sensitive ***MASKED***"

Never mask a property

It may be that you never want to mask the value of a property regardless of whether it matches a pattern for any of the masking operators. In that case you can specify that the property is never masked:

var logger = new LoggerConfiguration()
    .Enrich.WithSensitiveDataMasking(options => options.ExcludeProperties.Add("email"))
    .WriteTo.Console()
    .CreateLogger();

Note: The property names are treated case-insensitive. If you specify EMAIL and the property name is eMaIL it will still be excluded.

When you log any message with an email property it will not be masked:

logger.Information("This is a sensitive {Email}", "user@example.com");

the rendered log message comes out as: "This is a sensitive user@example.com"

Extending to additional use cases

Depending on the type of masking operation you want to perform, the RegexMaskingOperator base class is most likely your best starting point. It provides a number of extension points:

Method Purpose
ShouldMaskInput Indicate whether the operator should continue with masking the input
PreprocessInput Perform any operations on the input value before masking the input
PreprocessMask Perform any operations on the mask before masking the matched value
ShouldMaskMatch Indicate whether the operator should continue with masking the matched value from the input

To implement your own masking operator, inherit from RegexMaskingOperator, supply the regex through the base constructor and where necessary override any of the above extension points.

Then, when configuring your logger, pass your new encricher in the collection of masking operators:

var logger = new LoggerConfiguration()
    .Enrich.WithSensitiveDataMasking(options => {
        // Add your masking operator:
        options.MaskingOperators.Add(new YourMaskingOperator());
    })
    .WriteTo.Console()
    .CreateLogger();

JSON configuration

If you are configuring your logger through appsettings.json, you can configure the enricher too. You will have to add a Using section if it doesn't exist already and include the Serilog.Enrichers.Sensitive assembly name there, otherwise configuration will silently fail.

{
  "Serilog": {
    "Using": [
      "Serilog.Enrichers.Sensitive"
    ],
    "Enrich": [
      {
        "Name": "WithSensitiveDataMasking",
        "Args": {
          "options": {
            "MaskValue": "CUSTOM_MASK_FROM_JSON",
            "ExcludeProperties": [
              "email"
            ],
            "Mode": "Globally"
          }
        }
      }
    ]
  }
}

Note that options is the argument name of the WithSensitiveDataMasking extension method and must match exactly.

Masking operators

To configure masking operators you will need to specify the fully qualified name of the masking operator type. For example: MyApplication.Logging.Serilog.MyCustomMaskingOperator, MyAppliation.Logging for the type MyCustomMaskingOperator in the MyApplication.Logging assembly.

An example config file:

{
  "Serilog": {
    "Using": [
      "Serilog.Enrichers.Sensitive"
    ],
    "Enrich": [
      {
        "Name": "WithSensitiveDataMasking",
        "Args": {
          "options": {
            "MaskValue": "CUSTOM_MASK_FROM_JSON",
            "Operators": [ "MyApplication.Logging.Serilog.MyCustomMaskingOperator, MyAppliation.Logging" ]
          }
        }
      }
    ]
  }
}

Warning: Contrary to what you might expect, for JSON configuration Operators should be used instead of MaskingOperators.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages (6)

Showing the top 5 NuGet packages that depend on Serilog.Enrichers.Sensitive:

Package Downloads
Tingle.Extensions.Serilog

Extensions for working with Serilog. Including easier registration when working with different host setups, and general basics.

Org.Eclipse.TractusX.Portal.Backend.Framework.Logging

The Catena-X Portal Backend Framework library is a versatile .NET library that provides a set of powerful tools and utilities for common development tasks. This package simplifies various aspects of your application, including database interactions, asynchronous programming, file I/O, LINQ operations, logging, and database seeding.

fgli-sharedlibrary

Package Description

Carbon.Serilog

Package Description

Carbon.WebApplication.Grpc

3.2.0 - Degraded health check HTTP status code changed as custom 218 (This Is Fine) status code. Because even if system is degraed it should be working normally, so returning 5XX status code is not right for degraded state. 3.1.0 - Carbon.Common updated and Serilog.Enrichers.Sensitive Enricher added for masking sensitive values within logs. 3.0.2 - Cors Policy changes(Location parameter is added to WithExposedHeaders) 3.0.1 - Not a RC anymore - Cors Allow Credentials support added 3.0.0-preview - Dotnet 6 support with minimal and not minimal api added 2.5.1 -NetCore 3.1 target framework added 2.5.0 -Major bug fixed for UI Authorized request 401 error. Startup changed. 2.4.3 - Bearer Interceptor Included as default 2.3.0 - Can inject Interceptor as ctor parameter 2.2.10 - GRPC Web supported 2.2.6 - GRPC Extension for Carbon.WebApplication

GitHub repositories (3)

Showing the top 3 popular GitHub repositories that depend on Serilog.Enrichers.Sensitive:

Repository Stars
goatcorp/FFXIVQuickLauncher
Custom launcher for FFXIV
sunnamed434/BitMono
Unlock new level of security with BitMono. Advanced code obfuscation that protects your intellectual property like never before. Try now!
PlexRipper/PlexRipper
The best cross-platform Plex media downloader there is! In active development and feedback is very welcome!
Version Downloads Last updated
1.7.3 1,026,146 8/4/2023
1.7.2 866,275 3/4/2023
1.7.1 156,881 2/3/2023
1.7.0 307,982 1/23/2023
1.6.0 318 1/23/2023
1.5.1 467,340 10/20/2022
1.5.0 2,178 10/19/2022
1.4.0 31,761 10/11/2022
1.3.0 17,564 10/2/2022
1.2.0 51,261 9/10/2022
1.1.0 931 9/9/2022
1.0.0 1,226,909 5/31/2021
0.4.0 98,588 10/12/2020
0.3.0 553 10/12/2020
0.2.0 605 3/10/2020
0.1.2 605 3/3/2020
0.1.1 571 3/3/2020
0.1.0 937 3/2/2020