Skip to the content.

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:

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:

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.