Product · Personal data

83 kinds of identifier, found before the request leaves.

The address that finds it is the address that rewrites it. A prompt is assembled at runtime out of a conversation, a record lookup and a tool result, so what personal data leaves is a property of each request rather than of the code. One process holds the connection while that request is inspected, rewritten and forwarded.

what it reads
the request body and the response stream, in the process holding the connection
the operators
redact, mask, hash, placeholder, pseudonym, fpe
what it costs
16.8 µs on a 7 KB 20-turn conversation when one turn is new
turned back for
the tools you dial, by restore list

The pii detector finds 83 kinds of identifier with nothing configured: card and bank numbers, national identifiers, passports, driving licences, tax, health and vehicle numbers, phone numbers, email addresses and dates. Names are not among them, because a name has no shape a pattern can describe; that is what the nlp detector and a model are for. 51 of the 83 carry a checksum, so a match the arithmetic rejects is dropped instead of redacted. 9 are recognizers pistra adds where Presidio has none, and they are Gulf: Saudi national ID, unified number and VAT number, UAE Emirates ID, passport and TRN, Kuwait civil ID, Qatar ID, and IBAN. The same detector finds 19 kinds of API key and private key material, annotated credential/… because a key is not personal data. Every type, its patterns and its checksum, and the credential page.

Coverage is not the same as a guarantee. A rule decides which of those types it acts on, a validating rule leaves an identifier the checksum could not confirm, and the page below says what each operator costs the request it rewrites.

what the client sent

Please cancel my card 4095260993934932, my id is 1044880134, and mail me at aarav@example.com.

pii · fpe · placeholder
what the provider received

Please cancel my card 6880989289285872, my id is <SA_NATIONAL_ID_1>, and mail me at <EMAIL_ADDRESS_1>.

the card is still sixteen digits and still passes Luhn, reversible only by this gateway

403
sa-ndmo-personal-data-abroad-needs-approval guardrail rule
personal data is processed outside the Kingdom only through a provider whose metadata declares the written approval as transfer_basis

One pass, one address

A detector returns a finding as byte offsets into the text as sent. The operator rewrites those bytes. Nothing is re-parsed between the two, so a rewrite cannot land on the wrong characters because a second pass disagreed about where the value started. Everything the rule did not select is forwarded byte for byte.

So a redaction cannot miss a field the detector found, and cannot damage one it did not.

Where this sits

A DLP programme covers endpoints, mail and storage. This covers the request, at the last hop where it is still yours to change.

Responses are inspected the same way and are not buffered to do it. A 7 KB 20-turn conversation with every recognizer enabled costs 16.8 µs to inspect when one turn is new, and 341 µs with a cold cache, on an Apple M4 over loopback. How those were taken, and what they leave out.

Every operator breaks a tool call

The operator is chosen per rule, and the choice is a trade between what the model can still work with and what a tool downstream can still work with.

operatorwhat the provider receiveswhat a tool receives
redactthe span is removedan empty argument, and an error
mask************asterisks, and an error
hash64 hex charactersa hash, and an error
placeholder<PERSON_1>a placeholder, and an error
pseudonymGrace Hughesa name. It may find the wrong customer
fpe4532-7712-0483-1965a valid card. It may find the wrong account

Realism is the point of pseudonym and fpe, and it is the one place they are worse than the operators they improve on. A stand-in that reads as ordinary text reads that way to a tool as well. The first four operators break a tool call loudly: the argument is empty or malformed, the tool errors, and something in the trace says so. Hand a tool Grace Hughes or a well-formed card number instead and there is nothing to notice. lookup_customer(name="Grace Hughes") may find a real customer, the wrong one, and return 200.

Where a tool consumes the value and cannot be told the real one, placeholder is the operator to reach for. It produces worse text for the model and a failure somebody notices, which on a path ending in a lookup is the better trade. Refusing the call outright is the other answer, and a rule's when can read the server and the tool to say where.

A card stays a card

fpe encrypts a span into another span of the same shape with FF1, from NIST SP 800-38G. The implementation is written against the specification rather than taken from a dependency, and it is checked against NIST's own worked examples for all three AES key sizes, both sample radices, tweaked and not. FF3 is not offered, having been withdrawn after Durak and Vaudenay broke it.

A replacement that fails Luhn is rejected by anything that checks it. A replacement that passes Luhn but begins 59 is worse, because no issuer uses that prefix: no recognizer matches it, no later scan finds it, no rule meant to see it fires, and nothing can turn it back. The cycle walk keeps drawing until it has one that survives both.

FF1 is refused below a million possible values, the floor the draft revision of SP 800-38G raised it to. Four decimal digits is ten thousand, and an attacker holding the ciphertexts has the codebook, so the operator declines rather than pretending. Every way of declining ends at a numbered placeholder. Declining to encrypt is never permission to pass the value through.

Turning it back

A rewritten value is turned back for the tools you trust. An MCP server's restore list names the entity types it is allowed to receive in the clear, which is the declaration that the server is inside your boundary. It applies only at a hop pistra dials, because that is the only destination anyone can promise anything about.

Names are the exception. A pseudonym is a many-to-one fold followed by a keyed pick from a word list, and neither half inverts. The forward map takes unbounded text to a short plausible name, so anything that inverts it has stored the original somewhere. A PERSON reaching a tool that wanted the real one keeps failing quietly, and a server with no restore list receives stand-ins by design.

What finds it

Detectors run in the same process as the connection, and a rule chooses which. Patterns carry checksums and context lift, so a nine-digit run becomes a routing number when it satisfies the ABA check and the words around it agree. Models are pinned ONNX exports run through the gateway's own tokenizer binding. type: remote speaks presidio-analyzer's /analyze, so an existing Presidio deployment is a valid detector with no adapter to write.

pistra ships some models and pins other people's, and either can be a HuggingFace commit you name yourself. Both are measured on this build, against the traffic a deployment carries rather than the sets on a model card, before they go in. That is what the reports are, and choosing a detector is how the numbers in them were taken.

Measured, not asserted

The pattern tier alone, no model, over 20,000 documents of Nemotron-PII, four inspected at a time, at 0.26 ms a document:

typePRF1
EMAIL_ADDRESS0.9980.9970.997
MAC_ADDRESS0.9940.9990.996
CREDIT_CARD0.8100.9910.891
ABA_ROUTING_NUMBER0.8271.0000.905

Scored against the set as published, card recall is 0.117, because 11.9% of that set's card numbers pass Luhn and the recognizer correctly refuses the rest. The rows above are scored against the gold restricted to numbers that satisfy their own checksums. Any model trained on that set learns that invalid card numbers are cards. A checksum tier does not.

For Arabic, the pinned names model scores 0.748 exact and 0.934 overlap-allowed on a Gulf chat set built for the hazards no public corpus carries. The clitic slice scores 0.246 exact, because every Arabic NER corpus annotates a prefixed و, ل or ب as part of the name and a redactor wants it outside the span. The report says what that costs.

Threat model

The asset is a customer's identifiers: a card number, a name, a national ID, an address. The text that carries them is written by your users, by your own records when a lookup is pasted into the prompt, and by a tool's reply. It arrives in the request body and comes back in the response stream. The control is a detector that returns byte addresses and an operator that rewrites them, in one process, before the provider sees the request. The limit is the shapes the detectors know: paraphrase, an unusual encoding or a description of a secret pass through. Treat this as compliance and accident prevention rather than as an adversary-resistant filter.

The entry this section expands, and the ten beside it: threats and limits.

What this does not do

  • It does not find AI it is not in front of. What comes through pistra is seen, and drift on a key pistra issued is reported. Traffic on a credential nobody told us about is not discovered.
  • It carries no regime. There is no PDPL mode and no GDPR mode, and neither word is in the code. What ships is entity types and controls identifiers you attach to a rule. The mapping to a regime is your reading of it. If you want that reading bought rather than made, buy prepackaged framework packs from a detector vendor.
  • It does not fold coreference. Values are folded on case, whitespace, surrounding punctuation, a trailing possessive and a leading honorific, and it stops there. K. Reeves does not fold into Katherine Reeves, which needs the surrounding text and does not belong inside a rewrite pass.
  • Arabic clitic boundaries are open. The span comes back one letter long on names carrying a prefixed clitic, which over-redacts and breaks the grammar of the sentence around it. It is measured, published and not yet fixed.

The argument

  • Personal data in a prompt. Asked what personal data leaves the company, a code review cannot answer, because a prompt is assembled at runtime out of a conversation, a record lookup and a tool result. The request itself is where the question has an answer.
  • Every operator breaks a tool call. A redaction operator is chosen per rule, and the choice trades what the model can work with against what a tool downstream can work with. Realism is where the trade breaks.