New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

privaite

Package Overview
Dependencies
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

privaite

Drop-in self-hosted LLM proxy that reversibly redacts PII before LLM calls, including tool-call arguments and multimodal content

pipPyPI
Version
0.3.2
Weekly downloads
979
Maintainers
1
Created

PrivAiTe

Self-hosted PII redaction proxy for LLM APIs.

CI Python 3.11+ License PyPI

A drop-in LLM proxy that reversibly replaces PII before it reaches the provider, including inside tool-call arguments and multimodal content, with zero telemetry.

You type: "Je m'appelle Marie Dupont, email marie@acme.com"
LLM sees: "Je m'appelle <PERSON_1>, email <EMAIL_ADDRESS_1>"
LLM says: "Bonjour <PERSON_1>, votre email <EMAIL_ADDRESS_1> est noté."
You  see: "Bonjour Marie Dupont, votre email marie@acme.com est noté."

PrivAiTe sits between your app and the model provider. It finds names, emails, phones, cards, IBANs, secrets and more, swaps them for stand-ins before anything leaves your machine, and puts the real values back in the reply. Most tools scan only the plain message text; agent traffic hides PII inside tool-call JSON, and that is the gap PrivAiTe closes. Detection runs locally (two engines, Presidio + OpenAI's open privacy-filter model), and the engine runs three ways: standalone proxy, Open WebUI filter, or LiteLLM guardrail.

This is local pseudonymization, not anonymization, and detection is best-effort rather than a guarantee. You remain the data controller. The Threat model spells out exactly what it protects against and what it does not.

Quick start

Docker (fastest): the detection model is baked in, so it runs offline from the first request.

docker run -d -p 8400:8400 \
  -e PRIVAITE_API_KEYS=change-me \
  -e OPENAI_API_KEY=sk-... \
  ghcr.io/crp4222/privaite

The same image is on Docker Hub too: swap the last line for crp4222/privaite if you prefer pulling from there.

Two keys, two roles: PRIVAITE_API_KEYS is the key your client sends to PrivAiTe (pick any value); OPENAI_API_KEY is your real provider key, which stays in the container and never reaches your client. This exposes gpt-4o-mini and gpt-4o; for any other provider (Ollama, Azure, anything LiteLLM supports), mount a config: configuration.

pip:

pip install privaite
# One spaCy model per scanned language; the default preset scans EN + FR.
python -m spacy download en_core_web_lg && python -m spacy download fr_core_news_md

cat > privaite.yaml <<'EOF'
providers:
  - model_name: gpt-4o-mini
    litellm_params:
      model: openai/gpt-4o-mini
      api_key: ${OPENAI_API_KEY}
pii:
  enabled: true
  preset: onnx    # or "light": faster, no model download, classic PII only
EOF

PRIVAITE_API_KEYS=change-me
python -m privaite --config privaite.yaml

Connect: point any OpenAI-compatible client at http://localhost:8400/v1 with the key change-me. For Open WebUI: Admin → Settings → Connections → OpenAI API, URL http://localhost:8400/v1 (or http://host.docker.internal:8400/v1 if Open WebUI runs in Docker), key = your PRIVAITE_API_KEYS value. Client snippets (curl, Python, Node) are in examples/. Prefer no separate proxy? Use the in-process Open WebUI filter.

Benchmark

Measured on 120 real documents from the open AI4Privacy pii-masking-200k dataset (458 PII items, labeled by 10 independent auditor agents and cross-checked against the dataset's own mask) across DE, EN, FR, IT, plus 14 clean documents for false positives.

SolutionRecall (span)Recall (strict)False positivesTool-call protection
onnx (default)84.5%80.6%2 / 14100%
light (full Presidio)62.4%57.9%3 / 14100%
LiteLLM Presidio guardrail70.3%65.3%3 / 140.0%
LLM Guard (Anonymize)76.9%74.9%5 / 140.0%

Read the 100% precisely, it is structural, not absolute: of the PII PrivAiTe detects in plain text, 100% is also removed from tool-call JSON. End to end, its tool-call leak equals its detection misses (15.5% on this corpus with the onnx preset), the same misses flat text has.

Two honesty notes, both favoring caution. LLM Guard's detection model is fine-tuned on the exact dataset behind this corpus, so its recall here is optimistic; PrivAiTe's default model is not (OpenAI's model card states it did not train on it). An out-of-distribution cross-check on two independent corpora confirms the default generalizes: ~84% held on Gretel finance text while the AI4Privacy-tuned model drops to ~62% (OOD_COMPARISON.md).

Per-language and per-entity tables, competitor configs, methodology, reproduction: privaite-bench. Feature comparison: docs/comparison.md.

Presets

PresetWhat runsRecall*False positivesLatencySecrets
onnx (default)Presidio + Privacy Filter84.5%2 / 14~0.5syes
lightPresidio only62.4%3 / 14~60msno
maxonnx + GLiNERhigher OODmore~0.7syes

*Span recall on the AI4Privacy benchmark above. max adds GLiNER (trained on data independent of AI4Privacy): on out-of-distribution corpora it raises recall by several points at the cost of more false positives and a torch dependency (pip install 'privaite[gliner]'); with it selected but not installed, the proxy fails at startup with an install hint rather than silently degrading.

onnx = maximum coverage: secrets, passwords, API keys, unusual names, addresses. light = fastest, near-zero false positives, classic PII only, no addresses/URLs/secrets. How the engines work and what stays off by default: docs/detection.md.

Footgun: do not pin detectors.presidio.entities to a short allowlist on the light path. It restricts detection to only those types and roughly halves recall (to ~35%). Leave entities unset; the proxy logs a warning at startup if it detects a low-recall configuration.

What it scans

Before anything is forwarded: messages[].content (plain string or multimodal text parts), tool_calls[].function.arguments and the legacy function_call.arguments (parsed as JSON, scrubbed value by value including bare numeric leaves, keys and function names intact), /v1/completions prompt, /v1/embeddings input. On the way back, values are restored in content, tool calls and reasoning traces, streaming included.

NOT scanned (know your surface): messages[].name, top-level user/metadata, tools definitions, JSON object keys. Keep PII out of those fields. Endpoints, strict mode and passthrough caveats: docs/api.md.

Threat model

PrivAiTe performs local pseudonymization, not guaranteed anonymization. Detection runs on your machine; the real ↔ placeholder mapping lives in memory only for the duration of a request and is dropped afterwards.

What it protects against: the LLM provider storing, training on, or logging your raw PII. The provider receives placeholders (<PERSON_1>, …) for everything the detector catches, across message content, tool-call arguments, and multimodal text.

What it does NOT protect against:

  • PII the detector misses. Detection is statistical and never 100% (see the benchmark). A name it doesn't recognize reaches the provider. Treat the output as best-effort, not a guarantee.
  • Re-identification from context. Even with names replaced, the surrounding text can stay identifying ("the CEO of <ORG_1> who resigned in March").
  • A compromised local machine. The mapping and raw text live in local memory; this is not a defense against a local attacker.
  • The provider correlating requests within a session.

For GDPR/HIPAA: treat this as pseudonymization + transfer minimization, not anonymization. If you need irreversible removal, use method: "redact". Audit it on your own data: docs/verify.md.

Alternatives

Keeping PII out of LLM calls is a crowded space, and PrivAiTe is not always the right pick. Based on each project's public docs as of June 2026:

  • LiteLLM has a built-in Presidio guardrail, the natural choice if you already run the LiteLLM proxy and want PII handling inline (there are a few open bugs around scrubbing requests and responses).
  • Managed/cloud options exist too, such as Microsoft PII Shield and LangChain's gateway redaction.

Where PrivAiTe differs: it anonymizes PII inside tool-call arguments and multimodal content, not just message text (LangChain's gateway docs, for instance, note that tool-call arguments are not scanned), it restores the original values in the response, and it ships a reproducible benchmark. If your traffic is agentic or multimodal, that gap is the reason this exists.

Integrations

  • Open WebUI filter (setup, hub listing): an Open WebUI Filter Function running the engine in-process, no separate proxy. Admin Panel → Functions → paste integrations/openwebui/privaite_filter.py, enable, pick preset and languages in its valves. Covers message text, tool calls and multimodal.
  • LiteLLM guardrail (setup): a custom guardrail for teams already on the LiteLLM proxy. Mount integrations/litellm/privaite_guardrail.py next to your config.yaml to anonymize requests and restore responses inline, including tool-call arguments, which LiteLLM's built-in Presidio guardrail does not scan.

Docs

Development

git clone https://github.com/crp4222/PrivAiTe && cd PrivAiTe
pip install -e ".[dev]"
python -m spacy download en_core_web_lg && python -m spacy download fr_core_news_md

cp .env.example .env                                    # keys
cp config/privaite.example.yaml config/privaite.yaml    # providers
python -m privaite --reload                             # dev mode (auto-reload)

python -m pytest tests/ -v

License

BSD 3-Clause. See LICENSE.

Keywords

privacy

FAQs

Related posts