A Practical Guide to PII Detection in LLM Pipelines
Every LLM request is a potential data exfiltration vector. When a user pastes a support ticket into an AI chatbot, that ticket might contain email addresses, phone numbers, Social Security numbers, or API keys. If your pipeline sends that content to a third-party model without inspection, you have a compliance problem — and probably a contractual one too.
This guide walks through how to build a practical PII detection layer for LLM pipelines, from simple regex patterns to NER models to gateway-level enforcement.
The Detection Stack
Effective PII detection uses three layers, each with different tradeoffs between speed, accuracy, and coverage.
Layer 1: Pattern Matching
Regular expressions catch structured PII with high precision and near-zero latency. Start here — it handles the most common cases.
const PII_PATTERNS = {
// US Social Security Number
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
// Email addresses
email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
// US phone numbers (multiple formats)
phone: /\b(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g,
// Credit card numbers (Luhn-validated separately)
creditCard: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,
// API keys (common formats)
apiKey: /\b(?:sk|pk|api|key)[-_][A-Za-z0-9]{20,}\b/g,
// IP addresses
ipAddress: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
};
function detectPatterns(text: string): Detection[] {
const detections: Detection[] = [];
for (const [type, pattern] of Object.entries(PII_PATTERNS)) {
const matches = text.matchAll(pattern);
for (const match of matches) {
detections.push({
type,
value: match[0],
start: match.index!,
end: match.index! + match[0].length,
confidence: 0.95,
});
}
}
return detections;
}
Pattern matching is fast enough to run synchronously in the request path. The tradeoff is that it only catches PII with predictable formats — it will miss a name like “John Smith” or a mailing address written in prose.
Layer 2: Named Entity Recognition
NER models catch unstructured PII that regex cannot: names, addresses, organization references, and medical terms. These models add latency (typically 10-50ms per request), but they dramatically improve recall.
Use a lightweight model like dslim/bert-base-NER or a managed NER API. Run NER in parallel with pattern matching to minimize added latency. The output is a list of entity spans with type labels (PERSON, LOCATION, ORGANIZATION) that you merge with the regex detections.
Layer 3: Context-Aware Classification
For high-sensitivity pipelines, add a classification step that considers the full context of the request. A number like 123-45-6789 is almost certainly an SSN in a customer support conversation, but it might be a part number in a manufacturing context. Context-aware classifiers reduce false positives by evaluating detections against the surrounding text and the metadata of the request (which agent, which user role, which data source).
This layer is optional for most teams. Start with Layers 1 and 2, measure your false positive rate, and add Layer 3 if precision is a problem.
Handling Detected PII
Detection is only half the problem. Once you find PII, you need a policy for what to do with it. There are three common strategies:
Redact — Replace the PII with a placeholder before sending the request to the model. This preserves the structure of the prompt while removing sensitive data. For example, replace john.smith@acme.com with [EMAIL_REDACTED]. The model can still understand the intent of the message without seeing the actual data.
Block — Reject the request entirely and return an error to the user. This is appropriate for high-risk PII types (SSNs, credit card numbers) in pipelines where redaction might alter the meaning of the request.
Log and allow — Let the request through but create an audit record for compliance review. This is useful during a rollout period when you want to measure PII exposure without disrupting users.
Most production systems use a combination: redact emails and names, block SSNs and credit cards, and log everything.
Gateway-Level Enforcement
The most reliable place to enforce PII policies is at the gateway — the proxy layer that sits between your application and the LLM provider. Gateway-level enforcement means every request is inspected regardless of which application or team sent it. No agent can bypass the rules by “forgetting” to call the detection function.
In Rivano, PII policies are defined in YAML and attached to proxy routes:
# rivano-policy.yaml
policies:
- name: pii-protection
on: request
condition:
pii_detected:
types: [ssn, credit_card]
confidence_threshold: 0.9
action:
block:
status: 422
message: "Request blocked: sensitive data detected"
- name: pii-redaction
on: request
condition:
pii_detected:
types: [email, phone, name]
confidence_threshold: 0.85
action:
redact:
replacement: "[{type}_REDACTED]"
This configuration blocks requests containing SSNs or credit card numbers and redacts emails, phone numbers, and names — all before the data leaves your network.
Performance Considerations
PII detection adds latency to every request. Here is what to expect:
| Layer | Typical Latency | Accuracy | Coverage |
|---|---|---|---|
| Regex patterns | < 1ms | High precision, low recall | Structured PII only |
| NER model (local) | 10-50ms | Moderate precision, high recall | Structured + unstructured |
| NER model (API) | 50-200ms | High precision, high recall | Broad coverage |
| Context classifier | 20-100ms | Highest precision | Full context |
For most pipelines, the combined overhead of regex + local NER is 15-60ms per request — well within acceptable bounds given that LLM calls themselves typically take 500ms-3s. Run detection layers in parallel to keep the overhead at the slowest layer, not the sum.
The key principle: PII detection should be invisible to the developer and unavoidable for the data. Build it into the infrastructure, not the application code.