Skip to main content
CID222 Docs

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:

OutputWhat it is
Display tokenA 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 hashSHA-256 of the original, 64 hex characters
Original lengthThe 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.

Last updated on

On this page

Download PDF