Skip to content

PII & Output Validation

Key Points

  • PII detection: scrub sensitive data before sending to LLM and from responses before display/storage.
  • Tools: Microsoft Presidio (Python; called from .NET), Azure AI Language PII detection, regex for simple cases.
  • Output validation: schema, constraints, content moderation. Model can hallucinate JSON; verify.
  • Schema-strict mode (OpenAI) helps but doesn't replace runtime validation.

PII categories

  • Direct identifiers: name, email, phone, address, SSN, credit card.
  • Indirect: IP, device ID, location patterns.
  • Health (PHI): medical conditions, prescriptions.
  • Financial: account numbers, balances.

Pre-LLM redaction

public string Redact(string input)
{
    input = Regex.Replace(input, @"[\w.+-]+@[\w-]+\.[\w-]+", "[EMAIL]");
    input = Regex.Replace(input, @"\b\d{3}-\d{2}-\d{4}\b", "[SSN]");
    input = Regex.Replace(input, @"\b(?:\d[ -]*?){13,19}\b", "[CARD]");
    input = Regex.Replace(input, @"\b\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}\b", "[PHONE]");
    return input;
}

For richer:

// Azure AI Language PII
var client = new TextAnalyticsClient(uri, cred);
var pii = await client.RecognizePiiEntitiesAsync(input);
var redacted = pii.RedactedText;

Or Microsoft Presidio (Python) via subprocess / HTTP.

Tokenize for reversible

If the use case needs original PII back:

public class PiiTokenizer
{
    public (string redacted, Dictionary<string, string> map) Process(string input)
    {
        var map = new Dictionary<string, string>();
        var output = new StringBuilder(input);
        // detect entities, replace with tokens, store mapping
        return (output.ToString(), map);
    }

    public string Restore(string output, Dictionary<string, string> map)
    {
        foreach (var (tok, original) in map) output = output.Replace(tok, original);
        return output;
    }
}

LLM sees [USER_EMAIL_1]; final output restores actual email.

Output validation

LLM output → validate before use:

public bool TryParseAddress(string output, out Address addr)
{
    addr = null;
    try
    {
        addr = JsonSerializer.Deserialize<Address>(output);
    }
    catch { return false; }

    return !string.IsNullOrEmpty(addr.Street)
        && addr.Country?.Length == 2
        && IsValidPostalCode(addr.Country, addr.PostalCode);
}

Don't trust the LLM's "I have validated" claims.

Schema-strict mode

OpenAI's structured output:

new ChatOptions
{
    ResponseFormat = ChatResponseFormat.ForJsonSchema(
        JsonSchema.FromType<MyType>(),
        schemaName: "Address",
        strict: true)
};

Enforces JSON Schema. Still validate semantic constraints in code.

Content moderation

Azure AI Content Safety:

var safety = new ContentSafetyClient(uri, cred);
var result = await safety.AnalyzeTextAsync(new() { Text = output });

if (result.Categories.Any(c => c.Severity > 4))   // 0-7 scale
    return null;

Categories: Hate, SelfHarm, Sexual, Violence. Tunable thresholds.

Hallucination detection

// Check answer against retrieved sources
public bool IsGrounded(string answer, IList<string> sources)
{
    var sentences = SplitIntoSentences(answer);
    return sentences.All(s => sources.Any(src => SemanticOverlap(s, src) > 0.7));
}

Or use eval frameworks (Ragas faithfulness).

Output sanitization for display

If displaying LLM output as HTML: escape! LLMs can output <script> tags.

return HtmlEncoder.Default.Encode(output);

Or use markdown renderer that strips dangerous HTML.

File output validation

If LLM generates filenames / paths:

var safe = Path.GetFileName(filename);   // strip path
if (Path.GetExtension(safe) is ".exe" or ".dll" or ".ps1") /* reject */;

Code execution validation

If LLM generates code (code interpreter): - Sandbox. - Resource limits. - Network policy. - No filesystem access outside scratch.

Senior considerations

  • Pre + post: redact before LLM; validate after.
  • Defense in depth: multiple layers.
  • Schema-first: define types; validate against schema.
  • Trust nothing from LLM: assume hallucination, malicious crafting (via injection).
  • Compliance: PII handling must meet GDPR/HIPAA/etc.

Anti-patterns

  • ❌ Send raw user PII to external LLM API.
  • ❌ Trust LLM-generated SQL / code.
  • ❌ Display unsanitized LLM output as HTML.
  • ❌ Skip output validation because "LLM said it's correct".

Cross-references