Two failure modes, one root cause
Consider a support-assistant prompt that contains a customer's name, email, phone number, and a partial card number. There are two common implementations:
Implementation A — regex redaction. Replace matches with [REDACTED]. The prompt is now safe. It is also useless: the model can no longer distinguish between two customers in the same conversation, and the response comes back referring to “the customer” in a way that cannot be rehydrated into anything actionable.
Implementation B — no redaction, rely on the vendor. The prompt goes out intact. The vendor's data-processing terms are cited as the control. This is a legal position, not a technical control, and it fails the moment the same prompt is logged by an observability tool, cached by a gateway, or replayed in a fine-tuning pipeline.
Both failures share a root cause: the implementation treats PII as something to delete rather than something to substitute.
Tokenization, not deletion
The workable pattern is deterministic substitution. Each detected PII value is replaced with a stable surrogate token, and the mapping is held in an encrypted vault that never leaves your boundary.
Original : "Email jane.doe@example.com about invoice INV-4471"
Tokenized: "Email <EMAIL_7f3a> about invoice INV-4471"
Model out: "Send <EMAIL_7f3a> a reminder for INV-4471"
Rehydrated: "Send jane.doe@example.com a reminder for INV-4471"
Three properties make this work:
- Stability. The same value must produce the same token within a session. Otherwise the model cannot reason about co-reference (“the same customer”).
- Reversibility. The application must be able to restore the real value on the response path, inside your trust boundary.
- Non-invertibility outside the vault. The token itself must not encode the value. A hash of an email address is dictionary-reversible; a random surrogate keyed to a vault entry is not.
Why regex is not enough
Regex handles structured identifiers well: emails, phone numbers in known formats, IBANs, national ID patterns. It fails on the categories that matter most in practice:
- Person names. No reliable pattern. A name list produces false positives on product names and false negatives on names outside the list.
- Addresses. Free-form, multi-line, frequently abbreviated.
- Contextual identifiers. “The account ending 4471” is identifying in combination with a name, and matches no pattern at all.
- Unstructured narrative. “My daughter started at the Halifax campus in September” is a quasi-identifier that regex will never catch.
This is why TokenShield™ runs a layered detection pipeline: deterministic regex for structured identifiers, a named-entity model for names and locations, and an allowlist layer so that your own product names and internal identifiers are not mangled. The layers are ordered, and the first match wins — which keeps behavior predictable and auditable.
What to log, and what never to log
The most common leak is not the model provider. It is your own observability stack.
| Artifact | Safe to store | Never store |
|---|---|---|
| Prompt text | Tokenized form only | Raw prompt containing PII |
| PII references | Keyed digest (HMAC-SHA256) with a domain-separated salt | Plain SHA-256 of the value — dictionary-reversible |
| Vault mapping | Encrypted at rest (AES-GCM), access-logged, TTL-bounded | Plaintext mapping in application logs or cache |
| Model response | Tokenized form, rehydrated only in memory on the response path | Rehydrated response written to a log sink |
The keyed-digest point is worth emphasizing. An unsalted SHA-256 of an email address is not anonymization — an attacker with a list of candidate addresses can confirm membership in seconds. A keyed HMAC with a secret salt, and with domain separation so that a name reference and a domain reference cannot collide, is a genuine pseudonymization control.
Prompt injection is the other half
Privacy controls and prompt-injection defenses are usually built by different teams and fail together. A prompt firewall has to do both: detect and neutralize instructions embedded in retrieved content or user input, and prevent those same channels from exfiltrating the vault mapping.
Practical rules that hold up:
- Never place the vault mapping, or any key material, in the model's context window.
- Treat retrieved documents as untrusted input, not as instructions.
- Constrain tool calls with an explicit allowlist; a model that can call arbitrary URLs can exfiltrate anything it can read.
- Rate-limit per principal, not per API key, so a single compromised session cannot enumerate the vault.
A minimum viable control set
- Tokenize before the prompt leaves your boundary — including to your own observability stack.
- Hold the mapping in an encrypted vault with a TTL and an access log.
- Rehydrate only on the response path, in memory.
- Log keyed digests, never raw values, and never unsalted hashes.
- Run the same pipeline on retrieved context, not just on user input.
- Test it: send a prompt containing a known email and assert that the string does not appear in any log sink.
That last item is the one most programs skip. A privacy control that has never been tested against a real prompt is a design document, not a control.
Bottom line
Redaction protects the model and breaks the application. Doing nothing protects the application and leaks the data. Deterministic tokenization with an encrypted, TTL-bounded vault is the only pattern that satisfies both — and it is testable, which is what makes it a control rather than a policy.