
PII Detection and Redaction in LLM Pipelines
Implementing effective PII detection and redaction in LLM pipelines using tools like Presidio to ensure data protection and log hygiene.
This is Part 4 of the AI Security & Guardrails series.
Large Language Models (LLMs) introduce a fundamental shift in data handling. Unlike traditional applications where data flows through a predictable schema, LLMs ingest unstructured text and generate unstructured text. This fluidity creates two distinct failure points for Personally Identifiable Information (PII): the input prompt, where users may inadvertently leak sensitive data into the context window, and the output response, where the model may hallucinate or regurgitate training data containing PII. Furthermore, the internal logs generated by these systems often become the weakest link in security, storing raw, unsanitized data that violates privacy regulations like GDPR or HIPAA.
Implementing effective PII detection requires a multi-stage pipeline. We cannot rely on the model itself to "behave"; we must enforce boundaries using external detection engines. This article details how to integrate these checks using mechanisms like Microsoft Presidio, focusing on input interception, output redaction, and log hygiene.
The Mechanism of Leakage
To understand where to place guards, we must understand how LLMs process context. When a user sends a prompt, it is tokenized and added to the context window. The model attends to these tokens to generate the next one. If the context window contains PII (e.g., "My SSN is 123-45-6789"), that information becomes part of the model's immediate memory for the duration of the session.
The risk is twofold:
- Data Poisoning/Leakage: The user's PII is stored in the vector database or conversation history, potentially accessible to other users or developers if access controls fail.
- Model Memorization: Over time, models can memorize rare patterns from training data. As demonstrated by Carlini et al. in Extracting Training Data from Large Language Models, models can regurgitate specific PII when prompted with specific triggers.
Therefore, the pipeline must treat every interaction as a potential leak vector. Data protection is a primary driver for these safeguards, ensuring that privacy concerns are addressed at every layer of the stack.
Input Sanitization: The First Line of Defense
The most effective control is prevention. Before a prompt reaches the LLM API, it must pass through a PII detection engine. This stage acts as a filter, identifying sensitive entities and replacing them with placeholders or blocking the request entirely. Microsoft Presidio is an open-source framework designed specifically for this purpose. It uses a combination of Regular Expressions (Regex) and Named Entity Recognition (NER) models (such as spaCy or Hugging Face transformers) to detect entities like names, phone numbers, email addresses, and credit card numbers.
This layer is critical for data protection, as it stops sensitive information before it enters the model's context. By filtering inputs early, we also contribute to better log hygiene, as fewer raw PII entries need to be managed in downstream systems. Privacy is preserved not just by hiding data, but by ensuring it never unnecessarily touches the model's internal state.
Implementation Workflow
- Interception: The application layer captures the user's raw input string.
- Analysis: The input is passed to the Presidio Analyzer. The analyzer runs configured recognizers against the text.
- Redaction/Replacement: The Presidio Anonymizer replaces detected entities with pseudo-anonymized values (e.g.,
[PHONE_NUMBER]) or removes them. - Forwarding: The sanitized text is sent to the LLM.
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
from presidio_anonymizer.entities import OperatorConfig
# Initialize engines
analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()
def sanitize_input(user_prompt: str) -> str:
# 1. Analyze the text
results = analyzer.analyze(text=user_prompt, language='en')
if not results:
return user_prompt
# 2. Define redaction actions
# 'replace' keeps the field name but removes the value
# 'mask' partially hides the value
actions = {entity_type: 'replace' for entity_type in set(r.entity_type for r in results)}
# 3. Anonymize
anonymized_data = anonymizer.anonymize(
text=user_prompt,
analyzer_results=results,
operators={"DEFAULT": OperatorConfig("replace", {"new_value": "[REDACTED]"})}
)
return anonymized_data.text
# Example usage
raw_input = "Hi, I'm John Doe. My email is john@example.com and my phone is 555-1234."
clean_input = sanitize_input(raw_input)
print(clean_input)
# Output: "Hi, I'm [REDACTED]. My email is [REDACTED] and my phone is [REDACTED]."By replacing PII with generic tags like [REDACTED], the LLM receives the semantic structure of the sentence without the sensitive content. This prevents the model from learning the specific individual's data while still allowing it to perform the requested task (e.g., summarization or translation).
Output Redaction: Cleaning the Model's Response
Even with sanitized inputs, LLMs can generate PII. This occurs if the model draws from its pre-training data (which may contain public records) or if the context window contains enough information to infer sensitive details. For example, if a user asks, "Who is the CEO of Acme Corp?" and the model knows from training data that the CEO is Jane Smith, it will output her name.
Output redaction operates similarly to input sanitization but is applied to the LLM's response.
Challenges with Output Redaction
- Latency: Adding a post-processing step increases response time.
- Contextual Ambiguity: Regex might flag false positives (e.g., "555" in a phone number vs. a version number).
- Adversarial Attacks: Users may attempt to obfuscate PII in the input to bypass filters, hoping the model will still recognize the underlying intent.
To mitigate this, output redaction should use the same detection logic as input sanitization. However, it is often more aggressive. Instead of just replacing values, systems may block responses that contain high-confidence PII detections and return a generic error message or a sanitized summary.
def sanitize_output(model_response: str) -> str:
results = analyzer.analyze(text=model_response, language='en')
# If high-risk PII is detected, consider blocking or flagging
high_risk_types = {'PHONE_NUMBER', 'CREDIT_CARD', 'US_SSN'}
has_high_risk = any(r.entity_type in high_risk_types for r in results)
if has_high_risk:
return "[SYSTEM]: Response contained sensitive data and was redacted."
# Otherwise, anonymize
anonymized_data = anonymizer.anonymize(
text=model_response,
analyzer_results=results,
operators={"DEFAULT": OperatorConfig("replace", {"new_value": "[REDACTED]"})}
)
return anonymized_data.textLog Hygiene: The Silent Leak
Perhaps the most critical aspect of PII protection is log hygiene. In development and production environments, engineers often log raw requests and responses for debugging, observability, and fine-tuning purposes. If these logs are not sanitized, they become a static repository of PII, accessible to anyone with read access to the logging platform (e.g., Datadog, Splunk, CloudWatch).
Best Practices for Log Sanitization
- Never Log Raw Prompts/Responses: Always log the sanitized versions. If you need to debug why a model failed, use the redacted text.
- Log Audit Metadata: While PII values must be redacted, the fact of redaction and the type of PII detected (e.g., 'SSN detected') can be logged for auditing. Explicitly state that the reason for blocking/redaction is logged, but the sensitive content itself is not.
- Hash Identifiers: For user IDs or session tokens, use one-way hashing (e.g., SHA-256) rather than encryption, so they can be correlated across services but not reversed.
- Automated Scanning: Implement CI/CD checks that scan log files for common PII patterns. Tools like
trufflehogor custom scripts can alert if raw PII is accidentally written to logs. - Data Retention Policies: Define strict retention periods for logs containing any user data. Automated deletion ensures that PII does not accumulate indefinitely.
import logging
import hashlib
import re
# Configure logger to automatically sanitize
class PIIAwareHandler(logging.Handler):
def emit(self, record):
log_entry = self.format(record)
# Apply redaction to the log entry itself
sanitized_entry = sanitize_input(log_entry)
# Hash any remaining user identifiers
sanitized_entry = re.sub(r'user_id=(\w+)', lambda m: f'user_id={hashlib.sha256(m.group(1).encode()).hexdigest()}', sanitized_entry)
print(sanitized_entry)
logger = logging.getLogger('llm_pipeline')
logger.addHandler(PIIAwareHandler())Tradeoffs and Opinions
There is a significant tradeoff between privacy and utility. Aggressive redaction can degrade the quality of the LLM's output. If a user provides their medical history to ask for general advice, redacting the condition (e.g., "I have [DIAGNOSIS]") makes it impossible for the model to provide relevant information.
In such cases, a "block-and-ask" strategy is often superior to automatic redaction. If high-confidence PII is detected, the system can interrupt the flow and ask the user, "We detected sensitive information in your input. Would you like to proceed with redaction, or provide a generalized description?" This respects user autonomy and maintains utility where possible.
Furthermore, relying solely on regex and NER is insufficient for complex PII types. Contextual understanding is required. For instance, recognizing that "Apple" refers to a company and not a fruit in a financial context requires deeper semantic analysis. Future implementations should consider integrating lightweight classification models alongside Presidio to improve accuracy.
Conclusion
PII detection and redaction in LLM pipelines is not optional; it is a foundational requirement for secure AI deployment. By implementing a three-layered approach—input sanitization, output redaction, and rigorous log hygiene—organizations can significantly reduce the risk of data breaches. Tools like Microsoft Presidio provide the necessary infrastructure, but the architecture must be designed to treat every data point as potentially sensitive. As LLMs become more integrated into business workflows, the cost of failing to protect PII will far exceed the engineering effort required to implement these safeguards.
Related posts
Building a PII Redaction Layer for LLM Logs
Implement a PII redaction layer for LLM logs to ensure GDPR compliance and protect sensitive data during tokenization and retention.
OWASP Top 10 for LLM Applications, Applied
A practical examination of the OWASP Top 10 for LLM Applications, covering security checklists, mitigations, and appsec review processes for developers.
Identity Data Security: Encrypting and Tokenizing PII in Identity Stores
An examination of identity data security strategies focusing on encrypting and tokenizing personally identifiable information within identity stores.