PII masking for LLMs: three ways to keep personal data out of prompts
As of 12 October 2026
PII masking for an LLM means that names, email addresses, account numbers and similar values are detected in the prompt and swapped for placeholders before the text goes to the model provider. You can do that with a library you run yourself, such as Presidio, with a DLP service you call over an API, such as Nightfall, or inside the gateway that already sits between your apps and the models. Which one fits depends less on detection quality than on two questions: where the unmasked text travels on its way to being masked, and whether you need the real values back in the answer.
Where the clear text goes
Draw the path of one prompt first. Masking protects only the hops after it.
| Library in your code (Presidio) | DLP API (Nightfall) | Masking in the gateway (Myra) | |
|---|---|---|---|
| Who sees the unmasked prompt | Your own process | Your process, then Nightfall's scan endpoint | Your process, then Myra's gateway and its detection engine |
| What the model provider receives | Whatever your code sends after anonymising | The redacted payload Nightfall returns | The tokenised request |
| Getting the original values back | Your job (encrypt/decrypt or your own mapping) | No restore step in the OpenAI tutorial | Built in, if target is "both" |
| Who maintains recognisers and thresholds | You | Nightfall's detectors, configured by you | Admins per gateway, engine run by Myra |
| What you have to build | Integration into every app, mapping store, monitoring | One API call per prompt in every app | Point the client at the gateway endpoint |
With a library, nobody besides the model provider is involved. A DLP API adds a second processor that sees every prompt in clear text. A gateway is also a processor, but it is the hop that already holds your provider keys.
Presidio, the library you run yourself
Presidio is an open source framework for finding and anonymising personal data in text, images and structured data. Its repository used to sit under Microsoft's GitHub organisation; github.com/microsoft/presidio now redirects to data-privacy-stack/presidio, and the README says Presidio "has moved to a new home" as a community-owned project. The licence is MIT, and the project was active when we checked: release 2.2.364 came out on 22 July 2026, the latest commit on 8 October 2026.
The analyzer finds entities and returns spans with a confidence score; the anonymizer applies an operator to each span. A minimal round trip in Python:
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
text = "Please draft a reply to Jana Novak, jana.novak@example.com"
results = AnalyzerEngine().analyze(text=text, language="en")
masked = AnonymizerEngine().anonymize(text=text, analyzer_results=results)
print(masked.text) # Please draft a reply to <PERSON>, <EMAIL_ADDRESS>
Besides global types like EMAIL_ADDRESS and IBAN_CODE, the predefined recognisers include country sections, among them a German one with tax ID, ID card and health insurance numbers.
The operators are replace, redact, hash, mask, encrypt, custom and keep. Reversal exists for exactly one of them: the deanonymize engine's decrypt operator undoes encrypt with the same key. So if you want "Jana Novak" back in the model's draft, you either hope the model copies a ciphertext string character for character, or you write a custom operator that issues readable placeholders and stores the mapping yourself, per conversation, somewhere safe.
Presidio's README warns that it may not find all sensitive information and recommends additional protections. The same holds for every tool on this page.
Choose Presidio when the text must not leave your network before masking, or when you also mask logs or training sets. It costs engineering time: thresholds per language, false positives on product names, a mapping store, model updates.
Nightfall as a DLP service
Nightfall positions itself as data loss prevention across "AI agents, MCP servers, endpoints, and SaaS". On its homepage it describes stopping "corporate data leaving through personal ChatGPT, unmanaged copilots and browser AI tools", and, for Claude's apps and Claude Code, "Inference Hooks" that "inspect and block prompts, tool calls and responses inside the live session". For developers there are the Nightfall Developer APIs, which the help center says were formerly known as Firewall for AI.
The developer flow is a scan call. You send the prompt to the Nightfall text scan endpoint, get back the findings and a redacted version, and pass that version to OpenAI or another provider. Redaction can mask characters, substitute a fixed phrase, substitute the detector name, or encrypt a finding with your RSA public key. The tutorial for OpenAI does not describe restoring the original values in the response.
Nightfall is the better choice for teams whose problem is employees pasting data into public chatbots and SaaS apps. A gateway in front of your own apps does not reach that. The trade-off for developers is the extra hop: every prompt goes in clear text to a separate vendor, which needs its own processing agreement and its own review of where it processes data. Ask where scanning happens and how long payloads are kept before you sign.
Masking inside the gateway
The third option moves masking into the proxy that routes your model traffic anyway. In Myra AI Workspace this is the PII Protector guardrail. Personal data is replaced by placeholders before it reaches an external model and can be restored in the answer. The detection engine runs at Myra; the documentation names a locally hosted Presidio sidecar as its NLP analyzer, so the detection core is the project discussed above. Masking is available in every plan.
A placeholder looks like [MYRA-REDACT-EMAIL_ADDRESS:a3f1c2:9b1d4f7a2c3e]. The visible type lets the model write "I'll reply to that address" sensibly. The middle part is stable for a whole conversation and the last part is derived from the value, so the same person keeps the same token across turns.
Read the defaults before relying on it. A detector with three of them changed looks like this:
{
"type": "pii_protector",
"name": "mask-personal-data",
"target": "both",
"fail_open": false,
"de_recognizers": true
}
The docs give "request" as the default for target. With that default the request is tokenised but the response is not restored, so your client gets raw tokens back; "both" covers request and response. fail_open defaults to true, meaning a request goes through unmasked if the detection service is unavailable. A project set to "PII protection required", or a tenant that enforces masking, refuses such a request regardless. The German structured-ID recognisers (tax ID, VAT ID, health insurance number and others, validated by checksum) are off until you set de_recognizers. English and German are fully supported; other Latin-script languages are handled on a best-effort basis.
Two further limits from the docs. If the model paraphrases a token instead of repeating it, that value is not restored, though the provider still never saw it. And a request that goes entirely to a Myra-hosted model is not masked, because it does not leave Myra; those models run on Myra's own EU infrastructure and need the "EU-Gov" add-on on paid self-service plans. In the chat interface, users see a preview of what will be masked and can mask a missed term by hand.
If text must be masked before it leaves your own network, the managed gateway is the wrong tool; on-premises deployment is available on request.
What reversible masking actually means
There are three outcomes worth telling apart. Redaction removes a value for good: <PERSON>. Hashing gives a consistent stand-in but no way back for the application. Reversible tokenisation keeps a table that maps each token to its original value, so the answer can be filled in again.
Reversibility is what makes masking usable for real work. A model drafting a letter or summarising a case file needs to keep people apart, and the user wants their names back in the result.
The cost is that somebody holds the table. Under Art. 4(5) GDPR this is pseudonymisation: the data can be attributed to a person with additional information that is kept separately. Recital 26 says such data is still personal data. Whoever holds the mapping, be it your app, your DLP vendor or your gateway operator, is processing personal data.
What masking leaves open under GDPR
The GDPR names pseudonymisation as a safeguard in Art. 25 (data protection by design) and Art. 32 (security of processing). Switching it on still leaves these open:
- Detection misses things. A diagnosis described in prose, a rare surname read as a common noun, a customer number in your own format: if no recogniser matches, the value goes out in clear text. Test with your real document types.
- Context identifies people too. "The only female board member of our Leipzig subsidiary" contains no entity a recogniser will flag.
- Not everything is text. Images and PDFs embedded as binary data cannot be scanned like text; Myra's gateway rejects inline base64 files on a PII-protected gateway for that reason.
- Logs hold clear text. If your proxy or app logs prompts before masking, the log is the leak. In Myra's gateway the request log stores prompt and response by default (
log_payloads); switch it off per gateway or per request if you do not want that. - The legal groundwork stays. You still need a legal basis, information for the people concerned, processing agreements with every vendor in the path and a decision on transfers to non-EU providers.
Choosing between them
One or two apps and no appetite for another vendor: start with Presidio and budget for the mapping. Staff pasting data into tools you do not control is a DLP problem, which Nightfall targets. Several teams calling several providers is where the gateway pays off; set target to "both", decide on fail_open and check what the log stores.
For how a gateway fits into the rest of the stack, see the AI gateway guide; for the workspace around it, Myra AI Workspace.
Frequently asked questions
Is Microsoft Presidio still maintained?
Yes. The repository github.com/microsoft/presidio now redirects to github.com/data-privacy-stack/presidio, and the documentation lives at presidio.dataprivacystack.org, and the project's transition notes say it has become a community-owned project. The code is MIT-licensed; the last commit before 12 October 2026 was on 8 October.
Can Presidio restore the original values after the LLM answers?
Out of the box only through encryption. The anonymizer has an encrypt operator, and the deanonymize engine reverses it with the decrypt operator and the same key. That only works if the model copies the ciphertext unchanged into its answer. For readable placeholders you write a custom operator and keep your own mapping.
Does Nightfall restore redacted values in the model's response?
Nightfall's OpenAI prompt sanitization tutorial describes scanning the outgoing prompt via the Nightfall API and sending the redacted payload it returns to OpenAI. We found no step that puts the original values back into the answer.
Is an LLM with PII masking GDPR compliant?
Masking is a safeguard, and the GDPR names pseudonymisation as one in Art. 25 and Art. 32. Legal basis, transparency, processing agreements and transfers remain your job, and reversibly masked data stays personal data for whoever holds the mapping.
Sources
- Presidio on GitHub (data-privacy-stack/presidio, redirected from microsoft/presidio): README and MIT LICENSE, retrieved 12 October 2026
- Presidio documentation: home, retrieved 12 October 2026
- Presidio documentation: anonymizer and deanonymizer operators, retrieved 12 October 2026
- Presidio documentation: supported entities, retrieved 12 October 2026
- Nightfall: homepage, retrieved 12 October 2026
- Nightfall help center: Developer APIs (formerly Firewall for AI), retrieved 12 October 2026
- Nightfall help center: Scan API and redaction configuration, retrieved 12 October 2026
- Nightfall help center: OpenAI prompt sanitization tutorial, retrieved 12 October 2026
- Myra AI Workspace documentation: PII Protector, retrieved 12 October 2026
- Regulation (EU) 2016/679 (GDPR), Art. 4(5), Art. 25, Art. 32 and Recital 26, EUR-Lex, retrieved 12 October 2026