Add PII redaction POC for secure LLM prompting.

Implements detect-redact-sanitize pipeline with regex, domain rules, and ONNX NER before the LLM boundary, plus NUnit tests and Xenovex push documentation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Bilal Nazer Ali
2026-07-07 13:05:07 +05:30
commit dfc81dea28
60 changed files with 3283 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
using PiiRedaction.Core.Abstractions;
using PiiRedaction.Core.Models;
namespace PiiRedaction.Core.Redaction;
public sealed class PlaceholderPiiRedactor : IPiiRedactor
{
public RedactionResult Redact(string text, IReadOnlyList<PiiEntity> entities)
{
ArgumentException.ThrowIfNullOrWhiteSpace(text);
ArgumentNullException.ThrowIfNull(entities);
var placeholderByValue = new Dictionary<string, string>(StringComparer.Ordinal);
var counters = new Dictionary<PiiEntityType, int>();
var orderedEntities = entities.OrderByDescending(entity => entity.StartIndex).ToList();
var sanitized = text;
foreach (var entity in orderedEntities)
{
var mapKey = CreateValueKey(entity);
if (!placeholderByValue.TryGetValue(mapKey, out var placeholder))
{
counters.TryGetValue(entity.Type, out var count);
count++;
counters[entity.Type] = count;
placeholder = $"<{ToPlaceholderPrefix(entity.Type)}_{count}>";
placeholderByValue[mapKey] = placeholder;
}
sanitized = string.Concat(
sanitized.AsSpan(0, entity.StartIndex),
placeholder,
sanitized.AsSpan(entity.EndIndex));
}
var placeholderMap = placeholderByValue
.ToDictionary(pair => pair.Value, pair => pair.Key.Split('|', 2)[1], StringComparer.Ordinal);
return new RedactionResult(sanitized, placeholderMap);
}
private static string CreateValueKey(PiiEntity entity) =>
$"{entity.Type}|{entity.Value}";
private static string ToPlaceholderPrefix(PiiEntityType type) => type switch
{
PiiEntityType.Person => "PERSON",
PiiEntityType.Email => "EMAIL",
PiiEntityType.Phone => "PHONE",
PiiEntityType.Pan => "PAN",
PiiEntityType.Aadhaar => "AADHAAR",
PiiEntityType.CreditCard => "CREDIT_CARD",
PiiEntityType.LoanNumber => "LOAN_NUMBER",
PiiEntityType.CustomerId => "CUSTOMER_ID",
PiiEntityType.AccountNumber => "ACCOUNT_NUMBER",
_ => type.ToString().ToUpperInvariant()
};
}