
DLP for LLMs: How to Hide Employee Names from an External Model Without Breaking Search
What happens to enterprise search when employee names have to be hidden from an external LLM? Replacing surnames with random placeholders protects the data but breaks the link between the request, the documents it finds, and the model's response. Here's how to reconcile privacy and usefulness: where to keep the mapping, why you need entity resolution, how the policy engine makes decisions, and why you have to check the model's response, not just the incoming prompt.
Companies are connecting AI assistants to internal data: Jira tasks, wikis, meeting notes. That data is full of names of colleagues, clients, and contractors. Under the GDPR, these are personal data. Sending them to an external model means processing them, and if the provider is outside the European Economic Area, it also means transferring them to a third country.
The simplest fix is to replace names with placeholders before a request goes to an external model. But imagine an employee writes: "find van Dijk's latest tasks on project X and compare them with the meeting discussions." If van Dijk becomes NAME_1 here and a random NAME_7 in the next message, search no longer understands that this is the same person. If the name stays as it is, it may leave the trust boundary. Privacy and usefulness end up in conflict.
When usefulness loses that conflict, employees don't stop using AI. They switch to personal tools: according to Microsoft and LinkedIn, 78% of people who use AI at work bring their own AI tools.^source The same names end up there, with no filter at all.
At Imaga, we're building an internal layer that our employees go through to work with LLMs and agents. One of the nastiest problems in a layer like this isn't just hiding a name or a phone number before a request goes to an external model. It's keeping the request's meaning.
This article covers an architectural approach that helps reconcile these requirements: the entity mapping is stored inside the trust boundary, and the model receives only a typed pseudonym.
Why does replacing names with NAME_1 break enterprise search?
A naive scheme with NAME_1-style placeholders has two flaws.
The first is local numbering. A number is usually assigned by position within a specific message. In the next request, the same person gets a different placeholder, and in a long conversation you easily end up with two entities that play the same role and carry different numbers.
The second is the loss of type and relationships. To an LLM, the string NAME_1 says almost nothing. A phone number, a surname, an organization, and a login all turn into identical chunks of text. The model can continue the sentence, but it's worse at matching the entity to search data and doesn't understand which operations are allowed.
So it helps to separate two tasks:
- 1Hide the value. The external model doesn't need to see "Jan van Dijk."
- 2Preserve the entity's identity. Within one request and its related results, it must be clear that [PERSON_1] is the same object as in the retrieved documents.
This resembles a foreign key in a database. A foreign key doesn't reveal the whole record, but it lets you join tables. The difference is that in our case, the key must not be handed to the user or the model as a way to recover the original value.

Foundling no. 8338 came to London's Foundling Hospital in May 1758 with a ribbon embroidered "Philip Hollond." The hospital renamed him Mark Jessop and sealed the ribbon into his admission record. In the 19th century, the governors put such tokens on display without noting whose they were, and researchers are still matching them back to the children. A placeholder that changes from message to message breaks the link the same way.
How does an LLM gateway with PII masking work?
Our internal layer works as a gateway in front of the LLM: a request goes through authorization, a DLP check, and a de-identifier, and is then sent to the provider. On the way back, the response goes through controlled rehydration, where the original values are put back in place of the tokens. The user's identity is passed in a service header and through proxy permissions, not in text that the model can read or tamper with.
The simplified path looks like this:

The external provider only ever sees the typed prompt. The mapping stays on the gateway, and revealing a real value for an incident investigation is a separate, audited path that the response never takes.
Define the trust boundary and deny the model access to the mapping; we'll cover how the mapping is stored below. Keep detection, entity resolution, the policy engine, and rehydration separate, and tag every detector signal with its source: regex, NER, a PII model, a decoder, or a rule.
How do you resolve van Dijk and jvandijk to the same employee, in context?
The most common mistake in searching corporate data is treating every name as equally sensitive and equally useless. In a work request like "invite van Dijk to the review," the surname may simply refer to an employee the user already has the right to look up. In a request against a client data export, the same string may call for a different policy.
We solved this with a directory and matching rules. In the dev tenant, the directory includes employees and client legal entities from internal systems, plus thirty internal tools. Records are split into employee, legal_entity, and other. The tenant administrator adds and revokes them through the API/UI, and spelling variants, such as the prefixes van and de or ij and y, are computed on the matcher side.
For a new request, the pipeline might look like this:
- 1The detector finds a span and its type: Van Dyk, PERSON.
- 2The normalizer brings the spelling to a canonical form: van Dijk. The prefix "van" is written both lowercase and capitalized, and outside the Netherlands "ij" often turns into "y."
- 3The entity resolver matches the variants van Dijk, Van Dyk, Dijk, van, and jvandijk to an internal entity_id.
- 4The policy engine checks whether this entity can be used in a specific source and action.
- 5The prompt gets [PERSON_1], not the original string; the directory's internal entity_id is not passed into the prompt.
For search, this changes everything. The index and filters work on the stable internal entity_id of the directory corpus, while only [PERSON_1] goes into the prompt. The LLM gets a typed reference but not the entity_id itself or a way to reveal the full name.
![Tree diagram: four spellings of the same surname and a login go through normalization and matching, then split into two branches; a name found in the tenant directory is allowed, with a note that the directory is not an access permission, and a name not found is masked as [PERSON_1], with search handled on the integration side.](https://jnrwvrykmhldwxmdmicc.supabase.co/storage/v1/object/public/media/public/2_3_1794587902731_5522j3.webp)
Spelling variants and a login collapse into one entity before any policy decision. A lone surname that could mean two people stays unresolved and gets masked.
Why isn't an employee directory an access whitelist?
An employee directory is not a whitelist for bypassing DLP. It answers "which object is meant," not "can its data be shown." These decisions need to be kept apart.
For example, searching an employee's tasks may be allowed while displaying their phone number is not. A lone surname without a first name should not be resolved by the directory automatically: when it's ambiguous, the policy chooses masking. An employee and a client legal entity are different kinds of entities with different rules. For clients, the directory should be a separate perimeter altogether.
What should a DLP policy do with a detected name: mask, block, warn, or allow?
Our internal DLP discussions converged on the idea that the detector and the de-identifier solve different problems.
This breakdown is more useful than a blanket ban. Block every name, and employee search stops being usable. Let everything from the internal directory through, and you can accidentally move data into the wrong source. The mapping preserves connectivity, but the policy engine still decides what's allowed.
The policy engine routes each request to one of the four outcomes. It has the tenant settings no_mask_labels, muted_labels, and fail_mode; directory suppression gets reason=directory and is highlighted separately in the UI:

Whether the engine picked the right verdict is measured on its own, apart from detection recall: one overall protection score would hide where the system fails.
How should you store the PII mapping? Protect the table, not the token
req_map, the current request's mapping, is not a dictionary we slip into the prompt. It's server-side session state. In production, the session mapping lives for two hours, is capped at 500 tokens, and works in insert-only mode: a repeated token doesn't overwrite the value. The context doesn't mix with another session's mapping.
If an incident investigation is needed, the mapping is stored encrypted; access to reveal it is separated by the pii_reveal permission and audited. Keys and long-term mappings should be kept separate from the main database.

In August 1914, the German cruiser Magdeburg ran aground in the Baltic. The Russians recovered its signal book and passed a copy to the British, and German naval radio traffic, which anyone could intercept, became readable. Tokens work the same way: the provider sees them, and the protection sits in the mapping.
A token can have a predictable format, such as [PERSON_1]. This raises the chance that the model won't distort it in a reasoning context or while generating JSON. Security, however, should rely not on the secrecy of the token text but on the inaccessibility of the mapping itself. If an attacker types [PERSON_1] by hand, the gateway must not "guess" the value: only tokens present in the current request's req_map are rehydrated.
That's why random ciphertext isn't always better. It's harder for the model, and its cryptographic properties don't save the architecture if the key or the mapping table sits next to the prompt.
The approach described here isn't "permanent anonymization." The additional information makes it possible to recover the original value, so the mapping table itself needs protection. Under the GDPR, this is pseudonymization: for the company holding the mapping, the data remains personal data.^source
How do you check the model's response, not just the prompt?
Checking only the prompt isn't enough. The model can return an entity in text, JSON, a quote, tool arguments, or a streamed response.
- Text: only tokens present in the current request's mapping may be rehydrated.
- JSON: check the structure and the allowed fields.
- Tool calls: check the tool name and arguments before execution.
- Streaming: the policy chooses the mode. Buffer: the response is released only after it has been checked in full. Window: a sliding-fragment check that can cut the stream off. Pass-through with a post-scan.
The choice of streaming mode is a deliberate trade of latency for strictness.

In the First World War, officers read their men's letters home and struck out anything that could help the enemy. The Field Service Post Card went further: a soldier could only cross out printed sentences, and if anything else was added, the card was destroyed. A gateway applies the same idea to a model’s JSON response, checking its structure and allowed fields.
How do you test that PII masking hasn't broken search?
A minimal test set should check not only whether PII was found but also whether meaning was preserved. Before launch, ask your team to run these cases:
Metrics should be kept separate: detection recall, false positives, the share of successful entity matches, correctness of policy decisions, and no rehydration from someone else's mapping. A single overall "protection score" hides where exactly the system fails.
We don't have an automated search relevance test yet. Instead, we rely on a mask → upstream → rehydrate canary every 10 minutes, a policy-verdict match metric, red teaming, and a feedback loop for false positives and misses.
Bottom line and a first step
De-identification doesn't have to destroy meaning. To get there, stop treating the placeholder as a substitute for the name and start designing an identity layer, with mapping, lifecycle, permissions, and tests. The model sees a stable entity, and the trusted perimeter decides which value stands behind it and whether it can be returned.
You can go wrong in both directions. Block every name, and the assistant becomes useless, so employees take the same names to personal chats. Let everything through, and the names go to the provider.
You can check where you stand with a single request. Ask your assistant to find a specific colleague's tasks and compare them with the meeting discussions, as in the van Dijk example, then look at what went to the provider and what came back. If the name went out in plain text or the assistant got confused by the placeholders, start with the test set above.
![Diagram of one request split by a dashed trust boundary. On the inside: the employee asks for van Dijk's tasks, search finds two Jira tasks assigned to jvandijk and meeting notes mentioning Jan van Dijk and Sophie Jansen, and req_map links all three spellings to [PERSON_1]. On the provider side, every name appears as [PERSON_1] or [PERSON_2], in the prompt and in the response. Back inside, the employee sees the answer with the real names restored.](https://jnrwvrykmhldwxmdmicc.supabase.co/storage/v1/object/public/media/public/2_7_1794587904416_4f70hv.webp)
A passing check: the provider gets the same [PERSON_1] in the question, in both Jira tasks, and in the meeting notes, so it can still connect them, while the real names stay inside the boundary.
If you're connecting an AI assistant or agents to your company's internal data and don't want to send employee and client names to an external model, talk to Imaga. We'll help you design a gateway for your systems and test it before launch.
GenAi case studies

Self-Hosted RAG Chatbot for AKFA Holding

Designing an AI-Powered Journaling App for Daily Reflection and Personal Insights

Scientific Trial PDFs Extraction

SMM Content Automation with AI

17–22 PM Hours Saved per Project With AI Workflows

From a Month of Production to a 2-Minute Draft: AI Case Study Pipeline
