LeakGuard 0.4.0
dotnet add package LeakGuard --version 0.4.0
NuGet\Install-Package LeakGuard -Version 0.4.0
<PackageReference Include="LeakGuard" Version="0.4.0" />
<PackageVersion Include="LeakGuard" Version="0.4.0" />
<PackageReference Include="LeakGuard" />
paket add LeakGuard --version 0.4.0
#r "nuget: LeakGuard, 0.4.0"
#:package LeakGuard@0.4.0
#addin nuget:?package=LeakGuard&version=0.4.0
#tool nuget:?package=LeakGuard&version=0.4.0
LeakGuard
Prevent sensitive data from leaking into your .NET logs.
LeakGuard is a developer-first Roslyn analyzer that detects when values marked as PII, secrets, or sensitive data are passed directly to common logging APIs. Diagnostics appear at compile time in your IDE and in CI/CD pipelines — before sensitive data ever reaches production logs.
v0.4 adds object logging protection (LG004) so entire objects whose types contain LeakGuard-classified members are flagged when passed to ILogger.
v0.3 adds intra-procedural data-flow detection so sensitive values tracked through local variables, common string operations, and control-flow joins are flagged before they reach logging sinks.
v0.2 adds runtime redaction helpers and IDE quick fixes so you can resolve warnings safely without hand-editing every call site.
The problem
public sealed class Customer
{
[Pii(PiiType.Email)]
public string Email { get; init; } = "";
}
logger.LogInformation(
"Created customer {Email}",
customer.Email);
LG001: PII value Customer.Email may be exposed in logging.
Without LeakGuard, this compiles cleanly. With LeakGuard, you get an immediate, actionable warning pointing at the sensitive expression.
Quick fix (Visual Studio / Rider)
Place the caret on the warning and choose the light bulb quick action:
Redact email
logger.LogInformation(
"Created customer {Email}",
customer.Email.RedactEmail());
At runtime:
Created customer lu***@example.com
Diagnostics and quick fixes work in Visual Studio, JetBrains Rider, dotnet build, and CI.
Why LeakGuard?
- Explicit by design — Sensitivity is declared with attributes, not guessed from property names. Low false positives.
- Compile-time safety — Catches leaks before they ship.
- Actionable fixes — Redact email, phone, secrets, and generic sensitive values with one click.
- Runtime redaction — Best-effort masking when you must include a placeholder in structured logs.
- Incremental adoption — Mark sensitive members as you go; no logging framework changes required.
- Small and fast — A focused Roslyn analyzer; analysis stays lightweight.
- Ecosystem-friendly — Complements
Microsoft.Extensions.Logging; not a replacement for it.
Installation
dotnet add package LeakGuard
One package includes attributes, runtime redaction helpers, Roslyn analyzers (LG001–LG004), and IDE code fixes.
Quick start
using LeakGuard;
using Microsoft.Extensions.Logging;
public sealed class Customer
{
[Pii(PiiType.Email)]
public string Email { get; init; } = "";
}
public class CustomerService(ILogger<CustomerService> logger)
{
public void Create(Customer customer)
{
// Safe
logger.LogInformation("Customer created {CustomerId}", customer.Id);
// Warning LG001: PII value 'customer.Email' may be exposed in logging.
logger.LogInformation("Created customer {Email}", customer.Email);
}
}
Apply Redact email, rebuild, and the warning disappears.
Data-flow detection
LeakGuard tracks sensitive values through local assignments and common value-preserving operations within a single method.
[Pii(PiiType.Email)]
public string Email { get; init; } = "";
var email = customer.Email;
var normalized = email.Trim().ToLowerInvariant();
logger.LogInformation(
"Customer {Email}",
normalized);
LG001: PII value originating from Customer.Email may be exposed in logging.
Supported propagation includes:
- Local variables and reassignment (
email = customer.Email, thencopy = email) - String operations such as
Trim,ToLowerInvariant,Replace, andSubstring - String interpolation and concatenation
- Conditional expressions and null-coalescing (
condition ? customer.Email : safe,customer.Email ?? fallback) - Sensitive parameters marked with
[Pii],[Secret], or[SensitiveData] - Basic control-flow joins (if one branch assigns sensitive data, the merged value is treated as potentially sensitive)
- Sanitized values via LeakGuard's own
Redact*extension methods break taint
Classification merge precedence when branches differ: Secret > Pii > SensitiveData.
Data-flow limitations (v0.3)
Current analysis is intra-procedural only. LeakGuard does not yet track sensitive data through:
- Arbitrary method calls (unless they are known taint-preserving string operations)
- Cross-method or cross-file data flow
- Repository/service boundaries, DTO mapping, serialization, or collection contents
- Reflection or external libraries
- Captured locals in complex closures, async continuations, or loops with back-edges (best-effort only)
var email = Normalize(customer.Email); // custom helper
logger.LogInformation("{Email}", email); // not tracked unless Normalize is recognized
Direct logging of marked members continues to work alongside data-flow detection.
Object logging protection
Developers often log entire objects without realizing the type contains sensitive members — or sensitive properties may be added later:
public sealed class Customer
{
public Guid Id { get; init; }
[Pii(PiiType.Email)]
public string Email { get; init; } = "";
[Pii(PiiType.Phone)]
public string Phone { get; init; } = "";
}
logger.LogInformation(
"Customer {@Customer}",
customer);
LG004: Object 'Customer' contains sensitive data and may be exposed in logging.
This is separate from value-level diagnostics:
| Scenario | Diagnostic |
|---|---|
logger.LogInformation("{Email}", customer.Email) |
LG001 on customer.Email |
logger.LogInformation("{@Customer}", customer) |
LG004 on customer |
logger.LogInformation("{@Product}", product) |
No diagnostic when Product has no LeakGuard attributes |
LG004 inspects the compile-time type of each logging argument for instance properties and fields marked with [Pii], [Secret], or [SensitiveData], including members inherited from base types. Records and local aliases (var value = customer) are supported.
Object logging limitations (v0.4)
- Direct and inherited members only — no recursive object-graph traversal (e.g.
Order.Customer.Emailthrough nested objects) - No collection element inspection (
List<Customer>is not analyzed) - Compile-time type only — no speculative diagnostics for
objector broad interface types without classified members - Does not replace LG001–LG003 for individual sensitive values
Runtime redaction
The LeakGuard package provides small, dependency-free extension methods:
| Method | Use for | Example output |
|---|---|---|
RedactEmail() |
Email addresses | lu***@example.com |
RedactPhone() |
Phone numbers | 070****567 |
Redact() |
Generic sensitive data | *** |
RedactSecret() |
Secrets (API keys, passwords, tokens) | *** |
Nullable strings are handled naturally — null stays null; empty strings stay empty.
Email masking
Algorithm (deterministic, never throws):
- Keep the domain after
@ - Keep up to two characters of the local part, then mask the rest with
*** - Fall back to generic
***when no@is found
| Input | Output |
|---|---|
ludvig@example.com |
lu***@example.com |
ab@example.com |
a***@example.com |
a@example.com |
***@example.com |
invalid-email |
*** |
Phone masking
- Extract digits, keep a short prefix (up to 3 digits) and suffix (3 digits)
- Mask middle digits in place, preserving separators where present
- Not a full international phone parser — intentionally simple and predictable
| Input | Output |
|---|---|
0701234567 |
070****567 |
+46701234567 |
+467*****567 |
Secret handling
RedactSecret() always returns ***. No prefix or suffix of the original value is preserved.
Prefer not logging secrets at all. Redaction is a last resort when a log line must retain a placeholder.
Code fixes
The package includes a Roslyn CodeFixProvider for:
| Diagnostic | Quick fix | Result |
|---|---|---|
LG001 + [Pii(PiiType.Email)] |
Redact email | .RedactEmail() |
LG001 + [Pii(PiiType.Phone)] |
Redact phone number | .RedactPhone() |
| LG001 + other PII types | Redact sensitive value | .Redact() |
LG002 + [Secret] |
Redact secret | .RedactSecret() |
LG003 + [SensitiveData] |
Redact sensitive value | .Redact() |
Fix All is supported via BatchFixer for multiple occurrences of the same fix.
When using LeakGuard; is missing, the fix adds it automatically (including file-scoped namespaces).
Already-redacted values do not trigger diagnostics:
logger.LogInformation("{Email}", customer.Email.RedactEmail()); // OK
Redaction calls are recognized by semantic symbol matching on LeakGuard's own extension methods — unrelated user methods named Redact() are not trusted automatically.
PII vs SensitiveData vs Secret
| Attribute | Purpose | Typical examples |
|---|---|---|
[Pii] / [Pii(PiiType.*)] |
Personally identifiable information | Email, phone, name, address |
[SensitiveData] |
Sensitive business data that must not appear in logs | Internal notes, contract terms |
[Secret] |
Credentials and tokens | API keys, passwords, access tokens |
Attributes can be applied to properties, fields, parameters, and return values.
PiiType values
Unknown, Name, Email, Phone, Address, PersonalIdentifier, Financial, Location
Specialized redactors for Name, Address, and other types may be added later. They currently use generic Redact().
Supported logging APIs
LeakGuard analyzes direct arguments passed to Microsoft.Extensions.Logging.ILogger extension methods:
LogTrace,LogDebug,LogInformation,LogWarning,LogError,LogCritical
Both standard and Exception-first overloads are supported. Methods are identified semantically via Roslyn symbols.
Diagnostics
| ID | Severity | Message |
|---|---|---|
| LG001 | Warning | PII value '{member}' may be exposed in logging. |
| LG001 | Warning | PII value originating from '{source}' may be exposed in logging. (propagated) |
| LG002 | Warning | Secret value '{member}' may be exposed in logging. |
| LG002 | Warning | Secret value originating from '{source}' may be exposed in logging. (propagated) |
| LG003 | Warning | Sensitive data value '{member}' may be exposed in logging. |
| LG003 | Warning | Sensitive data value originating from '{source}' may be exposed in logging. (propagated) |
| LG004 | Warning | Object '{type}' contains sensitive data and may be exposed in logging |
Diagnostics highlight the sensitive expression (e.g. customer.Email) or the logged object (e.g. customer), not the entire logging call.
Analyzer behavior
- Attribute-based only — Property names like
EmailorPasswordare not classified automatically. - Direct, data-flow, and object logging — Individual sensitive values, tracked locals, and whole objects whose types contain classified members are flagged.
- Redaction-aware — LeakGuard's own
Redact*extension methods break taint and are treated as safe. - Generated code excluded — Source generators and generated files are not analyzed.
- Concurrent and scoped — Methods containing logging sinks are analyzed with CFG-based intra-procedural taint tracking.
This is flagged in v0.3:
var email = customer.Email;
logger.LogInformation("{Email}", email); // LG001
Reassignment is handled conservatively:
var email = customer.Email;
email = "safe";
logger.LogInformation("{Email}", email); // OK when overwrite is proven
Security considerations
LeakGuard is a developer safety tool. It is not:
- a complete GDPR compliance solution
- a DLP system
- a guarantee that sensitive data can never leak
- a substitute for correct logging policies
- a substitute for access controls
Runtime redaction algorithms produce best-effort safe representations for logs. They reduce accidental exposure but do not remove the underlying risk of logging sensitive data.
Secrets should ideally not be logged at all. Use redaction only when a placeholder is unavoidable, and prefer removing the secret from the log message entirely.
Performance
LeakGuard follows Roslyn analyzer best practices:
- Concurrent execution enabled
- Generated code excluded from analysis
- Methods containing logging sinks are analyzed with CFG-based data-flow tracking; others are skipped early
- No compilation-wide scans per syntax tree
The analyzer is designed to be invisible during normal IDE usage. Code fixes run only when you invoke them.
Roadmap
v0.4 (current)
- Object logging protection (LG004) for types containing LeakGuard-classified members
- Compilation-scoped type inspection cache for performance
v0.3
- Intra-procedural data-flow / taint tracking for locals, string ops, and control-flow joins
- Propagated diagnostic messages that reference the original sensitive source
v0.2
- Runtime redaction API (
Redact,RedactEmail,RedactPhone,RedactSecret) - IDE code fixes for LG001/LG002/LG003
- Redaction-aware analyzer (no repeated diagnostics)
v0.4
- Serilog support
- OpenTelemetry support
v1.0
- Stable analyzer API
- Configurable policies
- Custom sensitive attributes
Contributing
Contributions are welcome! Please open an issue to discuss significant changes before submitting a pull request.
- Fork the repository
- Create a feature branch
- Add tests for analyzer and code-fix behavior
- Ensure
dotnet buildanddotnet testpass - Open a pull request
License
MIT — see LICENSE.
| Product | Versions 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.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. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.4.0 | 115 | 8/6/2026 |