veil-engine
Finds sensitive values in text, replaces them with placeholders such as [EMAIL_1], and puts the real values back into the reply. It is the redaction engine of Veil, which keeps personal data and secrets out of what you send to AI models.
you write Email ana@example.com about card 4111 1111 1111 1111
the model sees Email [EMAIL_1] about card [CARD_1]
the model replies I emailed [EMAIL_1].
you see I emailed ana@example.com.
The engine does no file or network access and no logging. It only transforms the text and JSON you give it, so you can read all of it and check what it does. See the threat model for what it protects against and what it doesn't.
Redact and restore
use ;
let engine = builder.build?;
// Placeholders are numbered per scope: one conversation, profile or user.
let scope = new;
let r = engine.redact_text?;
assert_eq!;
assert_eq!;
let back = engine.restore_text;
assert_eq!;
# Ok::
The same value always gets the same placeholder in a scope, so a conversation stays consistent and a provider's prompt cache keeps working.
Streamed replies
A placeholder can be split across chunks of a streamed reply. [StreamRestorer] holds back only the text that could still become a placeholder:
use ;
let engine = builder.build?;
let scope = new;
engine.redact_text?;
let mut restorer = default;
let mut out = Stringnew;
for chunk in
out += &restorer.finish;
assert_eq!;
# Ok::
JSON request bodies
API requests mix content with structure such as model names and roles. A [KeyPolicy] says which keys hold content:
use ;
use ;
;
let engine = builder.build?;
let mut body = json!;
engine.redact_json?;
assert_eq!;
assert_eq!;
# Ok::
KeyAction::JsonString handles strings that hold JSON themselves, such as tool-call arguments.
Rules
What to detect is a [RulesFile], usually written as YAML:
use ;
let rules: RulesFile = from_str?;
let engine = builder.rules.build?;
let r = engine.redact_text?;
assert_eq!;
# Ok::
Built-in detectors: email, phone, payment cards (Luhn-checked, known issuers), IBAN (checksum-checked), API keys and tokens, IP addresses, URLs (off by default), and national ID numbers for US, GB, DE, FR, ES, IT, NL, PL and RO. Names of people, organisations and places need a name-detection model, which you plug in through [EntityRecognizer]. The engine doesn't include one, since a model has to be loaded from disk.
Where placeholders are kept
The engine asks a [Vault] which value each placeholder stands for. It needs three methods:
use ;
;
[MemoryVault] keeps everything in memory and is the default. The veil-vault crate stores values encrypted on disk, with the key in the OS keychain.
Guarantees
Each of these has a test.
- Redacting and then restoring gives back the original text.
- The same input in the same scope gives the same output.
- Redacting already redacted text changes nothing.
- Restoring a stream gives the same text as restoring it whole, wherever the chunks split.
- A placeholder the user typed is never given to a real value.
- Scopes never share placeholders.
- If name detection is on but no model was given, redaction fails rather than send names.
- Reports, matches and errors never hold the values found.
- The engine does no file or network access and no logging.
Showing which version runs
[VERSION] is the version of the engine compiled into a program, so an application can show which release it runs, for example on an About screen. veil_vault::VERSION does the same for the vault.
assert_eq!;
Cargo features
| Feature | Default | What it adds |
|---|---|---|
phone |
on | The phone number detector. Its number metadata is the largest part of the engine. Without it, rules that turn phone detection on fail to compile. |
schema |
off | schemars::JsonSchema for RulesFile and its parts, for editor autocompletion of rules files. |
License
Apache-2.0.