Data Redaction in ConnectSoft Microservice Template¶
Purpose & Overview¶
Data Redaction is the process of automatically masking or removing sensitive information from logs, telemetry, and responses to protect Personally Identifiable Information (PII), credentials, and other sensitive data. In the ConnectSoft Microservice Template, data redaction is implemented using Microsoft.Extensions.Compliance.Redaction and ConnectSoft.Extensions.Compliance, providing automatic, policy-driven redaction that ensures sensitive data never appears in logs or telemetry.
Data redaction provides:
- Automatic Protection: Sensitive data is automatically redacted in structured logs
- Policy-Driven: Classification-based redaction rules applied consistently
- Compliance Ready: Supports GDPR, CCPA, and other privacy regulations
- Zero Trust Logging: Ensures credentials and secrets never appear in logs
- Developer-Friendly: Simple attribute-based classification
- Performance Optimized: Low-overhead redaction for high-throughput logging
- Idempotent: Safe to apply multiple times without distortion
Redaction Philosophy
Data redaction is a security-first approach to logging and telemetry. By classifying data at the source and applying automatic redaction, we ensure that sensitive information never leaks into logs, telemetry, or error messages. The template uses a taxonomy-based classification system that maps data types to appropriate redaction strategies, ensuring consistent protection across all logging and telemetry outputs.
Architecture Overview¶
Redaction Flow¶
Application Code
├── DTOs with Classification Attributes
│ ├── [EmailData] string Email
│ ├── [PhoneData] string Phone
│ └── [SecretData] string ApiKey
├── Logger with Classified Parameters
│ └── LogInformation("User {Email}", [EmailData] email)
└── Manual Redaction
└── IRedactorProvider.GetRedactor(classification)
↓
Microsoft.Extensions.Compliance.Redaction
├── Classification → Redactor Mapping
├── Redactor Selection
└── Redaction Execution
↓
ConnectSoft.Extensions.Compliance
├── ConnectSoftTaxonomy (Classifications)
├── Custom Redactors (Email, Phone, JWT, etc.)
└── Profile Configuration
↓
Structured Logs
└── Redacted Values (Email: j**e@example.com)
Redaction Components¶
| Component | Purpose | Location |
|---|---|---|
| ConnectSoftTaxonomy | Data classification definitions | ConnectSoft.Extensions.Compliance |
| IRedactorProvider | Redactor factory and selection | Microsoft.Extensions.Compliance.Redaction |
| Redactor | Base class for redaction logic | Microsoft.Extensions.Compliance.Redaction |
| EmailRedactor | Email address masking | ConnectSoft.Extensions.Compliance |
| PhoneLast4Redactor | Phone number masking | ConnectSoft.Extensions.Compliance |
| JwtRedactor | JWT token masking | ConnectSoft.Extensions.Compliance |
| ErasingRedactor | Complete value erasure | Microsoft.Extensions.Compliance.Redaction |
Service Registration¶
AddMicroserviceRedaction Extension¶
Redaction is registered via AddMicroserviceRedaction():
// MicroserviceRegistrationExtensions.cs
services.AddMicroserviceRedaction(configuration, environment);
Implementation:
// MicroserviceRedactionExtensions.cs
internal static IServiceCollection AddMicroserviceRedaction(
this IServiceCollection services,
IConfiguration configuration,
IHostEnvironment environment)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
ArgumentNullException.ThrowIfNull(environment);
services.AddConnectSoftCompliance(configuration, environment);
return services;
}
Logging Integration¶
Redaction is automatically enabled in the logging pipeline:
// SerilogLoggingExtensions.cs
services.AddLogging(loggingBuilder =>
{
// Enable redaction of classified data in logs
loggingBuilder.EnableRedaction();
// ... rest of logging configuration
});
Data Classification Taxonomy¶
ConnectSoftTaxonomy¶
The template uses a standardized taxonomy for data classification:
// ConnectSoftTaxonomy.cs
public static class ConnectSoftTaxonomy
{
public static string Name => typeof(ConnectSoftTaxonomy).FullName!;
// PII (Personally Identifiable Information)
public static DataClassification Email => new(Name, nameof(Email));
public static DataClassification Phone => new(Name, nameof(Phone));
public static DataClassification PersonName => new(Name, nameof(PersonName));
public static DataClassification PostalAddress => new(Name, nameof(PostalAddress));
public static DataClassification IpAddress => new(Name, nameof(IpAddress));
public static DataClassification GeoLocation => new(Name, nameof(GeoLocation));
public static DataClassification GovernmentId => new(Name, nameof(GovernmentId));
// Financial Information
public static DataClassification PaymentCardPan => new(Name, nameof(PaymentCardPan));
public static DataClassification BankAccount => new(Name, nameof(BankAccount));
public static DataClassification FinancialId => new(Name, nameof(FinancialId));
// Credentials and Secrets
public static DataClassification Secret => new(Name, nameof(Secret));
public static DataClassification OAuthToken => new(Name, nameof(OAuthToken));
public static DataClassification Jwt => new(Name, nameof(Jwt));
public static DataClassification SessionId => new(Name, nameof(SessionId));
// Device and Tracking
public static DataClassification DeviceId => new(Name, nameof(DeviceId));
public static DataClassification UserAgent => new(Name, nameof(UserAgent));
// Health Information
public static DataClassification HealthInfo => new(Name, nameof(HealthInfo));
}
Classification Attributes¶
Use attributes to classify data in DTOs:
using ConnectSoft.Extensions.Compliance;
public sealed class UserDto
{
public string UserName { get; set; } // Public (no classification)
[EmailData]
public string Email { get; set; }
[PhoneData]
public string PhoneNumber { get; set; }
[SecretData]
public string ApiKey { get; set; }
[JwtData]
public string AccessToken { get; set; }
[IpAddressData]
public string? ClientIpAddress { get; set; }
[PaymentCardPan]
public string? CreditCardNumber { get; set; }
}
Available Attributes:
- [EmailData]: Email addresses
- [PhoneData]: Phone numbers
- [PaymentCardPan]: Payment card numbers
- [JwtData]: JSON Web Tokens
- [IpAddressData]: IP addresses
- [GuidData]: Device identifiers (GUIDs)
- [SecretData]: Generic secrets (passwords, API keys)
- [HealthData]: Health information (PHI)
Redactors¶
Built-in Redactors¶
The template includes custom redactors for common data types:
EmailRedactor¶
Purpose: Masks email addresses while preserving domain
Example:
Input: "john.doe@example.com"
Output: "j**e@example.com"
Input: "ab@example.com"
Output: "**@example.com" // Short local part fully masked
Characteristics: - Preserves domain name - Masks local part (characters before @) - Handles short email addresses gracefully - Idempotent (safe to apply multiple times)
PhoneLast4Redactor¶
Purpose: Shows only last 4 digits of phone numbers
Example:
Characteristics: - Preserves last 4 digits - Masks all other digits - Culture-aware formatting
PanLast4Redactor¶
Purpose: Shows only last 4 digits of payment card numbers
Example:
Characteristics: - Preserves last 4 digits - Formats as masked card number - Handles various card number formats
JwtRedactor¶
Purpose: Masks JWT tokens while preserving structure
Example:
Input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
Output: "<jwt>.<redacted>.<redacted>"
Characteristics: - Preserves JWT structure (3 parts) - Masks header and payload - Masks signature - Or erased in strict mode
IpAddressRedactor¶
Purpose: Masks IP addresses
Example:
Input (IPv4): "192.168.1.100"
Output: "192.168.1.x"
Input (IPv6): "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
Output: "2001:0db8:85a3:0000::/64"
Characteristics: - IPv4: Masks last octet - IPv6: Masks last 64 bits - Preserves network portion
ErasingRedactor¶
Purpose: Completely erases sensitive values
Example:
Characteristics: - Used for secrets, tokens, session IDs - Complete erasure (no trace) - Applied to highest-risk data
Redactor Mapping¶
Redactors are mapped to classifications during service registration:
// ConnectSoftComplianceServiceCollectionExtensions.cs
services.AddRedaction(cfg =>
{
// Erase high-risk credential tokens
cfg.SetRedactor<ErasingRedactor>(new DataClassificationSet(
ConnectSoftTaxonomy.Secret,
ConnectSoftTaxonomy.OAuthToken,
ConnectSoftTaxonomy.SessionId));
// Email / JWT: strict mode in development
var devStrict = environment?.IsDevelopment() == true && options.StrictInDevelopment;
if (devStrict)
{
cfg.SetRedactor<ErasingRedactor>(new DataClassificationSet(
ConnectSoftTaxonomy.Email,
ConnectSoftTaxonomy.Jwt));
}
else
{
cfg.SetRedactor<EmailRedactor>(new DataClassificationSet(ConnectSoftTaxonomy.Email));
cfg.SetRedactor<JwtRedactor>(new DataClassificationSet(ConnectSoftTaxonomy.Jwt));
}
// Map core custom redactors
cfg.SetRedactor<PhoneLast4Redactor>(new DataClassificationSet(ConnectSoftTaxonomy.Phone));
cfg.SetRedactor<PanLast4Redactor>(new DataClassificationSet(ConnectSoftTaxonomy.PaymentCardPan));
cfg.SetRedactor<IpAddressRedactor>(new DataClassificationSet(ConnectSoftTaxonomy.IpAddress));
cfg.SetRedactor<GuidRedactor>(new DataClassificationSet(ConnectSoftTaxonomy.DeviceId));
});
Configuration¶
ConnectSoftComplianceOptions¶
Configuration Class:
// ConnectSoftComplianceOptions.cs
public class ConnectSoftComplianceOptions
{
public const string ComplianceSectionName = "Compliance";
[Required]
required public string Profile { get; set; } = "default";
[Required]
required public bool EnableLoggingRedaction { get; set; } = true;
[Required]
required public bool StrictInDevelopment { get; set; } = true;
}
appsettings.json Configuration¶
Example Configuration:
{
"Compliance": {
"EnableLoggingRedaction": true,
"StrictInDevelopment": true,
"Profile": "default"
}
}
Configuration Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
EnableLoggingRedaction |
bool |
Enable automatic redaction in logs | true |
StrictInDevelopment |
bool |
Use stricter redaction in Development | true |
Profile |
string |
Redaction profile (default/audit/strict) | "default" |
Environment-Specific Configuration¶
Development (Strict Mode):
{
"Compliance": {
"EnableLoggingRedaction": true,
"StrictInDevelopment": true,
"Profile": "default"
}
}
Production:
{
"Compliance": {
"EnableLoggingRedaction": true,
"StrictInDevelopment": false,
"Profile": "default"
}
}
Usage Patterns¶
Pattern 1: Source-Generated Logger with Classified Parameters¶
Recommended for automatic redaction:
public static partial class UserLogs
{
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Information,
Message = "User login attempt: email={Email}, ip={IpAddress}")]
public static partial void LogLoginAttempt(
ILogger logger,
[EmailData] string email,
[IpAddressData] string ipAddress);
}
// Usage
UserLogs.LogLoginAttempt(
logger,
"john.doe@example.com",
"192.168.1.100");
// Output in logs:
// "User login attempt: email=j**e@example.com, ip=192.168.1.x"
Benefits: - Automatic redaction at callsite - Message template and structured state both redacted - Compile-time safety - Type-safe classifications
Pattern 2: DTO with LogProperties¶
For logging entire DTOs:
public sealed class LoginRequest
{
[EmailData]
public string Email { get; set; }
[SecretData]
public string Password { get; set; }
}
// Usage
private static void LogLoginRequest(ILogger logger, [LogProperties] LoginRequest request)
{
logger.LogInformation("Login request: {@Request}", request);
}
// Output in logs:
// "Login request: { Request: { Email: "j**e@example.com", Password: "" } }"
Characteristics:
- Redacts structured state automatically
- Message template not redacted (uses ToString())
- Useful for complex objects
Pattern 3: Manual Redaction¶
For custom scenarios:
public class UserService
{
private readonly IRedactorProvider redactorProvider;
public UserService(IRedactorProvider redactorProvider)
{
this.redactorProvider = redactorProvider;
}
public string MaskEmail(string email)
{
var redactor = this.redactorProvider.GetRedactor(ConnectSoftTaxonomy.Email);
Span<char> buffer = stackalloc char[redactor.GetRedactedLength(email)];
var written = redactor.Redact(email, buffer);
return new string(buffer[..written]);
}
}
Use Cases: - Custom masking logic - HTTP headers/query parameters - API responses - Error messages
Pattern 4: Classified Parameters in Controllers¶
Redact in API logging:
[HttpPost("users")]
public async Task<IActionResult> CreateUser(
[FromBody] CreateUserRequest request,
ILogger<UsersController> logger)
{
// Log with classified parameters
UserLogs.LogUserCreation(
logger,
request.Email, // Automatically redacted
request.PhoneNumber); // Automatically redacted
// ... process request
}
Redaction Profiles¶
Default Profile¶
Balanced masking: - Email: Masked (preserves domain) - Phone: Last 4 digits - PAN: Last 4 digits - JWT: Masked (preserves structure) - Secrets: Erased
Audit Profile¶
More structure preserved: - Email: Domain preserved, local part masked - Phone: Last 4 digits - PAN: Last 4 digits with formatting - JWT: Masked with structure - Secrets: Erased
Strict Profile¶
Maximum masking: - Email: Erased (or fixed mask) - Phone: Fully masked - PAN: Fully masked - JWT: Erased - Secrets: Erased
Development Strict Override¶
Development Environment:
When Environment=Development and StrictInDevelopment=true:
- Email: Erased (regardless of profile)
- JWT: Erased (regardless of profile)
- Other types: Follow profile rules
Purpose: Reduce risk during development and debugging
Best Practices¶
Do's¶
-
Classify All Sensitive Data
-
Use Source-Generated Loggers
-
Enable Redaction in Logging
-
Use Strict Mode in Development
-
Never Log Secrets Directly
Don'ts¶
-
Don't Bypass Classification
-
Don't Use String Interpolation with Sensitive Data
-
Don't Log Full Exception Details with Sensitive Data
-
Don't Disable Redaction in Production
-
Don't Manually Mask Values
Common Redaction Scenarios¶
Scenario 1: User Registration¶
public sealed class RegisterUserRequest
{
[EmailData]
public string Email { get; set; }
[PhoneData]
public string PhoneNumber { get; set; }
[SecretData]
public string Password { get; set; }
}
[LoggerMessage(Message = "Registering user: {Email}, {PhoneNumber}")]
public static partial void LogUserRegistration(
ILogger logger,
[EmailData] string email,
[PhoneData] string phoneNumber);
// Usage
LogUserRegistration(logger, request.Email, request.PhoneNumber);
// Output: "Registering user: j**e@example.com, ****4567"
Scenario 2: Payment Processing¶
public sealed class PaymentRequest
{
[PaymentCardPan]
public string CreditCardNumber { get; set; }
[EmailData]
public string BillingEmail { get; set; }
}
[LoggerMessage(Message = "Processing payment: {CardNumber}, {Email}")]
public static partial void LogPayment(
ILogger logger,
[PaymentCardPan] string cardNumber,
[EmailData] string email);
// Usage
LogPayment(logger, request.CreditCardNumber, request.BillingEmail);
// Output: "Processing payment: **** **** **** 1234, j**e@example.com"
Scenario 3: API Authentication¶
public sealed class ApiRequest
{
[JwtData]
public string AuthorizationToken { get; set; }
[IpAddressData]
public string ClientIpAddress { get; set; }
}
[LoggerMessage(Message = "API request: token={Token}, ip={Ip}")]
public static partial void LogApiRequest(
ILogger logger,
[JwtData] string token,
[IpAddressData] string ip);
// Usage
LogApiRequest(logger, request.AuthorizationToken, request.ClientIpAddress);
// Output: "API request: token=<jwt>.<redacted>.<redacted>, ip=192.168.1.x"
Troubleshooting¶
Issue: Data Not Being Redacted¶
Symptoms: Sensitive data appears in logs without masking.
Solutions: 1. Verify Redaction is Enabled
-
Check Logging Integration
-
Verify Classification Attributes
-
Check Logger Message Format
Issue: Redaction Too Aggressive¶
Symptoms: Needed information is being erased or masked too heavily.
Solutions: 1. Check Profile Setting
-
Disable Strict Mode in Development
-
Review Redactor Mappings
- Check if
ErasingRedactoris applied when masking is needed - Verify profile-specific redactor selection
Issue: Performance Impact¶
Symptoms: Logging performance degradation with redaction enabled.
Solutions:
1. Redaction is Optimized
- Redactors use Span<char> for zero-allocation redaction
- Pre-computed redacted lengths
- Minimal overhead in hot paths
- Profile Performance
- Measure actual impact (usually < 1% overhead)
- Use source-generated loggers for best performance
- Avoid string interpolation in logging
Related Documentation¶
- Logging: Comprehensive logging documentation including redaction patterns
- HTTP Logging: HTTP request/response logging with redaction
- Exception Handling: Exception logging with redaction
Summary¶
Data redaction in the ConnectSoft Microservice Template provides:
- ✅ Automatic Protection: Sensitive data automatically redacted in logs
- ✅ Taxonomy-Based Classification: Standardized data classification system
- ✅ Custom Redactors: Specialized redactors for email, phone, JWT, PAN, etc.
- ✅ Policy-Driven: Configurable profiles and strict mode
- ✅ Zero Trust Logging: Credentials and secrets never appear in logs
- ✅ Developer-Friendly: Simple attribute-based classification
- ✅ Performance Optimized: Low-overhead redaction for high-throughput
- ✅ Compliance Ready: Supports GDPR, CCPA, and other privacy regulations
By following these patterns, teams can:
- Protect Sensitive Data: Automatically mask PII and credentials in logs
- Ensure Compliance: Meet privacy regulations with policy-driven redaction
- Maintain Security: Zero trust approach to logging and telemetry
- Simplify Development: Attribute-based classification with automatic enforcement
- Optimize Performance: Minimal overhead with optimized redactors
Data redaction is an essential security feature that ensures sensitive information never leaks into logs, telemetry, or error messages, protecting both users and organizations from data breaches and compliance violations.