Consistent Pseudonymization (Replacement Scope)
When Philter replaces a piece of sensitive information with a generated value (for example, replacing a name with a realistic fake name), you often want the same original value to be replaced with the same generated value everywhere it appears, not just within a single document but across every document you process. This is consistent pseudonymization, and it keeps a redacted dataset analytically useful: you can still tell that the same individual is referenced across many documents without ever knowing who they are.
Consistent pseudonymization is controlled by the replacement scope of a filter strategy.
Setting the replacement scope
Each filter strategy has an optional replacementScope field. It accepts two values:
| Value | Meaning |
|---|---|
DOCUMENT |
(Default.) Each document is pseudonymized independently. The same value may receive a different replacement in a different document. |
CONTEXT |
The same value receives the same replacement across every document processed in the same context. |
Set it on the strategy in your policy. For example, to pseudonymize SSNs consistently across a context:
{
"identifiers": {
"ssn": {
"ssnFilterStrategies": [
{
"strategy": "RANDOM_REPLACE",
"replacementScope": "CONTEXT"
}
]
}
}
}
CONTEXT scope only takes effect when the redaction request supplies a context; consistency is shared among documents redacted under that same context name.
How the context mapping table works
Under CONTEXT scope, the first time a value is replaced Philter records the mapping and reuses it for every later occurrence of that value in the same context.
- Persistence. Mappings are stored in Philter's database and survive across requests and restarts, so consistency holds for documents processed days apart.
- Scoping. Each mapping is keyed by user and context name, so contexts are isolated from one another and one user's mappings are never visible to another.
- Privacy. The original value is not stored in clear text. Only a keyed hash (HMAC-SHA256 under a key derived from
PHILTER_ENCRYPTION_KEYand the context owner's username) is kept, alongside the generated replacement, the filter type, a read counter, and a timestamp. The key matters: most detected values are low-entropy, so a bare digest of one could be reversed by trying candidates. - Capacity and eviction. A context holds at most
MAX_CONTEXT_SIZEmappings (default10000, configurable with theMAX_CONTEXT_SIZEenvironment variable; see Settings). When a context is full, the least-read mapping is evicted to make room. A value whose mapping has been evicted is treated as new the next time it is seen and receives a fresh replacement.
Interaction with replacement strategies
Replacement scope matters only for strategies that generate a value that would otherwise vary from run to run. Deterministic strategies are already consistent across documents regardless of scope.
| Strategy | Effect of CONTEXT scope |
|---|---|
RANDOM_REPLACE (pseudonymize) |
Primary use. The randomly generated surrogate is stored and reused, so the same value maps to the same surrogate across documents. |
MAP_REPLACE |
A value the lookup table does not map gets a generated replacement, which is stored and reused, so it maps to the same replacement across documents. |
FPE_ENCRYPT_REPLACE |
No table needed. Format-preserving encryption is already deterministic for a given key, so the same input always encrypts to the same value. See FPE. |
HASH_SHA256_REPLACE |
Deterministic on its own (unless salted), so it is consistent regardless of scope. |
STATIC_REPLACE, MASK, REDACT |
Produce a fixed output for a given input, so scope has no effect. |
Managing the mappings (API)
The mapping table for a context can be inspected and managed through the context-entries API. All endpoints are scoped to the authenticated user.
| Method & path | Description |
|---|---|
GET /api/contexts/{name}/entries |
List the mappings in a context (paged with offset/limit). Each entry returns its id, replacement, filter type, read count, and timestamp. The original (hashed) token is never returned. |
DELETE /api/contexts/{name}/entries |
Remove all mappings from the context. |
DELETE /api/contexts/{name}/entries/{entryId} |
Remove a single mapping. |
GET /api/contexts/{name}/entries/export |
Export the context's mappings. |
POST /api/contexts/{name}/entries/import |
Import mappings into the context (subject to the same capacity/eviction limit). |
Deleting a mapping means the next occurrence of that value is treated as new and receives a fresh replacement.
Not the same as disambiguation scope
Replacement scope is distinct from disambiguation scope, despite the similar document and context wording:
- Replacement scope (this page) is set per filter strategy in the policy and controls whether generated replacements are reused across documents.
- Disambiguation scope is set on the context, with its
disambiguationScopesetting (documentorcontext), and controls whether Philter shares entity-type disambiguation knowledge across documents to improve detection accuracy.