Prevent sensitive data exposure in log with Serilog
Tags: csharp, security, dotnet
Published: 2022-07-29 08:38:40

## 1. Problem

Writing log when developing the application will help the developer to easy debugging and tracing. But writing a good log is not enough, because some sensitive data maybe exposed in log such as: password, account number or something like that.

## 2. Idea

To prevent this issue, we need to replace all sensitive words to any mask character.
The purpose of this article is help you implement the simple Sensitive data destructing policy with Serilog - one of common logger extension in .NET Core. By applying this policy you can prevent the sensitive data exposure in you log. It's enough, let's go through the code...

## 3. Implementation
### 3.1. Tech stack

- .NET Core (on this article, I use .NET 6.0)
- Serilog

## 3.2 Show the code

- To implement the Destructing Policy we need to implement the interface **IDestructuringPolicy** from namespace **Serilog.Core**

```c#
public class SensitiveDataDestructuringPolicy : IDestructuringPolicy
{
    public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
    {
        throw new NotImplementedException();
    }
}
```

- Now, we need to define a mask value (such as * character) and list of sensitive keywords

```C#
var mask = "******";
var sensitiveKeywords = new List<string> {
    "Password", "NewPassword",
};
```
Actually, we should define the list of sensitive keywords some where such as Configuration file.

- We will lookup all **Log Event Properties**, if we found any sensitive keyword in the log object, we will create a new **Log Event Property** with the mask value and add the new this property to the new list of  **Log Event Properties**, for non sensitive keyword, we also create a new **Log Event Property**, but keep the original value. After that, just return the result.

Bellow is complete code.

```c#
using Microsoft.Extensions.Configuration;
using Serilog.Core;
using Serilog.Events;
using System.Reflection;

namespace Microsoft.Extensions.Logging;

public class SensitiveDataDestructuringPolicy : IDestructuringPolicy
{
    private const string DEFAULT_MASK_VALUE = "******";
    private const string SENSITIVE_KEYWORDS_SECTION = "Logging:SensitiveData:Keywords";
    private const string MASK_VALUE = "Logging:SensitiveData:Mask";

    private readonly IConfiguration _configuration;

    public SensitiveDataDestructuringPolicy(IConfiguration configuration)
    {
        _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
    }

    public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
    {
        var sensitiveKeywords = _configuration.GetValue<string[]>(SENSITIVE_KEYWORDS_SECTION) ?? Array.Empty<string>();
        var maskValue = _configuration.GetValue<string>(MASK_VALUE) ?? DEFAULT_MASK_VALUE;

        if (!sensitiveKeywords.Any())
        {
            result = new StructureValue(new LogEventProperty[] { });

            return false;
        }

        var props = value.GetType().GetTypeInfo().DeclaredProperties;
        var logEventProperties = new List<LogEventProperty>();

        foreach (var propertyInfo in props)
        {
            if (sensitiveKeywords.Any(x => x.Equals(propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase)))
            {
                logEventProperties.Add(new LogEventProperty(propertyInfo.Name, propertyValueFactory.CreatePropertyValue(maskValue)));
            }
            else
            {
                logEventProperties.Add(new LogEventProperty(propertyInfo.Name, propertyValueFactory.CreatePropertyValue(propertyInfo.GetValue(value))));
            }
        }

        result = new StructureValue(logEventProperties);

        return true;
    }
}

```

- Finally, add the **SensitiveDataDestructuringPolicy** to the **loggerConfiguration** as below:

```C#
# Rest of code
.UseSerilog((context, services, configuration) =>
{
    var loggerConfiguration = configuration
        .ReadFrom.Configuration(context.Configuration)
        .Enrich.FromLogContext()
        .Enrich.WithProperty("Application", context.HostingEnvironment.ApplicationName)
        .Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
        .MinimumLevel.Information();

    loggerConfiguration.Destructure.With<SensitiveDataDestructuringPolicy>();
});
```

## 4. Concluding
By applying the **SensitiveDataDestructuringPolicy**, you will make the log safer, that help to prevent the sensitive data exposing while tracing log.

*Notes: The sample above just a very simple example, and you can modify, optimize to fit with your project.*

*This article was originally published at: https://sangau.me/prevent-sensitive-data-exposure-in-log-with-serilog*