- what it reads
- the request body and the response stream, in the process holding the connection
- the operators
replace,redact,mask,hash,placeholder,pseudonym,fpe- what a stream waits for
- the longest span a selected detector could still be growing into
- turned back for
- the MCP servers and agents you dial, by
restorelist
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. One process holds the connection while that request is inspected, rewritten and forwarded, and the address that finds a value is the address that rewrites it.
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; the nlp detector and a model find those. 51 of the 83 carry a checksum, so a match the arithmetic rejects is dropped instead of redacted.
9 are recognizers Gate 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 Oman's 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 are listed, and credentials have their own page.
Coverage does not decide what happens to a value. A rule decides which of those types it acts on, a validating rule leaves an identifier the checksum could not confirm, and the sections below say 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.
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. Only this gateway's key turns it back.
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. Gate covers the request, at the last hop where it is still yours to change.
Responses are inspected the same way. A streamed response is released as it arrives, held back only by the longest span a selected detector could still be growing into, and each type's longest span is on the coverage page. The commit horizon explains how a stream is released.
Every operator breaks a tool call
The operator is chosen per rule. The choice is a trade between what the model can still work with and what a tool downstream can still work with.
| operator | what the provider receives | what a tool receives |
|---|---|---|
replace | <PERSON> | a type name, and an error |
redact | the span is removed | an empty argument, and an error |
mask | ************ | asterisks, and an error |
hash | 64 hex characters | a hash, and an error |
placeholder | <PERSON_1> | a placeholder, and an error |
pseudonym | Grace Hughes | a name. It may find the wrong customer |
fpe | 4532-7712-0483-1965 | a valid card. It may find the wrong account |
Realism is the purpose 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 five 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, use placeholder. 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.
Format-preserving encryption
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, without a dependency, and it is checked against every sample in NIST's own worked examples. FF3 is not offered: it was 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 values, and an attacker holding the ciphertexts has the codebook, so the operator declines. Every way of declining ends at a numbered placeholder. Declining to encrypt is never permission to pass the value through.
Turning it back
A value fpe rewrote is turned back for the systems you trust. An MCP server's or an A2A agent's restore list names the entity types it may receive in the clear, which is the declaration that it is inside your boundary. It applies only at a hop Gate dials, because that is the only destination anyone can promise anything about, and only in the tool arguments or message parts going there.
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
The pattern and model tiers run in the process that holds the connection, and a rule chooses which detectors it reads. Patterns carry checksums and context words, 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.
Gate ships some models and pins other people's, and either can be a Hugging Face commit you name yourself. Both are measured on the build that runs them, against the traffic a deployment carries, before they go in. The research reports are those measurements, and choosing a detector is how the numbers in them were taken.
Precision and recall on a public set
These are the pattern tier alone, with no model, over the first 10,000 documents of Nemotron-PII at threshold 0.55.
| type | P | R | F1 |
|---|---|---|---|
EMAIL_ADDRESS | 0.998 | 0.997 | 0.998 |
MAC_ADDRESS | 0.998 | 1.000 | 0.999 |
US_SSN | 1.000 | 0.961 | 0.980 |
Scored against the set as published, card recall is 0.114, because 89% of that set's card numbers fail Luhn and the recognizer correctly refuses them. Routing numbers are the same story with the ABA checksum. On the 8,458 documents whose numbers are all valid, recall is 1.000 for both, at precision 0.75 for cards and 0.81 for routing numbers. Any model trained on that set learns that invalid card numbers are cards. A checksum tier does not.
For Arabic names, the XLM-R model the report recommends scores 0.748 exact and 0.934 overlap-allowed F1 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. This stops accidents and produces evidence. An adversary with time is a different problem. The entry this section expands sits with the others in threats and limits.
- The address that finds it is the address that rewrites it. A detector returns byte offsets into the text as sent and the operator rewrites those bytes, with nothing re-parsed in between.
- 51 of the 83 carry a checksum. A match the arithmetic rejects is dropped instead of redacted, so a validating type does not spend its false positives on your users.
- 9 recognizers Presidio has none for, all Gulf. Saudi national ID, unified number and VAT number, UAE Emirates ID, passport and TRN, Kuwait civil ID, Qatar ID and Oman's IBAN.
- A card stays a card.
fpeis FF1 from NIST SP 800-38G, checked against NIST's worked examples. The cycle walk keeps drawing until the replacement passes its checksum and carries a prefix an issuer uses. - Declining to encrypt is never permission to pass the value through. FF1 is refused below a million possible values, and every way of declining ends at a numbered placeholder.
- The regime is your reading. There is no PDPL mode and no GDPR mode. A control pack such as
sa-pdplis rules thatkirto-gate pack addcopies into your configuration, and from then on you choose which entity types count and which rule discharges which control.
Where the pass ends
- It does not find AI it is not in front of. What comes through Gate is seen, and drift on a key Gate issued is caught by the coverage audit. Traffic on a key nobody told it about is neither.
- It does not fold coreference. Values are folded on case, whitespace, surrounding punctuation, a trailing possessive and a leading honorific. A pronoun or a description standing in for a name earlier in the conversation is a different string and is treated as one.
- Arabic clitic boundaries are open. On names carrying a prefixed clitic the span comes back one letter too long, so the clitic is rewritten with the name and the sentence loses the word it belonged to.
The posts behind this
- Personal data in a prompt. A code review cannot list the personal data that leaves the company, because a prompt is assembled at runtime. It is built from a conversation, a record lookup and a tool result, so 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.
Start with Redact PII from model output, or read the measurements.