PII Redaction in LLM Pipelines

AILLMSecurityPrivacy
Share on LinkedIn

A customer pasted medical records into our summarization feature. The prompt logged to our observability vendor contained patient names, dates of birth, and MRN numbers in plain text — three compliance violations before the model returned a single token. LLM pipelines treat text as fuel; PII redaction is the filter that keeps you from burning regulated data in third-party inference, training corpora, and log aggregators that retain everything for ninety days.

Where PII enters the pipeline

User input ──→ RAG retrieval ──→ Prompt assembly ──→ LLM ──→ Response
     │               │                  │
  Logs/trace     Embeddings DB      Provider logs

Redact at every egress point. Assume the model provider, vector database, and tracing backend are not your HIPAA BAA-covered systems unless contracted explicitly.

Detection approaches

Pattern-based — high precision for structured IDs:

import re

SSN = re.compile(r"\b\d{3}-\d{2}-\d{4}\b")
EMAIL = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
PHONE = re.compile(r"\b\+?1?\d{10,14}\b")

def pattern_scan(text: str) -> list[tuple[int, int, str]]:
    findings = []
    for name, pat in [("SSN", SSN), ("EMAIL", EMAIL), ("PHONE", PHONE)]:
        for m in pat.finditer(text):
            findings.append((m.start(), m.end(), name))
    return findings

ML-based NER — catches names, locations, organizations in context:

from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine

analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()

results = analyzer.analyze(text=text, language="en", entities=[
    "PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER", "US_SSN", "CREDIT_CARD"
])
redacted = anonymizer.anonymize(text=text, analyzer_results=results)
print(redacted.text)  # "Contact <PERSON> at <EMAIL_ADDRESS>"

Tune entity lists per jurisdiction — GDPR cares about EU national IDs; HIPAA about PHI categories.

Redaction strategies

Strategy Use when
Replace with type tag [EMAIL], [PERSON_1] — model retains structure
Pseudonymize (consistent hash) Same person → same token across session
Reversible vault token Need to restore in UI output only
Drop / refuse High-sensitivity input, no safe path
def pseudonymize(name: str, session_salt: bytes) -> str:
    h = hashlib.sha256(session_salt + name.encode()).hexdigest()[:8]
    return f"PERSON_{h}"

Consistent pseudonyms help the model track entities without seeing real names.

Pipeline integration

async def prepare_prompt(user_text: str, session: Session) -> str:
    detected = analyzer.analyze(user_text, language="en")
    if any(r.entity_type in BLOCK_ENTITIES and r.score > 0.85 for r in detected):
        raise InputRejected("Sensitive identifiers detected")
    redacted = anonymizer.anonymize(user_text, detected)
    session.store_mapping(redacted, detected)  # vault for restore
    return redacted.text

async def finalize_response(model_output: str, session: Session) -> str:
    output_scan = analyzer.analyze(model_output, language="en")
    redacted_out = anonymizer.anonymize(model_output, output_scan)
    return session.restore_allowed_fields(redacted_out.text)

Scan model output — models hallucinate or regurgitate PII from training.

RAG-specific concerns

Embeddings of raw PII in vector stores leak via semantic search — "find patients like John Smith" retrieves neighbors. Redact before chunking and embedding:

def prepare_chunk(doc: str) -> str:
    results = analyzer.analyze(doc, language="en")
    return anonymizer.anonymize(doc, results).text

Metadata filters should not store raw PII in facet fields indexed by the vector DB vendor.

Logging and observability

Never log raw prompts in production tracing. Options:

logger.info("llm_request", extra={
    "prompt_hash": hashlib.sha256(prompt.encode()).hexdigest()[:16],
    "prompt_redacted": redact_for_logs(prompt),
    "model": model_id,
})

Review OpenTelemetry exporters and LLM wrapper defaults — many log full messages unless configured.

Compliance mapping

GDPR — minimize personal data sent to processors; DPAs with LLM vendors; support erasure (delete vault mappings and logs).

HIPAA — BAA required with subprocessors; de-identify per Safe Harbor or Expert Determination before non-covered use.

PCI — never include full PAN in prompts; tokenize at payment gateway boundary.

Document data flows in your privacy impact assessment — auditors ask where text goes, not whether you use AI.

Testing redaction quality

Build evaluation sets with synthetic PII:

Regression test on model upgrades — new tokenizer behaviors occasionally split entities across tokens.

PII detection pipeline

Scan input and output with NER + regex:

Common production mistakes

Teams get safety pii redaction wrong in predictable ways:

LLM features around safety pii redaction break in production when prompts assume deterministic output, context windows are sized for dev datasets, or token costs are never budgeted per user session. Always log prompt hash, model version, and latency—not raw prompts with PII.

Debugging and triage workflow

When safety pii redaction misbehaves in production, work top-down instead of guessing:

  1. Confirm scope — one tenant, region, or deployment stage? Narrow blast radius before deep diving.
  2. Check recent changes — deploys, flag flips, config pushes, and schema migrations in the last 24 hours.
  3. Compare golden signals — latency, error rate, saturation, and traffic for the affected surface vs. baseline.
  4. Reproduce minimally — smallest input or scenario that triggers the failure; capture traces/logs with correlation IDs.
  5. Fix forward or rollback — if rollback is faster than root-cause during incident, rollback first, postmortem second.
  6. Add a guard — alert, integration test, or circuit breaker so the same class of failure is caught earlier next time.

Document the timeline during triage. Future you (and on-call) will need timestamps, not just conclusions.

Resources

Frequently asked questions

Should PII be redacted before sending data to an LLM or after the response?

Before — always redact or pseudonymize PII in prompts, retrieved documents, and tool inputs before they reach the model or leave your trust boundary. Post-response redaction catches model-generated PII but does not prevent the provider from logging sensitive inputs. Input redaction is the compliance control; output redaction is defense-in-depth.

Is reversible tokenization safe for LLM workflows?

Reversible tokenization (vault maps [EMAIL_1] to real email) lets you restore values in final output when the model need not see real PII. The vault becomes a high-value target — encrypt at rest, strict access controls, audit logs. For analytics and training, prefer irreversible redaction or hashing. Never send reversible tokens to third-party models if the mapping could be inferred.

What PII detection tools work in production LLM pipelines?

Microsoft Presidio, AWS Comprehend PII, Google DLP API, and spaCy NER models are common. Combine ML detectors with regex for structured identifiers (SSN, credit cards, IBAN). Tune confidence thresholds per entity type — false positives frustrate users; false negatives create compliance incidents. Human review sampling validates detector quality over time.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →