Pseudonymization and masking
The two separate mechanisms that replace a sensitive value — reversible masking on the request path, and irreversible pseudonymization in the detection record.
- Version: 0.4
- Role: admin_user, normal_user, viewer
CID222 replaces a sensitive value in two different places, for two different reasons, with two different guarantees. Confusing them leads to the wrong expectation about what can be recovered.
The problem
A prompt containing a customer's email address should not reach a commercial model — but the
answer that comes back is useless to the bank clerk who asked it if it is about [EMAIL_1]
instead of a person. Meanwhile the detection record proving the gateway caught that email must
survive for an audit, and must not itself become a copy of the data it was protecting. One
substitution mechanism cannot satisfy both: the first has to be reversible, and the second must
not be.
How CID222 does it
Masking — on the request path, reversible by policy
Before the request leaves the gateway, each entity marked for masking is replaced in place with a
numbered placeholder: [EMAIL_1], [CREDIT_CARD_2]. The provider sees only the placeholder.
Numbering is stable for the whole conversation. The placeholder-to-value map from earlier turns is
carried forward, so one customer stays [EMAIL_1] from turn one to turn ten, and a later turn
cannot mint a colliding number. That map is the encrypted pii_map described in
data handling and retention.
Two behaviours are worth knowing:
- An entity marked for rejection short-circuits masking entirely. The text is not rewritten; the request is refused.
- An entity marked for masking that carries no offsets — a whole-text verdict rather than a span — replaces the entire message with its placeholder, not just part of it.
Unmasking — off by default, per tenant
When the reply comes back, an opted-in tenant gets the original values restored, so the reader sees a usable answer. This runs last, on a copy: the session keeps the masked text.
Unmasking is off for everyone by default. It is enabled globally, per tenant, or per tenant group,
and precedence runs explicit tenant setting, then any of the tenant's groups, then the global
default — so an explicit false on one tenant carves that user out of an enabled group. The
setting is read from the database and reloaded roughly every 30 seconds.
Warning
Enabling unmasking means real personal data is returned to whoever holds that tenant's session.
It is a decision about who the reader is, not a display preference. It also depends on
PII_MAP_ENCRYPTION_KEY being set; without the key, responses stay masked regardless of policy.
Pseudonymization — in the detection record, irreversible
Separately, when a detection is written to the database, its value goes through the pseudonymization service, which produces three things and keeps none of the original:
| Output | What it is |
|---|---|
| Display token | A readable stand-in built as <TYPE_index> from the detection's own entity type — an email detection becomes <EMAIL_0>, the next card <CREDIT_CARD_1> |
| Value hash | SHA-256 of the original, 64 hex characters |
| Original length | The character count |
The hash supports deduplication and audit — two records can be compared without either holding the data. It cannot be reversed.
The default is on. A filter row with the flag unset, and an entity type with no filter row at all, both pseudonymize. Storing plaintext requires an administrator to explicitly turn the flag off for that filter.
The two are not connected
They share a <TYPE_n>-shaped token and nothing else. Masking is a request-path transformation
with a key that can undo it; pseudonymization is a storage-path transformation with no undo. A
value can be masked and not pseudonymized, or the reverse, depending on how each filter is
configured.
Limits and known gaps
- Masking depends on detection. An entity the pipeline did not find is not masked, and reaches the provider intact. Coverage is the entity catalogue in PII detection, not "all personal data".
- Overlapping detections are reduced before masking. Where two detectors claim overlapping spans, one span survives. The surviving placeholder names one entity type, so a span that was both a name and an address is recorded as one of them.
- A whole-text mask destroys the message. A masking verdict without offsets replaces the entire message with a placeholder. This is correct for a positive verdict on an unlocatable span and surprising to a user who expected a redaction.
- Pseudonymization is per filter, and can be switched off. A deployment that has disabled it
for a filter is storing that entity's plaintext in
ai_content_detections. Nothing warns after the fact; the flag is the only record of the choice. - The hash is unsalted SHA-256. For a low-entropy value — a national identity number, a phone number — a determined holder of the database can confirm a guess by hashing it. The hash proves two records refer to the same value; it does not hide a value drawn from a small space.
- A raw detection value is still readable by an administrator. Where pseudonymization is off,
the detection API returns the value to
admin_user. An auditor account receives the masked form or the entity-type placeholder instead.
Related
- PII detection — what gets found in the first place.
- Data handling and retention — where each of these records lives and how long it lasts.
- Regulatory mapping — the obligations these mechanisms are offered against.
Last updated on