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:
58
src/PiiRedaction.Core/Redaction/PlaceholderPiiRedactor.cs
Normal file
58
src/PiiRedaction.Core/Redaction/PlaceholderPiiRedactor.cs
Normal 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()
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user