From dbc36ac07510957b932db3c709f241bd4ed159c8 Mon Sep 17 00:00:00 2001 From: crp4222 <121295348+crp4222@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:21:41 +0100 Subject: [PATCH 1/3] docker: add drop-in OpenAI config selection --- Dockerfile | 8 +-- config/privaite.openai.yaml | 103 ++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 config/privaite.openai.yaml diff --git a/Dockerfile b/Dockerfile index 5a4a18c..08c997a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,8 @@ EXPOSE 8400 HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ CMD curl -f http://localhost:8400/health || exit 1 -# config/privaite.yaml is the operator's own file (gitignored); a fresh clone -# only has the example. Fall back to it so the image runs out of the box. -CMD ["sh", "-c", "python -m privaite --config \"$([ -f /app/config/privaite.yaml ] && echo /app/config/privaite.yaml || echo /app/config/privaite.example.yaml)\""] +# Config selection, in order: a mounted config/privaite.yaml wins; otherwise, if +# OPENAI_API_KEY is set, the drop-in OpenAI config is used (docker run -e +# OPENAI_API_KEY=... just works); otherwise the local Ollama example config, so +# the image still boots out of the box. +CMD ["sh", "-c", "if [ -f /app/config/privaite.yaml ]; then CFG=/app/config/privaite.yaml; elif [ -n \"$OPENAI_API_KEY\" ]; then CFG=/app/config/privaite.openai.yaml; else CFG=/app/config/privaite.example.yaml; fi; exec python -m privaite --config \"$CFG\""] diff --git a/config/privaite.openai.yaml b/config/privaite.openai.yaml new file mode 100644 index 0000000..adcd19d --- /dev/null +++ b/config/privaite.openai.yaml @@ -0,0 +1,103 @@ +server: + host: "0.0.0.0" + port: 8400 + workers: 1 + log_level: "info" + +auth: + enabled: true + +providers: + # Drop-in OpenAI: this config is selected automatically when OPENAI_API_KEY is + # set in the environment and no config/privaite.yaml is mounted. Add more models + # by copying a block. Uses the standard OpenAI provider via litellm. + - model_name: "gpt-4o-mini" + litellm_params: + model: "openai/gpt-4o-mini" + api_key: "${OPENAI_API_KEY}" + + - model_name: "gpt-4o" + litellm_params: + model: "openai/gpt-4o" + api_key: "${OPENAI_API_KEY}" + +pii: + enabled: true + # Full ONNX suite (default): ~84.5% recall on the benchmark (~749ms). Detects + # everything including secrets and passwords; downloads the ONNX model on first + # run. Use "light" for the fast Presidio-only path (~62% recall, near-zero + # latency). Do NOT pin detectors.presidio.entities below on the light path: it + # restricts detection to only those types and drops recall to ~35%. + preset: "onnx" + + detectors: + presidio: + enabled: true + languages: ["fr", "en"] + score_threshold: 0.4 + # entities: leave unset. The onnx preset automatically scopes Presidio to + # the types it is precise at (the ONNX model covers the rest). Only pin an + # allowlist for advanced tuning, and never on preset: "light". + + # ONNX privacy-filter detector. This is what preset: "onnx" turns on (the + # default). The preset enables it automatically; uncomment to tune it. + # onnx: + # enabled: true + # model_name: "openai/privacy-filter" + # onnx_variant: "q4f16" # quantized variant downloaded from Hugging Face + # device: "auto" # auto, cpu, cuda, coreml/mps + # score_threshold: 0.5 + + bert_ner: + enabled: false + model_name: "dslim/bert-base-NER" + device: "auto" + score_threshold: 0.5 + + mlmodel: + enabled: false + model_name: "openai/privacy-filter" + device: "auto" + torch_dtype: "float16" + score_threshold: 0.5 + + merge_strategy: "union" + overlap_resolution: "highest_score" + on_error: "block" + + # Hard policy gate: PII types listed here cause the WHOLE request to be + # rejected (HTTP 400) instead of being pseudonymized. Empty by default, so + # every detected type is masked and forwarded as usual. Opt-in per type. + block_entities: [] + # block_entities: ["US_SSN", "CREDIT_CARD"] + + anonymization: + method: "placeholder" + faker_locale: ["fr_FR", "en_US"] + entity_overrides: + CREDIT_CARD: + method: "mask" + masking_char: "*" + SECRET: + method: "redact" + + deanonymization: + enabled: true + # Opt-in: also restore placeholders the model re-typed imperfectly (small + # wrong-substitution risk on lookalike spans). + fuzzy_matching: false + fuzzy_threshold: 0.85 + + # Dry-run inspection endpoint (POST /v1/pii/inspect): returns the detections + # for a text you submit, without forwarding anything to any provider and + # without logging any value. For testing what gets redacted. Off by default. + inspect: + enabled: false + + passthrough: + system_messages: false + tool_calls: false + +logging: + format: "json" + level: "info" From eea2cd7991bbbde2d553a9042066106dbb1c6bc9 Mon Sep 17 00:00:00 2001 From: crp4222 <121295348+crp4222@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:21:41 +0100 Subject: [PATCH 2/3] ci: publish docker image to ghcr on release --- .github/workflows/docker-publish.yml | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/docker-publish.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..0b79ed8 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,49 @@ +name: Publish Docker image + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + docker: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/crp4222/privaite + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From 0912625e0d56fb7fbe88a7782be93012c145d58f Mon Sep 17 00:00:00 2001 From: crp4222 <121295348+crp4222@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:21:41 +0100 Subject: [PATCH 3/3] docs: docker quickstart, drop rate-limited downloads badge --- README.md | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b25ba3..6ac332e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ Self-hosted PII redaction proxy for LLM APIs. [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/) [![License](https://img.shields.io/badge/license-BSD--3--Clause-green.svg)](LICENSE) [![PyPI](https://img.shields.io/pypi/v/privaite.svg)](https://pypi.org/project/privaite/) -[![PyPI downloads](https://img.shields.io/pypi/dm/privaite.svg)](https://pypi.org/project/privaite/) **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.** @@ -236,10 +235,48 @@ WebUI filter (see [Open WebUI filter](#open-webui-filter) below). ## Docker +Pull and run, no build needed. The detection model is baked into the image, so it +runs offline from the first request. + +Proxying OpenAI, just set your key: + +```bash +docker run -d -p 8400:8400 \ + -e PRIVAITE_API_KEYS=change-me \ + -e OPENAI_API_KEY=sk-... \ + ghcr.io/crp4222/privaite +``` + +That 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 instead: + ```bash -docker compose up -d +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 ``` +A minimal `privaite.yaml`: + +```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. + +Then point any OpenAI-compatible client (or Open WebUI) at `http://localhost:8400/v1` +with the key `change-me`. + ## Open WebUI filter `integrations/openwebui/privaite_filter.py` is an Open WebUI Filter Function. It