Configuration reference
Everything below goes in your privaite.yaml (python -m privaite --config privaite.yaml).
The README quick start has the minimal
working file; this page covers every knob.
Server
server:
host: "0.0.0.0" # default
port: 8400 # default
Both can also be overridden at launch: python -m privaite --config privaite.yaml --host 127.0.0.1 --port 8452.
LLM providers
Any LiteLLM-supported provider works:
providers:
- model_name: "gpt-4o"
litellm_params:
model: "openai/gpt-4o"
api_key: "${OPENAI_API_KEY}"
- model_name: "local-llama"
litellm_params:
model: "ollama/llama3.1"
api_base: "http://localhost:11434"
A ${VAR} whose environment variable is unset fails startup on purpose, so a
missing key is caught at boot rather than at the first request.
Agent CLI gateway
Opt-in native routes for Claude Code (Anthropic Messages, validated live) and Codex (OpenAI Responses, beta). Gateway routes relay the client’s own provider credentials as-is; PrivAiTe injects and validates no key there. Enable the detection cache alongside it. Full setup, scanned surface and limits: gateway.md.
gateway:
enabled: false # default: off, the routes do not exist
anthropic:
base_url: "https://api.anthropic.com/v1"
openai_responses: # beta (Codex)
base_url: "https://api.openai.com/v1"
Docker with a custom config
With just OPENAI_API_KEY set, the image exposes gpt-4o-mini and gpt-4o. For
any other provider (Ollama, Azure, a self-hosted endpoint, or your own LiteLLM
proxy), mount a config:
docker run -d -p 8400:8400 \
-e PRIVAITE_API_KEYS=change-me \
-v $PWD/privaite.yaml:/app/config/privaite.yaml:ro \
ghcr.io/crp4222/privaite
The image is published to both ghcr.io/crp4222/privaite and Docker Hub
(crp4222/privaite); either works in the commands above.
A minimal privaite.yaml:
providers:
- model_name: my-model
litellm_params:
model: openai/gpt-4o-mini # any litellm model string, e.g. ollama/llama3
api_base: https://api.openai.com/v1
api_key: ${OPENAI_API_KEY}
pii:
enabled: true
preset: onnx
Auth is on by default, so PRIVAITE_API_KEYS is required. From a clone you can
also docker compose up -d; put the key in a .env file to keep it off the
command line.
Anonymization method
pii:
anonymization:
method: "placeholder" # <PERSON_1>, <EMAIL_ADDRESS_1> (recommended)
# method: "fake_replacement" # Realistic fakes via Faker (Jean → Michel)
# method: "redact" # [PERSON], [EMAIL_ADDRESS] (irreversible)
# method: "mask" # ******** (irreversible)
redact and mask are lossy on purpose: the original never enters the reversible
map, so nothing is restored in responses for those types (and two values that mask
to the same string can never cross-restore).
Blocking specific PII types (hard policy gate)
By default every detected PII item is pseudonymized and the request goes through.
If some PII types must never leave your network at all, even as a placeholder,
list them under block_entities. A request containing any listed type is rejected
with 400 and nothing is forwarded to the provider. The error names the type(s),
never the value.
pii:
block_entities: [] # default: block nothing, mask everything
# block_entities: ["US_SSN", "CREDIT_CARD"] # opt-in: reject these outright
Types not listed are still masked as usual, so blocking is purely additive on top of the default behavior.
The proxy refuses to start if a listed type cannot be emitted by any enabled
detector (for example US_PASSPORT under the default onnx preset): a block
rule that can never fire would be silently unenforceable. Fix it by removing the
type or enabling a detector that produces it (a label_mapping value, a Presidio
entity, or a custom_patterns entity type).
Detection cache (agent sessions)
Agent CLIs (Claude Code, Codex) resend the entire growing conversation on every
turn, so the proxy re-scans mostly identical bytes: O(n) detector work per turn,
O(n^2) per session. The detection cache remembers the merged detection result
for each exact text leaf, so a resent leaf skips the detectors entirely. On a
real captured 11-turn Codex session with the onnx preset, per-turn scrub time
drops to well under a second from turn 2 on, with byte-identical output.
pii:
detection_cache:
enabled: false # default: off (see the README threat model)
max_entries: 4096 # LRU bound
ttl_seconds: 1800 # entries expire after 30 minutes (swept on the next write)
What it stores and what it never stores:
- Stored: salted BLAKE2b hashes of scanned text leaves (the salt is random per process and never leaves it), plus span metadata (start, end, entity type, score, detector source) for each hash.
- Never stored: the text itself, the matched PII values, the anonymized output, or any placeholder mapping. Placeholder numbering stays per-request.
The block_entities gate and the anonymizer run on every request, cached or
not, so a cached detection still blocks and still fails closed. A change to any
detector setting changes the cache key fingerprint, so stale results are never
served after a config change.
Why it is off by default: with the cache enabled, PII-derived metadata (hashes,
positions, types) survives in process memory up to ttl_seconds after a
request ends, instead of nothing outliving the request. An expired entry is
never served again; it is removed from memory on the first cache write after
its expiry, and the whole cache is cleared at engine shutdown, so only a
process that goes completely idle holds its last (expired, unusable) entries
longer, until that next write or shutdown. The full delta,
including the multi-user dedup timing side channel, is spelled out in the
README threat model. Enable it if you use the
agent CLI gateway or any client that resends conversation
history; leave it off if the stricter memory posture matters more than latency.
Custom regex patterns
Add your own PII patterns without touching code:
pii:
custom_patterns:
- pattern: "KD-\\d{6}"
entity_type: "CUSTOMER_ID"
- pattern: "REF-[A-Z]{3}-\\d+"
entity_type: "REFERENCE"
Languages
7 languages supported with spaCy NER and contextual patterns: FR, EN, DE, ES, IT (benchmarked), plus PT and NL (best-effort, not yet in the benchmark).
pii:
detectors:
presidio:
languages: ["fr", "en"] # the default; add "de", "es", etc.
Each language needs its spaCy model: python -m spacy download de_core_news_md.
The default list is ["fr", "en"], so a fresh install fetches fr_core_news_md
on first boot if it is missing; set languages: ["en"] for an English-only,
no-surprise-download setup.
Detector model revisions
The built-in Hugging Face detector models are pinned to immutable commits, so a
fresh install does not silently pick up different weights from a moving main
branch:
openai/privacy-filter:7ffa9a043d54d1be65afb281eddf0ffbe629385b(the default ONNX detector and the optional torch detector)dslim/bert-base-NER:d1a3e8f13f8c3566299d95fcfc9a8d2382a9affcurchade/gliner_multi_pii-v1:1fcf13e85f4eef5394e1fcd406cf2ca9ea82351d
When changing a detector’s model_name, also set revision to a commit SHA
from that model’s repository. A default SHA from a different repository fails to
load instead of silently using different weights. revision: null intentionally
follows the mutable default branch and is not reproducible.