Detection¶
Two settings decide what gets masked: the detector finds sensitive values,
and the policy decides which of those findings are masked.
Try a config before putting it in front of real traffic. privyx detect uses
the same detector and policy as the proxy:
privyx detect -c my.yaml "ann from acme, ann@acme.example"
privyx detect -c my.yaml --transform "..." # also print the masked text
privyx detect -c my.yaml --no-policy "..." # every detection, before the policy
Built-in patterns¶
With no configuration, the regex detector finds:
| Entity | Matches |
|---|---|
EMAIL |
Email addresses |
PHONE |
Phone numbers of three digit groups (555-010-4477), or two with a +<country> code, an area code in parentheses, or a leading 0 ((021) 5550-1234); without separators, in E.164 form (+6281234567890) or starting with 08 |
CREDIT_CARD |
Runs of 13 to 16 digits, optionally split by spaces or dashes, that pass the Luhn checksum like every card number |
IP_ADDRESS |
IPv4 addresses, private ones included, except loopback (127.0.0.1), 0.0.0.0, and the documentation ranges (192.0.2.x, 198.51.100.x, 203.0.113.x) |
SSN |
US social security numbers (123-45-6789) |
API_KEY |
Vendor-prefixed keys (OpenAI and Anthropic sk-, Stripe, GitHub, GitLab, AWS, Google, Slack, Hugging Face, and more) and webhook URLs |
JWT |
JSON Web Tokens |
PRIVATE_KEY |
PEM private key blocks, even when cut off before the end |
AUTH_TOKEN |
The credential in Authorization: Bearer … or Basic … |
URL_CREDENTIAL |
The password in scheme://user:password@host |
SECRET |
A value assigned to a secret-looking name (DB_PASSWORD=…, api_key: …, "token": "…") in .env, YAML, JSON, shell, and code |
Names and anything specific to you need more configuration.
Secrets¶
Most built-in secret patterns match a recognizable shape, such as a vendor
prefix or a PEM header. A password or key without one, such as
DB_PASSWORD=hunter2, is found by its name: SECRET masks the value assigned
to a name such as password, token, secret, api_key, or an upper-case
…_KEY. It leaves alone what cannot be the secret:
- a variable that holds it:
api_key=api_key,SECRET_KEY = os.environ[…]; - a name that describes it:
TOKEN_URL,token_type,KEY_FILE; - a placeholder or punctuation:
sk-...,${API_KEY:-},**Token:**; - a sentence after a colon, as in a docstring:
api_key: When set, ….
Detector types¶
type |
Finds | Needs |
|---|---|---|
regex (default) |
The built-in patterns plus your patterns and terms |
Nothing |
yaml |
Only your patterns and terms |
Nothing |
presidio |
Names, organizations, locations, and more, with NLP | privyx[presidio] and a spaCy model |
llm |
Context-dependent PII, by asking a language model | privyx[providers] and a provider API key |
A plugin can add more types; see Plugins.
Your own patterns¶
patterns maps an entity name to a regular expression. For regex, they are
added to the built-in set, and a pattern with a built-in name replaces that
one:
detector:
type: regex
patterns:
EMPLOYEE_ID: '\bEMP-\d{6}\b'
PASSWORD: 'PASSWORD=(?P<value>\S+)'
- The entity name becomes the
{type}in the token (<PRIVYX_EMPLOYEE_ID_1>). It must start with a letter and contain only letters, digits, and underscores, up to 64 characters. - A pattern that replaces a built-in one is used as written, without the
built-in's check, such as the Luhn checksum for
CREDIT_CARD. - Patterns are case-sensitive. Start one with
(?i)to ignore case. - A group named
valuemasks only that part of the match: the pattern above masks the password and leavesPASSWORD=for the model to read. - In YAML, write patterns in single quotes or a
|block so backslashes stay as they are. Inside double quotes, every backslash must be doubled.
Word lists¶
terms maps an entity name to plain strings. Privyx escapes them, so no regex
knowledge is needed:
detector:
type: regex
terms:
PERSON: [ann, bob]
ORGANIZATION: [acme, initech]
URL: ["https://git.internal.example/team"]
Matching ignores case. Longer terms are tried first, so a compound term wins
over a shorter term inside it. A term only matches as a whole word: ann does
not match inside annual.
terms and patterns can be used together. If both name the same entity, the
patterns regex wins.
Presidio¶
Presidio finds names, locations, organizations, and other entities with a spaCy language model:
pip install 'privyx[presidio]'
python -m spacy download en_core_web_sm
detector:
type: presidio
language: en
model: "" # empty → en_core_web_sm
entities: [] # empty → every Presidio recognizer
score_threshold: 0.35 # drop findings below this confidence
If Presidio or the model is missing, Privyx fails at startup and prints the command that installs it.
LLM detector¶
The llm detector asks a language model to list the PII in the text. It
catches what patterns cannot, such as a name that is also a common word, at the
cost of a model call per request.
detector:
type: llm
llm_provider: openai # or anthropic
llm_model: "" # empty → gpt-4o-mini / claude-haiku-4-5
llm_timeout: 30 # seconds per scan
llm_max_chars: 4000 # longer text is scanned in overlapping chunks
llm_fallback_on_error: false
Set llm_model to a model your account can use; the built-in default for a
provider can be one that provider has since retired.
The text is sent unmasked to that provider, so use one you already trust with
it. The key comes from llm_api_key, or else the SDK's own variable
(OPENAI_API_KEY, ANTHROPIC_API_KEY). The SDK also reads OPENAI_BASE_URL /
ANTHROPIC_BASE_URL from the proxy's environment, which can point the detector
at a local model. Do not let it point at Privyx itself.
If a scan fails, times out, or the reply has no list of findings, the request
fails with 503 and a privyx_scan_failed error rather than going upstream
with a weaker scan. With llm_fallback_on_error: true the request is scanned
with the built-in patterns instead, which lets through whatever only the model
would have caught. Every fallback is counted in the audit trail as
detector_counts.llm_fallbacks.
Several detectors at once¶
Give detector a list to run several detectors. Their findings are pooled:
detector:
- type: regex # structured PII and your terms
terms:
PROJECT: [bluebird]
- type: presidio # names, organizations, locations
- type: llm # what the others miss
Each item is a complete detector config. A regex item includes the built-in
patterns; a yaml item has only its own.
Cache¶
Detections are cached by text, so in a multi-turn conversation the earlier turns are not scanned again. The cache is on by default and holds 10,000 entries:
detector:
type: regex
cache:
enabled: true
max_size: 10000
cache: false or PRIVYX_DETECTOR_CACHE=false turns it off.
Policy¶
The policy filters what the detector found.
defaultmasks every detection.strictmasks only the entity types inallowed, and leaves the rest in the text, built-in secret types included. An emptyallowedmeans every built-in entity.
policy:
type: strict
allowed: [EMAIL, PHONE, SSN, CREDIT_CARD, IP_ADDRESS, EMPLOYEE_ID]
With strict, a custom entity that is missing from allowed is detected and
then left in the text. To spot one, compare privyx detect --transform with
and without --no-policy:
$ privyx detect -c strict.yaml --transform "bluebird mail a@b.io"
14:20 EMAIL 'a@b.io'
bluebird mail <PRIVYX_EMAIL_1>
$ privyx detect -c strict.yaml --transform --no-policy "bluebird mail a@b.io"
0:8 PROJECT 'bluebird'
14:20 EMAIL 'a@b.io'
<PRIVYX_PROJECT_1> mail <PRIVYX_EMAIL_2>