Presidio was built at Microsoft and is now governed by its community. It is one of the best open-source PII toolkits, and we learned from its source. We still wrote our own engine. Presidio reports where a pattern matched in a string, while our users need to know what to hide on a page, offline. Most of that work is deciding what not to flag.
A page is not a text stream
Presidio works on plain text. You give it a string, and it returns positions: characters 40 to 51 are a name. That suits chat messages or a database column. Our user is looking at a page.
A PDF page is a layout. Words sit in boxes at fixed coordinates, and one name can wrap across two lines, run across two columns or be rotated. The user blacks out a rectangle on the page. For us, finding a name and locating it are one step.
When our pipeline finds "Pierre Dubois" split across two lines, it draws two rectangles and covers both. A name found on page 1 is tagged again on page 4, so the reviewer does not flag one person eleven times. Presidio knows only positions in a string. Wrapping it in layout code would still leave the difficult work to us.
Detectors that have to agree
In Presidio, each recogniser works alone and you pool the results. That is flexible, but one over-eager recogniser puts all its false alarms in your output.
Our detectors have to agree. Which ones run depends on the language and on the packs installed:
- Regex: precise patterns for Social Security numbers, IBANs, emails, phone numbers and business IDs. A bare 9-digit number counts as an SSN only if the words around it say so.
- One entity model per language: BERT if its pack is installed, spaCy otherwise.
- GLiNER, for entity types the trained models miss. It is validated on French, German, Italian and Dutch, and does not run on other languages.
- An optional LLM layer, local or through a remote API you set up, for cases that depend on context. It is off by default.
A span two detectors agree on counts for more. A span only one detector found must meet a stricter threshold before the user sees it. How we weigh agreement, merge overlaps and carry results across pages is where our accuracy comes from. Presidio leaves that decision to you, and the code that makes it is most of an engine.
It runs with the network unplugged
The desktop app runs detection, text recognition and redaction on your machine by default. There is no cloud round trip and no API key. Only the optional Deep Analysis pass can use the network, and only if you point it at a remote API yourself.
Presidio can run locally too. But the common path to good accuracy is Microsoft's or Amazon's cloud language services, which we promised never to call. Its local option uses smaller models. That is the setup in our benchmark, and it floods financial documents with false positives.
Models raise a second problem. Only the English model is in the installer. The rest we download, pin, check for tampering, then load and unload. Windows will not delete a language pack while part of its model is still in memory, so we release every loaded copy first. That needs control over when each model loads, which a third-party engine keeps for itself.
Noise filtering is most of the work
In our head-to-head benchmark, Presidio's default install emitted 666 spans on 14 documents and we emitted 252. The benchmark has not been re-run against the current build, so read these as dated figures.
Most of what we drop is plain noise: countries in governing-law clauses, URLs in footers, job titles read as names, departments tagged as organisations, one person split into three spans. The role-title list alone holds 195 entries across seven languages.
Each filter is tuned per language on real contracts and runs where the detectors' results are compared. There it can weigh detector confidence and position on the page. You could rebuild this as add-ons to Presidio. Presidio would then only run the detectors, and we would carry a large dependency mostly to switch parts of it off.
For us, this difficult work is most of the product, and it is all intertwined. So we wrote the detectors too.
What it cost us
- We lose Presidio's range of recognisers. Its community ships entity types and locales we have not built. We write and test each one we want.
- We maintain everything ourselves. When a transformer library breaks compatibility, there is no upstream to report it to.
- We cannot borrow Presidio's name. "Built on Presidio" helps in a procurement meeting. We offer a reproducible benchmark instead, which is more work.
When to use Presidio instead
Presidio is probably the right choice if one of these fits:
- You scan text streams such as chat logs, database columns or API payloads, rather than laid-out documents.
- You want library-level control inside a larger Python system for data-loss prevention, and you will own the precision tuning.
- You can use a cloud entity service such as Azure or AWS and want one interface over several providers.
- You need many entity types and locales more than a clean review step on one kind of document.
Common questions
Why not use an existing detection library?
A library answers a different question. We need to decide what to hide on a page, offline, and most of that is deciding what not to flag.
Is the difference the model or the filtering?
Mostly the filtering. In the published run, most of the gap between 666 and 252 spans was noise our filters drop and a default install keeps. Our benchmark against Presidio lists the categories.
Does the engine run without a network?
The desktop app does: detection, encoding and export all run on the device. The free browser app ships its own model built for the browser, so no document is uploaded there either.