# PII Redaction by [kirupa](https://www.kirupa.com/me/index.htm) | filed under [AI Fundamentals](https://www.kirupa.com/ai_fundamentals/index.htm) People type the most personal things into chat boxes. They'll paste a full card number into a support chat, include their home address ***just in case***, and mention their kid's school while asking about a data plan. If our AI feature is on the other end of that chat box, all of that personal information (aka PII, short for personally identifiable information) flows into our system along with the question. The model needs some of that information to help. Most of it, though, the model doesn't need at all, and our logs need even less. PII redaction is how we find personal details in text and swap them out for something harmless before they end up somewhere they shouldn't. In this article, we'll look at where that data goes, how to find it, what to replace it with, and how to put it back in the few places where it belongs. Onwards! ## One Message, Many Destinations Imagine we run the support assistant for Beacon Mobile, a phone carrier. A customer named Tomás Rivera sends this: > Hi, this is Tomás Rivera. Will you refund me? My card 4242 4242 4242 4242 was charged twice. Call 555-0142 or email tomas.r@example.com. Account 88-1203-77. My wife ana pays it. (Don't worry, that card number is a well-known test number, and the phone number is from the range set aside for fiction.) Here is how casually that lands in Beacon Mobile's support chat: ![Beacon Mobile's support chat. Tomás Rivera writes, Hi, this is Tomás Rivera. Will you refund me? My card 4242 4242 4242 4242 was charged twice. Call 555-0142 or email tomas.r@example.com. Account 88-1203-77. My wife ana pays it. The assistant shows three typing dots.](https://www.kirupa.com/ai_fundamentals/images/pii_redaction_chat_tomas.png) It's easy to think of this message as going to ***the AI***. In practice, a single message fans out to a surprising number of places: ![Tomás's single support message fans out to five places: the model API, traces and prompt logs, analytics, an eval dataset, and the support ticket tool.](https://www.kirupa.com/ai_fundamentals/images/pii_redaction_fanout.png) Each of those destinations has different people who can read it, different retention rules, and sometimes a different company running it. The trace store from [Tracing and Prompt Logs](https://www.kirupa.com/ai_fundamentals/tracing_and_prompt_logs.htm) might keep data for 30 days and be readable by every engineer. The eval dataset might be copied into a spreadsheet and live forever. Our job is to make sure each destination gets only the personal data it actually needs, which for most of them is none. ## Step 1: Finding the PII Redaction has two parts: find the sensitive bits, then replace them. Finding them is the harder part, and it uses two kinds of detectors. The first kind is patterns. Email addresses, phone numbers, card numbers, and government ID numbers have predictable shapes, so regular expressions catch most of them. We can make the patterns smarter with checks. For example, real card numbers pass the Luhn checksum, so a random 16-digit order number that fails it won't be mistaken for a card. We can also add patterns for our own identifiers, like Beacon's `88-1203-77` account format, which no off-the-shelf tool knows about. The second kind is named entity recognition, which is a model trained to spot names, places, organizations, and addresses in ordinary sentences. Names don't follow a pattern, so a model like this is the main way to find them. Open-source libraries like [Presidio](https://data-privacy-stack.github.io/presidio/) combine both kinds of detectors and let us plug in our own. Here is what a detector finds in Tomás's message, including two mistakes: ![The detector correctly tags Tomás Rivera as a person, the card number, the phone number, the email address, and the account number. It also wrongly tags the word Will as a person, and it misses the lowercase name ana.](https://www.kirupa.com/ai_fundamentals/images/pii_redaction_detect.png) The detector tagged ***Will*** as a person, because ***Will*** is a perfectly good name. And it missed ***ana***, because a lowercase name tucked at the end of a sentence doesn't look much like a name. Neither kind of detector is perfect, so we're always trading off between these two mistakes. How we tune that trade-off depends on the destination. For logs and analytics, a false positive (flagging harmless text as PII) is cheap. Losing the word ***Will*** from a log line hurts no one, so we can redact aggressively. For the text we send to the model, over-redaction can break the task itself: ![A letter so heavily redacted that only Dear and Sincerely survive, next to a puzzled robot with a question mark, trying to make sense of it.](https://www.kirupa.com/ai_fundamentals/images/pii_redaction_over_redacted.png) ## Step 2: Replacing It With Something Useful Once we've found the PII, we have to decide what to put in its place. The simplest option is to mask it with asterisks, and it's also the worst option for anything a model reads. Here is the same sentence with three different replacement strategies: ![The same sentence with three replacement strategies. Asterisks lose who is who. Typed placeholders like PERSON_1 and CARD_1 keep the meaning without the data. Fake values look real, which makes them easy to confuse with real data.](https://www.kirupa.com/ai_fundamentals/images/pii_redaction_replace.png) With asterisks, the model can't tell Tomás from his wife or a card from a phone number. Fake values keep the sentence natural, but they look real, which makes them easy to confuse with real data later. Typed placeholders like `[PERSON_1]` and `[CARD_1]` are usually the sweet spot. The model can still reason about the conversation (***the customer, PERSON_1, says CARD_1 was charged twice***) without ever seeing the actual values. Two details make placeholders work well. They should be consistent within a conversation, so Tomás is `[PERSON_1]` in every message, and they should carry the type, so the model knows a card from a phone number. ## Step 3: Putting It Back Where It Belongs Placeholders create a new puzzle. The assistant needs to refund the right card and greet Tomás by name. How does it do that if it never sees the real values? The answer is to keep a small lookup table (the mapping from `[PERSON_1]` to ***Tomás Rivera***) on our side, in a locked-down store often called a vault, and to swap real values back in only at the edges where they're needed. Here is the full round trip: ![Six steps. Tomás types his card number. Redaction turns it into CARD_1 before the model sees it. The model asks to refund ACCOUNT_1. The tool layer looks up the real account number in the mapping vault and makes the refund. The model replies to PERSON_1, and the restore step turns that into Hi Tomás with only the last four card digits. The logs only ever see the placeholder versions.](https://www.kirupa.com/ai_fundamentals/images/pii_redaction_round_trip.png) The model calls a tool like `refund("[ACCOUNT_1]")`. Our tool layer, which is ordinary code, looks up the real account number in the vault and makes the refund. When the model writes ***Hi [PERSON_1]***, our code swaps in ***Tomás*** right before the reply is shown, and for the card we choose to show only the last four digits. The logs and traces store the placeholder version of everything. The vault itself is now the most sensitive thing in the system. It should be encrypted, readable only by the redact and restore steps, scoped to one conversation, and deleted when that conversation ends. It should never be logged, which sounds obvious until someone adds a debug line that prints the whole request object. ## The Parts That Are Easy to Miss A few things catch teams off guard, even after the basic pipeline works. Model output needs checking too. The model can produce personal data that wasn't in the user's message at all, because it came from a retrieved document or a tool result. If a lookup tool accidentally returns the wrong customer's address, the model will happily repeat it. Running the same detector over outputs, and designing tools that only return what's needed (see [Least-Privilege Access for Tools](https://www.kirupa.com/ai_fundamentals/least_privilege_tools.htm)), catches this. Telling the model ***never reveal personal information*** in the [system prompt](https://www.kirupa.com/ai_fundamentals/system_prompts_vs_user_prompts.htm) is only a request. It helps a little, but a cleverly worded message can talk the model out of it, so it can't be the only thing standing between a card number and a log file. Hashing isn't always anonymous. Teams sometimes hash phone numbers so analytics can count repeat customers. But there are only so many phone numbers, and an attacker can hash all of them and match the results. A keyed hash (like HMAC, where the key is a secret) avoids that. Here is the difference a secret key makes: ![Hashing phone numbers. With a plain hash, there are only so many phone numbers, so an attacker can hash all of them and match the results. With a keyed hash like HMAC, the key is a secret, so an attacker can't make matching hashes.](https://www.kirupa.com/ai_fundamentals/images/pii_redaction_keyed_hash.png) It's also worth collecting less in the first place. A note above the chat box that says ***Never share your full card number here*** will keep more card numbers out of our system than any detector. And if we don't need to store a conversation at all, not storing it beats redacting it. Finally, laws like the GDPR in Europe and HIPAA for US health data have specific rules about personal data. Redaction is one of the tools for following them, but your privacy or legal team should decide what's actually required for your product. ## Conclusion Tomás's one message could easily have left copies of his name, card number, phone, email, and account number in five different systems. With redaction in place, the model sees `[PERSON_1]` and `[CARD_1]`, the logs store only placeholders, the refund still lands on the right account, and Tomás still gets a reply that greets him by name. Detectors will always make some mistakes, so we tune them per destination, aggressive for logs and gentler for the model's input, and back them up with output checks and with simply collecting less. The other half of keeping data safe is controlling what our tools are allowed to touch in the first place, and that's where [Least-Privilege Access for Tools](https://www.kirupa.com/ai_fundamentals/least_privilege_tools.htm) picks up.