Masking and encryption at rest solve different problems and compose. Encryption protects stored content from whoever reaches the disk. Masking decides what is stored and what is sent to a third-party model in the first place — the data never leaves your network unmasked, encrypted or not.
Turning it on
Two variables, both needed. The analyzer finds entities and the anonymizer replaces them; one without the other is a request that fails halfway..env.local
The kill switch
0 does this; any other value leaves a configured deployment masking.
What it recognises
Presidio’s standard recognisers (names, email addresses, phone numbers, credit cards, IBANs, and more) plus Polish entities that Ragen adds, with checksum validation — the built-in patterns match the shape of these numbers but not their validity, which produces false positives on any similar-looking digit string:
The recognisers live in
infra/presidio/analyzer/recognizers/, and the analyzer image is built from infra/presidio/analyzer/ — so adding one is a change to your own deployment, not a fork of Ragen.
Choosing what happens to the original
Masking is not one behaviour. Each organisation picks an ingestion mode in Organization → PII policy:Standard
The original is irreversibly replaced before storage. Nothing sensitive survives the masking step — not in Postgres, not in the vector store.Changing to this later means re-processing existing documents.
Extended
The visible content is masked, and an AES-256-GCM encrypted original is kept beside it so the assistant can answer accurately from it.The original passes through your search infrastructure. Enable only if your security policy allows it.
The policy control is unavailable until a masker is configured, and this is deliberate rather than a UI convenience: a stored policy that nothing enforces reads as active in the admin panel while changing nothing about what is written to disk. Ragen refuses the write, not just the button.
What it costs
Two containers, roughly 1 GB of RAM together. The analyzer is built locally rather than pulled, because it carries the spaCy language models and the Polish recognisers. This is the reason masking is opt-in rather than on by default, and the reason the services carry a Compose profile: a deployment that does not mask should not pay for them.Failure behaviour
Chat fails closed. If the analyzer cannot be reached while a message is being masked, the request fails rather than continuing with unmasked text. Unmasked personal data never reaches a model because a container was down. Two security events are recorded, visible in the incidents dashboard:Limits worth knowing
- Detection is statistical. Checksum-validated entities are reliable; name recognition is not, in any language. Treat masking as a strong control, not a guarantee, and pair it with the access controls that decide who can read a document at all.
- Existing documents are not re-processed. Turning masking on affects what is ingested from that point. Documents already in the knowledge base keep the form they were stored in.
- Thread titles are not masked, for the same reason they are not encrypted: title search would stop working. Avoid putting personal data in a thread title.