Skip to main content

Data Masking

Data masking protects sensitive information (PII/PHI) by replacing identifiable patterns with masked values in logs, error messages, and API responses.

Before You Start

  • .NET 8.0+ (or .NET 9/10 for latest features)
  • Install the required packages:
    dotnet add package Excalibur.Dispatch.Security
  • Familiarity with encryption providers and GDPR erasure

Overview

flowchart LR
subgraph Input
RAW[Raw Data]
end

subgraph Masking
DM[IDataMasker]
RULES[MaskingRules]
end

subgraph Output
SAFE[Safe Data]
end

RAW --> DM
RULES --> DM
DM --> SAFE

Quick Start

Configuration

services.AddDataMasking();

// Or with specific compliance mode
services.AddHipaaDataMasking(); // PHI patterns
services.AddPciDssDataMasking(); // Credit card only
services.AddStrictDataMasking(); // All patterns

Basic Usage

public class LoggingService
{
private readonly IDataMasker _masker;

public LoggingService(IDataMasker masker)
{
_masker = masker;
}

public void LogMessage(string message)
{
// Mask all sensitive patterns before logging
var safeMessage = _masker.MaskAll(message);
_logger.LogInformation(safeMessage);
}
}

Masking Patterns

Standard Patterns

PatternExampleMaskedRule Property
Email[email protected]j***@e***.comMaskEmail
Phone555-123-4567***-***-4567MaskPhone
SSN123-45-6789***-**-6789MaskSsn
Credit Card4111-1111-1111-1111****-****-****-1111MaskCardNumber
IP Address192.168.1.100***.***.***.100MaskIpAddress
Date of Birth12/25/1990**/**/****MaskDateOfBirth

Pattern Details

Credit card masking preserves the last 4 digits for verification while hiding the PAN:

var input = "Card: 4111-1111-1111-1111";
var masked = _masker.Mask(input, MaskingRules.PciDss);
// Result: "Card: ****-****-****-1111"

SSN masking preserves the last 4 digits per IRS guidelines:

var input = "SSN: 123-45-6789";
var masked = _masker.Mask(input, MaskingRules.Default);
// Result: "SSN: ***-**-6789"

Masking Rules

Default Rules

var rules = MaskingRules.Default;
// Enables: Email, Phone, SSN, Credit Card
// Disables: IP Address, Date of Birth

Compliance-Specific Rules

// PCI-DSS: Credit card numbers only
var pciRules = MaskingRules.PciDss;

// HIPAA: All PHI patterns
var hipaaRules = MaskingRules.Hipaa;
// Enables: Email, Phone, SSN, IP Address, Date of Birth

// Strict: All patterns enabled
var strictRules = MaskingRules.Strict;

Custom Rules

var customRules = new MaskingRules
{
MaskEmail = true,
MaskPhone = true,
MaskSsn = true,
MaskCardNumber = false, // Disable card masking
MaskIpAddress = true,
MaskDateOfBirth = true,
MaskCharacter = '#' // Use # instead of *
};

services.AddDataMasking(customRules);

Object Masking

Automatic Property Masking

Mark properties with attributes for automatic masking:

public class CustomerDto
{
public string Name { get; set; }

[PersonalData(MaskInLogs = true)]
public string Email { get; set; }

[PersonalData(MaskInLogs = true)]
public string PhoneNumber { get; set; }

[Sensitive(MaskInLogs = true)]
public string SocialSecurityNumber { get; set; }
}

Mask Objects

var customer = new CustomerDto
{
Name = "John Doe",
Email = "[email protected]",
PhoneNumber = "555-123-4567",
SocialSecurityNumber = "123-45-6789"
};

var masked = _masker.MaskObject(customer);
// Email: j***@e***.com
// PhoneNumber: ***-***-4567
// SocialSecurityNumber: ***-**-6789

Integration Patterns

Logging Middleware

public class MaskingLoggingMiddleware : IDispatchMiddleware
{
private readonly IDataMasker _masker;

public async ValueTask<IMessageResult> InvokeAsync(
IDispatchMessage message,
IMessageContext context,
DispatchRequestDelegate next,
CancellationToken ct)
{
// Mask message before logging
var safeMessage = _masker.MaskObject(message);
_logger.LogInformation("Processing: {@Message}", safeMessage);

return await next(message, context, ct);
}
}

Exception Handling

public class SafeExceptionHandler
{
private readonly IDataMasker _masker;

public string GetSafeErrorMessage(Exception ex)
{
// Mask any PII in exception messages
return _masker.MaskAll(ex.Message);
}
}

API Responses

public class MaskingActionFilter : IAsyncActionFilter
{
private readonly IDataMasker _masker;

public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
var result = await next();

if (result.Result is ObjectResult objectResult)
{
objectResult.Value = _masker.MaskObject(objectResult.Value);
}
}
}

Performance

The masker uses compiled regex patterns with timeout guards for optimal performance:

MetricValue
Overhead< 2% per operation
Regex timeout100ms (ReDoS protection)
Pattern cachingStatic compiled patterns
Thread safetyFully thread-safe
// Patterns are compiled once at startup
// Safe for concurrent access from multiple threads
var masker = new RegexDataMasker(MaskingRules.Default);

Security Considerations

ReDoS Protection

All regex patterns include timeout guards to prevent Regular Expression Denial of Service:

// Internal implementation uses:
new Regex(pattern, RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));

Partial Visibility

Some patterns preserve partial data for verification:

PatternPreservedPurpose
Credit CardLast 4 digitsReceipt verification
SSNLast 4 digitsIRS compliance
PhoneLast 4 digitsCaller verification
IPLast octetNetwork troubleshooting

Attribute-Based Discovery

Use attributes to mark sensitive properties at the source:

// PersonalData - GDPR Article 4(1) personal data
[PersonalData(Category = PersonalDataCategory.Identity)]
public string Email { get; set; }

// Sensitive - General sensitive data
[Sensitive(Classification = DataClassification.Confidential)]
public string ApiKey { get; set; }

Testing

Verify Masking

[Fact]
public void Should_Mask_CreditCard_Preserving_Last4()
{
var masker = new RegexDataMasker(MaskingRules.PciDss);
var input = "Card: 4111-1111-1111-1111";

var result = masker.Mask(input, MaskingRules.PciDss);

result.ShouldBe("Card: ****-****-****-1111");
}

[Fact]
public void Should_Mask_Multiple_Patterns()
{
var masker = new RegexDataMasker(MaskingRules.Strict);
var input = "Email: [email protected], Phone: 555-123-4567";

var result = masker.MaskAll(input);

result.ShouldContain("j***@e***.com");
result.ShouldContain("***-***-4567");
}

Best Practices

PracticeRecommendation
Default maskingEnable in all environments
Compliance modeMatch your regulatory requirements
Log reviewAudit logs periodically for unmasked data
Attribute usageMark all PII/PHI at definition time
Custom patternsAdd organization-specific patterns as needed

Next Steps

See Also