How do you keep personal data out of LLM prompts? Send less of it in the first place, then detect what remains with the method that fits each type of data — patterns with checksums for card numbers, IBANs and secrets; named-entity recognition for names and addresses; model-based classifiers for context — and apply an action per rule: block, redact to a placeholder, or flag. Check the model's responses as well as the prompts, and hold your own request logs to the same rules. Then describe the result honestly: every automated detector misses things.
OWASP lists sensitive information disclosure as LLM02 in its 2025 risks for LLM applications, and recommends input validation to filter sensitive data and least-privilege access to it1. This guide turns that into a working design.
Where personal data enters a prompt
The chat box is the obvious door and rarely the largest one:
- User input. People paste emails, contracts, screenshots and log lines.
- Retrieval. A RAG pipeline puts whatever the index holds into the prompt, including customer records nobody intended to send.
- Tool results. An agent that calls your CRM, ticket system or database pastes the results into its next turn.
- System prompts and examples. Few-shot examples built from real data carry it into every request.
- Your own logs. If you log prompts and responses, you have a second copy of everything above, often kept longer and seen by more people. A prompt and payload logging policy covers that side.
Minimisation beats detection. Retrieve fields, not whole records; pass an internal id instead of a name where the model does not need the name; strip signatures and quoted threads from emails before they reach a prompt.
Three detection methods, and what each catches
| Method | Good at | Misses | Cost |
|---|---|---|---|
| Patterns with structure and checksum tests | Card numbers (Luhn), IBANs (mod-97), formatted national ids, API keys with known prefixes, emails, phone formats | Names, addresses, dates of birth, anything unformatted or split | Microseconds, deterministic |
| Named-entity recognition | Person names, locations, organisations in prose | Rare names, context-dependent identifiers, other languages without models | Milliseconds, probabilistic |
| Model-based classifiers | Context: "this message contains medical history", injection attempts | Exact spans; can be wrong confidently | A model call per check, latency and cost |
Open-source tools combine the first two. Microsoft's Presidio, for example, uses named-entity recognition, regular expressions, rule-based logic and checksums with relevant context, across languages, and de-identifies what it finds with configurable operators. Its documentation is explicit about the limit: "there is no guarantee that Presidio will find all sensitive information," and it recommends additional protections2.
Two implementation details catch most bypasses:
- Normalise before matching. Fullwidth digits, zero-width characters and soft hyphens let a card number slip past a naive pattern. Unicode normalisation (NFKC) and stripping invisible characters first closes that gap.
- Inspect structured content. Tool-call arguments and JSON tool results carry data too. Parse them and check the string values, rather than matching against serialised JSON.
Choose an action per rule
| Data | Typical action | Why |
|---|---|---|
| API keys, private keys, passwords | Block | There is no legitimate reason to send them to a model |
| Card numbers, bank details, government ids | Redact | The model rarely needs the value; a placeholder keeps the task working |
| Emails, phone numbers | Redact or flag | Depends on the task; support summaries can work with placeholders |
| Names | Flag, or redact with consistent placeholders | Detection is probabilistic; blocking on it causes false refusals |
| Terms specific to you (project codenames, client names) | Block or redact | Your own deny list catches what generic detectors cannot |
Redact with consistent placeholders. Replace each distinct value with the same token throughout a request — [EMAIL_1], [EMAIL_2] — so the model can still refer to them. If your application must restore the values in the answer, keep the mapping in your application, never in the prompt, and restore only into output that goes back to the user who supplied them.
Know what you cannot do on redacted text. Validating an address, matching a name against a list or answering "what is my account number" all need the real value. Those tasks need a different design — a tool the model calls that looks the value up inside your system — not a weaker redaction rule.
Check the response too
Models repeat what is in their context and can reconstruct values from fragments. Run output checks on responses, at least for the strictest rules: a response containing a card number or a key should be blocked or redacted before it reaches the user or your logs. With streaming, an output rule needs the whole answer before it can decide, which delays delivery; apply output checks to the streams that need them rather than all of them.
Where to run the checks
- In the application: closest to the data and its meaning; must be built into every service.
- In a gateway in front of the providers: one policy for every service and every provider, applied before the request leaves your control; it sees the prompt but not your business context.
- At the provider: some offer their own guardrails, but the data has already left by the time they run.
Most teams use two layers: minimisation and domain-specific rules in the application, and a uniform policy at the gateway as the backstop. LLM guardrails at the gateway covers the gateway layer in depth.
A rollout plan
Keep a short exception process: a team that genuinely needs to send a class of data to a model should get an approved path with a named owner, not a quiet workaround.
Measure it
Flag before you block. Run new rules in flag mode for a week, count matches per rule and per service, sample them for false positives, then switch the precise rules to redact or block. Keep counts — not the matched values — on every request, so you can show a reviewer how often each rule fired.
Guardrails in INFRO
INFRO's guardrails check the text of chat completion requests and responses, the prompts of image and video requests, and Claude Code and Codex traffic on its Messages and Responses endpoints, before INFRO sends the request to your provider. Detectors run inside the gateway with no outside service: email, phone, card numbers (issuer prefix and Luhn), IBAN (mod-97), US Social Security numbers, IP addresses, secrets with known prefixes (OpenAI, Anthropic, AWS access key ids, GitHub, Slack, Stripe live keys, Google API keys, JWTs, PEM private keys) and your own deny terms. Text is NFKC-normalised and invisible characters removed before matching, and tool-call arguments are checked inside the parsed JSON. Each rule blocks with 403, redacts to a placeholder such as [EMAIL], or only flags, on input, output or both. Optional classifier checks for prompt injection, jailbreaks and off-topic requests run on your own decision-model connection. Guardrails come with the Business plan, and INFRO's documentation says plainly that detection is pattern-based and can miss things: names, addresses and numbers written in words are not detected.
Summary
Minimise what reaches the prompt, detect the rest with the method that fits each data type, choose block, redact or flag per rule, check responses and logs as well as prompts, and run rules in flag mode before you enforce them. Describe the result as reducing what reaches your provider, because no detector finds everything.
