Your Privacy Docs Are Fiction: Let’s Fix That with .NET CLI Tools
Every quarter, your compliance team gathers around conference tables reviewing spreadsheets that claim your organization processes personal data lawfully. Meanwhile, your production databases collect new PII fields nobody documented, consent records expire without notification, and deletion requests sit in ticketing systems with no automated verification they were honored. You’re not non-compliant because you don’t care. You’re non-compliant because manual privacy audits cannot keep pace with continuous deployment.
Here’s the uncomfortable truth: privacy compliance is a continuous state, not a quarterly checkpoint. Every code deployment potentially introduces new PII processing. Every schema migration might create compliance gaps. Every API endpoint modification could violate documented privacy purposes. Manual audits examine yesterday’s system while today’s changes accumulate unreviewed. By the time the next audit rolls around, you’ve already shipped three months of undocumented data collection.
This article demonstrates building .NET CLI tools that automate privacy audit workflows. We’ll examine why manual approaches fail, then construct automated solutions using reflection for PII discovery, Entity Framework metadata for database schema analysis, and GitHub Actions for continuous compliance monitoring.
The Fatal Manual Privacy Audit Pattern
Let’s examine what “compliance” looks like in organizations that rely on quarterly manual reviews:
Schema Drift: The Silent Compliance Killer
public class UserProfile
{
public Guid Id { get; set; }
public string Email { get; set; }
public string FullName { get; set; }
// Added in sprint 47 - privacy docs? What privacy docs?
public string SocialSecurityNumber { get; set; }
public string BiometricHash { get; set; }
}
Sensitive PII hits production while compliance reviews migration files from three months ago. By the next audit, you’ve collected biometric data for an entire quarter without documented legal basis. The spreadsheet says you’re compliant. Reality disagrees.
The Consent Expiration Time Bomb
Your consent records have expiration dates. Your tracking spreadsheet has a quarterly reminder to “check expired consents.” Meanwhile, 14,000 expired consent records remain marked active, and marketing emails continue flowing to users who withdrew consent months ago.
GDPR requires making consent withdrawal as easy as giving it. If your expiration tracking relies on someone remembering to run a database query every few months, you’re systematically violating this requirement. A daily automated check would catch this in hours, not quarters.
Deletion Requests: Hope Is Not a Strategy
public async Task DeleteUserAccount(Guid userId)
{
// Deletes from Users table
await _dbContext.Users.Where(u => u.Id == userId).ExecuteDeleteAsync();
// But PII remains in: OrderHistory, AuditLogs, EmailCampaigns,
// BackupSnapshots, and that analytics database nobody remembers exists
}
User requests deletion. Support marks ticket resolved. Compliance assumes it happened. Meanwhile, email addresses live on in five different tables across three systems nobody checked. Without automated scanning, deletion requests become best-effort guesses.
Documentation Drift: Accurate Records of a System That No Longer Exists
Your processing records say you collect “email addresses, names, and purchase history” with 36-month retention. Your actual system now processes health information, geolocation data, and biometric logs (none documented) with 60-month retention. The documentation was accurate. Eighteen months ago. Now it’s compliant fiction, and auditors are reviewing a system that no longer exists.
Building the Fix: .NET CLI Tools for Privacy Automation
Enough problems. Let’s build solutions. The goal: make privacy compliance a build-time fact rather than a quarterly hope. These CLI tools integrate into your CI/CD pipeline, failing builds when privacy violations exist.
PII Discovery: Find Everything You Forgot to Document
First, a CLI tool that scans your codebase and database schema for PII attributes:
public class DiscoverPiiCommand
{
public async Task<PrivacyAuditReport> ExecuteAsync(string assemblyPath, string connectionString)
{
var report = new PrivacyAuditReport();
var assembly = Assembly.LoadFrom(assemblyPath);
// Find all properties marked with [PersonalData]
var piiProperties = assembly.GetTypes()
.SelectMany(type => type.GetProperties())
.Where(prop => prop.GetCustomAttribute<PersonalDataAttribute>() != null)
.Select(prop => new PiiProperty
{
TypeName = prop.DeclaringType?.FullName,
PropertyName = prop.Name,
LegalBasis = prop.GetCustomAttribute<LegalBasisAttribute>()?.Basis ?? "NOT_DOCUMENTED"
});
report.DiscoveredPiiProperties = piiProperties.ToList();
// Scan EF metadata for database columns
await using var dbContext = new ApplicationDbContext(connectionString);
foreach (var entityType in dbContext.Model.GetEntityTypes())
{
var piiColumns = entityType.GetProperties()
.Where(p => p.FindAnnotation("Privacy:IsPII")?.Value as bool? == true);
report.DatabasePiiColumns.AddRange(piiColumns.Select(p => new DatabasePiiColumn
{
TableName = entityType.GetTableName(),
ColumnName = p.GetColumnName(),
LegalBasis = p.FindAnnotation("Privacy:LegalBasis")?.Value as string ?? "UNDOCUMENTED"
}));
}
// Cross-reference against processing records - anything missing is a compliance gap
var documented = await LoadProcessingRecordsAsync();
report.UndocumentedPii = report.DatabasePiiColumns
.Where(col => !documented.Any(rec => rec.CoversColumn(col.TableName, col.ColumnName)))
.ToList();
return report;
}
}
The tool scans code via reflection and database schema via EF metadata, cross-referencing against your processing records. Anything not documented shows up as a compliance gap. Run it in CI/CD and you’ll know within minutes when someone adds undocumented PII.
Consent Monitoring: Catch Expirations Before Regulators Do
A CLI tool that runs daily to catch consent violations:
public class ConsentAuditCommand
{
public async Task<ConsentAuditReport> ExecuteAsync()
{
var today = DateTime.UtcNow;
var report = new ConsentAuditReport { AuditDate = today };
// Find expired consents still marked active
report.ExpiredActiveConsents = await _dbContext.MarketingConsents
.Where(c => c.IsActive && c.ExpirationDate < today)
.Select(c => new ExpiredConsent
{
UserId = c.UserId,
Purpose = c.Purpose,
DaysOverdue = (int)(today - c.ExpirationDate).TotalDays
})
.ToListAsync();
// Verify "completed" deletion requests actually deleted everything
var deletionRequests = await _dbContext.GdprDeletionRequests
.Where(r => r.Status == DeletionStatus.Completed)
.Select(r => r.UserId)
.ToListAsync();
foreach (var userId in deletionRequests)
{
var remainingData = await ScanAllTablesForUser(userId);
if (remainingData.Any())
report.IncompleteDeletions.Add(new IncompleteDeletion { UserId = userId, Locations = remainingData });
}
return report;
}
}
Run this daily. When it finds 14,000 expired consents still active, you’ll know about it the same day, not three months later during the quarterly audit.
Change Detection: Know When Privacy Impact Assessments Need Updates
Git integration catches privacy-impacting changes automatically:
public class DpiaChangeDetectionCommand
{
public async Task<DpiaChangeReport> ExecuteAsync(string currentCommit, string previousCommit)
{
var report = new DpiaChangeReport { CurrentCommit = currentCommit };
var gitDiff = await GetGitDiffAsync(previousCommit, currentCommit);
foreach (var changedFile in gitDiff.ChangedFiles.Where(f => f.Path.EndsWith(".cs")))
{
var currentTree = CSharpSyntaxTree.ParseText(await File.ReadAllTextAsync(changedFile.Path));
var previousTree = CSharpSyntaxTree.ParseText(await GetFileContentAtCommitAsync(changedFile.Path, previousCommit));
// Detect new [PersonalData] properties
var newPiiFields = GetPiiProperties(currentTree.GetRoot())
.Except(GetPiiProperties(previousTree.GetRoot()))
.ToList();
if (newPiiFields.Any())
{
report.FilesWithPrivacyChanges.Add(new PrivacyChange
{
FilePath = changedFile.Path,
NewPiiFields = newPiiFields,
RequiresDpiaUpdate = true
});
}
}
return report;
}
}
New PII field added in a commit? The tool flags it. External data flow changed? Flagged. Now your Data Protection Impact Assessment updates happen as part of code review, not eighteen months later when an auditor notices the mismatch.
Deletion Verification: Test That It Actually Works
Don’t trust that deletion requests were honored. Verify:
public class DeletionVerificationCommand
{
public async Task<DeletionReport> ExecuteAsync()
{
// Find all tables containing user PII
var piiTables = _dbContext.Model.GetEntityTypes()
.Where(e => e.GetProperties().Any(p => p.FindAnnotation("Privacy:IsPII")?.Value as bool? == true))
.Select(e => e.GetTableName())
.ToList();
// Create synthetic test user with data in all PII tables
var testUserId = await CreateSyntheticUserWithPii();
await _deletionService.DeleteUserAccount(testUserId);
// Verify deletion actually worked
var report = new DeletionReport();
foreach (var table in piiTables)
{
var remainingRecords = await CountRecordsForUser(table, testUserId);
if (remainingRecords > 0)
report.Issues.Add($"Table {table} still contains {remainingRecords} records after deletion");
}
return report;
}
}
This creates a synthetic user, exercises deletion, then verifies every PII table is actually empty. No more assuming deletion worked. Prove it.
Processing Records From Code, Not Spreadsheets
Generate compliance documentation from your actual system state:
public class GenerateProcessingRecordCommand
{
public async Task ExecuteAsync()
{
var record = new ProcessingRecord { GeneratedDate = DateTime.UtcNow };
foreach (var entityType in _dbContext.Model.GetEntityTypes())
{
var piiProperties = entityType.GetProperties()
.Where(p => p.FindAnnotation("Privacy:IsPII")?.Value as bool? == true)
.ToList();
if (!piiProperties.Any()) continue;
record.Activities.Add(new ProcessingActivity
{
Name = entityType.ClrType.Name,
DataCategories = piiProperties.Select(p => p.FindAnnotation("Privacy:Category")?.Value as string).ToList(),
LegalBasis = entityType.FindAnnotation("Privacy:LegalBasis")?.Value as string ?? "NOT_DOCUMENTED",
Purpose = entityType.FindAnnotation("Privacy:Purpose")?.Value as string ?? "NOT_DOCUMENTED"
});
}
await File.WriteAllTextAsync("ProcessingRecord.json", JsonSerializer.Serialize(record, new JsonSerializerOptions { WriteIndented = true }));
}
}
Documentation generated from code annotations is always current. When someone adds a new PII field and forgets to annotate it, the tool flags it as undocumented. The processing record reflects reality, not eighteen-month-old assumptions.
GitHub Actions Integration
Wire everything into CI/CD:
name: Privacy Compliance Audit
on:
push:
branches: [main, develop]
pull_request:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM
jobs:
privacy-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Discover Undocumented PII
run: dotnet run --project PrivacyAudit.Cli -- discover-pii --output pii-report.json
- name: Audit Consent Records
run: dotnet run --project PrivacyAudit.Cli -- audit-consent --output consent-report.json
- name: Detect Privacy-Impacting Changes
run: dotnet run --project PrivacyAudit.Cli -- detect-changes --output changes-report.json
- name: Verify Deletion Completeness
run: dotnet run --project PrivacyAudit.Cli -- verify-deletion --output deletion-report.json
- name: Fail on Violations
run: dotnet run --project PrivacyAudit.Cli -- check-compliance --fail-on-violations true
- uses: actions/upload-artifact@v4
if: always()
with:
name: privacy-audit-reports
path: '*-report.json'
Undocumented PII? Build fails. Expired consents? Build fails. Incomplete deletions? Build fails. Compliance becomes a technical gate, not a quarterly prayer meeting.
What These Tools Actually Prove
Let’s map this back to what regulators care about:
Legal basis documentation: The PII discovery tool scans everything and flags undocumented processing. No more hoping developers remembered to update the spreadsheet.
Consent management: Daily automated checks catch expired consents within 24 hours, not 90 days. When someone asks “how do you ensure consent is current?”, you have timestamped reports.
Deletion verification: Synthetic user tests prove deletion works across all systems. When someone asks “how do you verify erasure requests?”, you have test results, not assurances.
Change detection: Git-integrated analysis flags privacy-impacting code changes at PR time. Impact assessments update when code changes, not whenever someone remembers.
Processing records: Documentation generated from code annotations reflects current reality. The record you show auditors matches the system they’re auditing.
Getting Started Without Boiling the Ocean
Don’t try to implement everything at once:
Week 1-2: Deploy PII discovery. Baseline your current state. You’ll find undocumented fields in 40-60% of your tables. That’s normal. Start documenting.
Week 3-4: Add consent monitoring. Run it daily. When you discover 14,000 expired consents nobody knew about, you’ll understand why quarterly audits don’t work.
Week 5-6: Integrate change detection into CI/CD. Make privacy compliance a build requirement. New undocumented PII stops reaching production.
Week 7-8: Deploy deletion verification. Create synthetic test users. Prove erasure actually works.
Start with non-production environments to tune false positive rates. Privacy audit automation reveals uncomfortable truths. Expect resistance from teams who prefer checkbox compliance to technical accountability.
The Uncomfortable Truth
Manual privacy audits are security theater. Quarterly spreadsheet reviews cannot detect PII added last Tuesday, consent that expired this morning, or deletion requests honored in some tables but not others.
Privacy compliance is a continuous technical state, not a point-in-time assessment. Your compliance documentation is either generated from your actual system state, or it’s fiction. There’s no middle ground.
Build .NET CLI tools that make compliance a build-time fact. Use reflection to discover all PII. Use Entity Framework metadata to scan schemas. Use Git integration to catch privacy-impacting changes. Fail builds when violations exist.
The tools in this article are a starting point. Real implementations need customization for your specific architecture and regulatory requirements. But the fundamental principle stands: automate privacy audits or accept that your compliance documentation is wishful thinking.
Stop reviewing spreadsheets. Start building proof.

Comments