GDPR, Presidio and LLMs: What Redacting PII Actually Buys You
Every enterprise AI conversation I have in Germany reaches the same question: can we send this to the model at all? The reflex is to strip the personal data out of the prompt first, and Presidio is the tool most teams reach for.
The reflex is right. The reasoning behind it usually is not. So I spent a day running Presidio over German support mail, and a second reading what the CJEU and the EDPB have said since.
What the law actually says
Art. 4(1) defines personal data as information relating to a person identifiable “directly or indirectly”. That one word means removing the name does not settle the question.
Art. 4(5) defines pseudonymisation as processing such that data can no longer be attributed to a person “without the use of additional information”, kept separately. Note what that presupposes: the additional information still exists. A mapping table is not an accident of the design — it is what makes this pseudonymisation and not anonymisation.
Recital 26 draws the line: pseudonymised data “should be considered to be information on an identifiable natural person”, while genuinely anonymous information falls outside the Regulation — judged by the “means reasonably likely to be used”, by the controller or by another person.
Replace a name with <PERSON_0>, keep the mapping, and you have performed textbook pseudonymisation. You are still processing personal data: still a legal basis, still an Art. 28 processor contract, still a Chapter V analysis if the model sits outside the EU.
The judgment everyone half-quotes
In EDPS v SRB (C-413/23 P, 4 September 2025) the Court of Justice held at paragraph 86 that pseudonymised data “must not be regarded as constituting, in all cases and for every person, personal data” — a sentence that now travels around LinkedIn stripped of every qualifier it contains. It was the First Chamber, decided under Regulation (EU) 2018/1725, reaching the GDPR only via paragraph 52, and referred back to the General Court: nothing was finally determined.
What matters for architecture is paragraph 85: data not being personal for a recipient has no bearing on the controller who transferred it. The relative approach points away from you, not towards you.
The standard is also moving. The EDPB’s draft Guidelines 02/2026 on Anonymisation test anonymity against three criteria: no singling out, no linkability, no inference of attributes. Hold on to the third — it is what entity-based redaction is structurally worst at.
Presidio in one paragraph
Presidio is an open-source PII detection and anonymisation SDK. One correction, because most write-ups are stale: it is no longer a Microsoft project. The canonical repository is now data-privacy-stack/presidio, and the docs moved to presidio.dataprivacystack.org. presidio-analyzer and presidio-anonymizer are at 2.2.364 as of August 2026, and detection combines spaCy NER with pattern recognizers and checksum validators.
The architecture
The encrypt operator round-trips losslessly but uses a random IV, so it is useless as a join key. What works for LLM round-trips is a custom operator pair sharing a mapping dict, producing stable <PERSON_0> tokens. One trap: anonymize() returns offsets into the source document, useless against the model’s reply. Restoring the answer is a plain substitution:
reverse = {tok: orig for vals in entity_mapping.values() for orig, tok in vals.items()}
for token, original in reverse.items():
restored = restored.replace(token, original)
What it looks like on German text
Out of the box, AnalyzerEngine() supports exactly one language: English. Point it at German and it does not degrade gracefully — it produces confident nonsense. Sehr is tagged LOCATION at 0.85, Zu meinem Vertrag comes back a PERSON. Ask for language="de" and you get a ValueError, not a fallback.
Correct setup needs an NLP model, a language registration, and — the part that bites — the German recognizers registered explicitly:
nlp_engine = NlpEngineProvider(nlp_configuration={
"nlp_engine_name": "spacy",
"models": [{"lang_code": "de", "model_name": "de_core_news_lg"}],
}).create_engine()
analyzer = AnalyzerEngine(nlp_engine=nlp_engine, supported_languages=["de"])
# Without these: no German IDs, no credit cards, and no warning.
analyzer.registry.add_recognizer(DeTaxIdRecognizer(supported_language="de"))
analyzer.registry.add_recognizer(DeSocialSecurityRecognizer(supported_language="de"))
analyzer.registry.add_recognizer(CreditCardRecognizer(supported_language="de"))
Presidio ships 13 German recognizers — Steuer-ID, Sozialversicherungsnummer, PLZ, Kfz-Kennzeichen and more — and every one is enabled: false by default, so a German pipeline loads none of them. CreditCardRecognizer is enabled but scoped to en, es, it, pl, so a German pipeline silently detects no credit cards at all — a default that costs you an entity class.
Configured, a 535-character support mail yields 17 entities in 164 ms. The interesting part is the overlaps:
KUNDENNUMMER score=1.00 'KD-2024-889231'
PHONE_NUMBER score=0.40 '2024-889231'
EMAIL_ADDRESS score=1.00 'katharina.meinhardt@webmail-beispiel.de'
ORGANIZATION score=0.85 'katharina.meinhardt@webmail-beispiel.de'
One address, three competing labels. Business identifiers like KUNDENNUMMER need custom recognizers — twenty lines of regex, and the highest-value thing you will add.
Where it breaks
False negatives. Ansprechpartner: Dr. Weber is detected as nothing at all, while the same model handles Mein Name ist Thomas Mueller correctly. A German tax ID in its official spaced form, 30 866 963 147, is missed entirely: the shipped pattern matches unspaced digits only, and the same number unspaced scores 1.00.
False positives. Ich wohne Goethestr. 12, 10115 Berlin. returns Goethestr as a PERSON at 0.85 while the house number and postcode go undetected: the address is simultaneously over-redacted and under-redacted.
The important part: all of these score 0.85, exactly like the correct detections. These are not calibrated probabilities — no threshold removes Goethestr without also removing real names. Anyone planning to “just tune the confidence cutoff” should test that before it becomes an architecture.
The good news is real: German boilerplate produced no false positives, and performance is a non-issue at 5–25 ms per warm call.
The harder limit: redaction is not anonymity
Everything above is fixable with effort. This is not.
On the Text Anonymization Benchmark Presidio reaches 0.460 entity-level recall on direct identifiers — roughly half. A fine-tuned Longformer reaches 1.000 on the same corpus, so the gap is closable with domain adaptation; out-of-the-box detection is not where to set expectations.
Recall on direct identifiers is the easy metric anyway. Staab et al., Beyond Memorization (ICLR 2024): GPT-4 infers personal attributes from ordinary text at 85.5% top-1 accuracy, at roughly 1/100th the cost of human analysts. In the follow-up (ICLR 2025), a commercial anonymiser drops location-inference accuracy only from about 86% to 55% — while costing 0.38 on readability.
Read that against the EDPB’s inference criterion. Removing every entity Presidio can find leaves the writing style, the sequence of events, and the fact that this customer complained about a specific branch on a specific date. A model reads those — and increasingly, so do agents with a search tool.
What I would actually build
- Treat it as data minimisation under Art. 5(1)(c), not as an exemption. Write that in the DPIA. Do not write “anonymised”.
- Configure German properly, then measure on your own text — and write custom recognizers for your business identifiers first.
- Protect the mapping like the credential store it is, and reduce what the crossing means: an EU-hosted model is a materially different posture than a third-country API.
Bottom line
Presidio is a good tool doing a real job, and the honest description of that job is narrower than the pitch. It pseudonymises, and pseudonymised data is still personal data. The CJEU’s relative approach does not rescue the controller, and the EDPB’s inference criterion is aimed at what entity redaction cannot do.
Use it — I do. Just make the claim you can defend: we minimised what left our infrastructure, not we anonymised it.
The compliance argument was never going to be won in the regex.
Sources
- GDPR — Art. 4(1), 4(5), 5(1)(c), 28, 44; Recital 26.
- CJEU, EDPS v SRB, C-413/23 P, 4 September 2025 — paras 52, 85, 86.
- EDPB Guidelines 02/2026 on Anonymisation — draft, to 30 October 2026.
- DSK, KI und Datenschutz, 2024 — the German DPAs’ checklist.
- data-privacy-stack/presidio, its docs, and
default_recognizers.yamlwhere the German recognizers sit disabled. - Pilán et al., Text Anonymization Benchmark, 2022; Staab et al., Beyond Memorization (2024) and LLMs are Advanced Anonymizers (2025).
Verified against presidio-analyzer 2.2.364 and de_core_news_lg 3.8.0 on 11 August 2026; it will age. Identifiers are synthetic. Engineering write-up, not legal advice.