Skip to main content

PII Guard

PII Guard, a privacy gateway for AI agents: personal data becomes reversible placeholders before it leaves your network and comes back in the answer. You send 'Email Jane Placeholder: jane@example.net', the model sees 'Email [[PERSON_7f3a]]: [[EMAIL_20c2]]', you get back 'Done: emailed Jane Placeholder.' Built on Meta's open models, runs on your machine, Apache-2.0.

Try it live Demo Video CI Model on Hugging Face License: Apache-2.0

  • What it does. An OpenAI-compatible gateway for AI agents. Change one base URL, and the names, addresses, IDs and faces it detects become reversible placeholders ([[PERSON_7f3a]]) before the request leaves. The real values come back in the answer, mid-stream and inside tool calls. Detection is rules plus a small NER that runs on CPU: XLM-RoBERTa base, distilled from Meta's Muse Glimmer 30B.
  • How well. On 990 fictional agent-traffic documents (de, en, hu), 0.2% of personal values reached the model and 0.7% of masks hit text that was not personal data. The lowest-leak alternative, PrivAiTe, leaked 2.4% and over-masked 35.2% on the same documents.
  • How fast. The NER takes 43 ms per message on a laptop CPU (p50; p95 50 ms, measurement). No LLM call and no API cost in fast mode.
  • Where your data goes. In fast mode, the default (rules plus the on-device NER, no LLM call), the original text is processed only on your machine. The hosted demo receives what you type, so use fictional data there. Images go unmasked to SAM 3.1 on the Meta Model API and to Muse Spark Open on Baseten, which reads their text; the optional audit endpoint receives the original text. Anything the detectors miss passes through: pseudonymization, not anonymization or a compliance guarantee.
Leak rate against over-masking rate on 990 fictional agent-traffic documents; lower-left is better. PII Guard fast 0.2% leak, 0.7% over-masking; PrivAiTe 2.4% and 35.2%; Presidio 16.6% and 33.4%; LLM Guard 20.2% and 16.9%; PII Guard rules only 38.8% and 0.0%.

990 fictional documents (de, en, hu), drawn by pii-guard eval report from the run records. All sets · Shareable PNGs

Built for the Meta Global AI Developer Hackathon (Oct 26 – Nov 4, 2026) · 3-minute video walkthrough. Released on PyPI as wattmind-pii-guard. Known gaps.

Quickstart

Walkthrough: watch the 3-minute demo video on YouTube.

No install: the live demo runs as a guest with no sign-up (fictional data only; capped sponsored allowance).

On your machine (needs uv): the web console, the API and the gateway on http://127.0.0.1:8787, with a mock model that echoes what it receives:

uvx --python 3.12 --from wattmind-pii-guard pii-guard up

Launch a coding agent directly (OpenCode, Codex, Claude Code, pi, Muse Code) behind an in-process local gateway with Meta's muse-spark-1.3:

export META_API_KEY=<your-key>
uvx --python 3.12 --from wattmind-pii-guard \
  pii-guard run opencode --upstream meta --model muse-spark-1.3 --dashboard local

--dashboard local also serves the web console on the gateway port: open the local dashboard http://127.0.0.1:<port>/#traffic URL that run prints at startup.

  • The package is wattmind-pii-guard: pii-guard on PyPI is an unrelated project. The latest develop builds from git (--from 'wattmind-pii-guard @ git+https://github.com/watt-mind/pii-guard@develop') without the web console; everything else is the same.
  • First run: downloads PyTorch and the NER once (the NER is 1.1 GB); /healthz reports "ner": "ready" when detection is live.
  • Platforms: macOS 14+ on Apple silicon and Linux (x86_64, arm64); on Windows use WSL2 (it is Linux) or Docker, native Windows is not tested yet. On Linux and WSL2 add --torch-backend cpu after uvx to skip the multi-GB CUDA build of PyTorch. Intel Macs and macOS 13 have no PyTorch build: Intel Macs install text masking with rules only and refuse images (the banner says so); the Docker demo runs everything on either.
  • A real model: --upstream meta with META_API_KEY; openai, fireworks, openrouter or any --upstream-url work the same way (pii-guard up options, coding agent integrations).
  • No auth by default, bound to 127.0.0.1: set PII_GUARD_API_TOKEN before exposing it (how to expose it safely).

Results

Results table, leak · over-masking per fixture set for PII Guard fast, PrivAiTe, Presidio and LLM Guard; lower is better and the lowest per metric in each row is bold. PII Guard fast leaks 0.0% on the hand-written set, 0.2% on agent traffic, 0.6% on Nemotron-PII, 0.8% on CEE-PII and 19.1% on Gretel finance (Presidio 18.8%). Every number is in the text table below the image.
Table as text
Leak · over-masking PII Guard fast PrivAiTe Presidio LLM Guard
Hand-written · 62 docs (de+en+hu+pl) 0.0% · 1.3% 13.0% · 25.7% 25.5% · 26.1% 46.3% · 33.7%
Agent traffic · 990 docs (de+en+hu) 0.2% · 0.7% 2.4% · 35.2% 16.6% · 33.4% 20.2% · 16.9%
Nemotron-PII · 300 docs (en) 0.6% · 1.5% 4.0% · 5.6% 12.8% · 32.0% 28.8% · 0.6%
CEE-PII · 451 docs (en+hu) 0.8% · 7.3% 4.4% · 59.2% 36.5% · 52.5% 75.8% · 66.4%
Gretel finance · 400 docs (de+en) 19.1% · 24.4% 27.7% · 46.5% 18.8% · 75.4% 59.3% · 17.0%

Leak: labeled personal values left fully readable in what the model receives. A value with only part left readable (a surname, the tail of a number) is a partial leak, counted separately: PII Guard fast 0.0% on agent traffic, 1.4% on the hand-written set. Over-masking: masks on text that was not personal data. Lower is better for both; in each row, the lowest leak and the lowest over-masking are bold.

Caveats

  • The sets are small (tens to hundreds of documents per language): a point or two of difference is noise. Agent traffic is written by Muse models from fictional seeds, which can flatter a detector taught by Muse labels; that is why the hand-written and public sets sit next to it.
  • Finance text is the weak spot. On Gretel finance's own labels PII Guard leaks 19.1%, about level with Presidio's 18.8%; on corrected labels (why) 2.4% against 14.8%. Over-masking is high on Gretel finance (24.4%) and CEE-PII (7.3%), where reference numbers without a cue word come out as IDs.
  • NER v4.2 leaks more than the previous v2.0 on CEE-PII (0.8% against 0.4%), the Polish pl-pii-bench (4.2% against 3.5%) and TAB court text (5.1% against 4.0%, scored locally only), and less on the hand-written sets, agent traffic and the EU multilingual set (model notes).
How this is measured

Every tool ran on the same documents (byte-identical files) and was scored by the same code; comparison tools use their documented defaults (configurations). All data is fictional. PII Guard fast is rules plus the published NER v4.2 (model card). The hand-written set pools de, en, hu, pl; the full tables, per-language numbers, Ai4Privacy (in-distribution for the NER, so not a win) and the corrected finance labels are in the benchmark report.

NER model time per message on CPU (the model alone, not the whole gateway): p95 50 ms (122 messages of about 382 characters, arm64 macOS 26.5, 8 threads; measurement).

Records behind the PII Guard fast column: Hand-written (1 · 2), Agent traffic, Nemotron-PII, CEE-PII, Gretel finance; every other cell is in the run log.

Per-language results, the open NER models compared behind the same rules, and latency are in the benchmark report.

Which Meta models do what

Muse Glimmer 30B was the best zero-shot detector we measured (F1 99.3, 0.0% leak on the 38 Hungarian and English hand-written documents of that run, record) and far too slow for a request path at 9.4 s per document (p50). So it labeled the training text instead, and XLM-RoBERTa base learned from it: 43 ms on CPU, published as watt-mind/pii-guard-ner-xlmr-base v4.2.

Only Meta models run by default (ADR 0015). In the default fast mode the rules and the NER, the models that read text, run on your machine. Images go, unmasked, to SAM 3.1 on the Meta Model API and to Muse Spark Open on Baseten, which reads their text (ADR 0016): without their keys they are refused, never handed to a non-Meta model. Audit mode (Muse Spark Open) is opt-in and receives originals at the endpoint you configure. Escalate mode (Muse Spark Open) is opt-in and sends masked windows only.

Job Meta model
Fast-mode NER (CPU) XLM-RoBERTa base (FacebookAI, MIT), fine-tuned: model card, training
Teacher: labels training text Muse Glimmer 30B (open weights, Apache-2.0): distillation
Benchmark writer; demo model Muse Spark (Meta Model API): which model does what
Escalate and audit modes, red team Muse Spark Open (open weights): escalate runs, audit runs, red team
Fictional ID specimens Muse Image: painted the portraits, signatures and stamps (generation record)
Faces, ID photos and signatures in images SAM 3.1 (Meta Model API, decoded with Meta's meta_sam_parser), self-hosted and hosted: specimen results
Text in images (OCR) Muse Spark Open (Baseten Model APIs, or your own endpoint), self-hosted and hosted: Muse vs Tesseract

Optional, off by default (not Meta models): YuNet faces and the YOLOS-tiny signature detector on the device (PII_GUARD_SEGMENTER=hybrid or local), Tesseract OCR on the device (PII_GUARD_OCR_ENGINE=tesseract), and the public fallback NER (PII_GUARD_NER_FALLBACK=download). See which model does what.

Inside the gateway

How it works: your app or agent sends a request to PII Guard, which runs on your side. On the way out PII Guard does 1. extract text, tool calls and image regions; 2. detect personal data with rules, then NER, then an optional audit LLM; 3. apply policies and pseudonymize with keyed placeholders; 4. mask image regions (faces, SAM 3.1, OCR). Only the masked request, with placeholders such as [[PERSON_968e29]] in place of personal data, crosses to the LLM provider. The response streams back to PII Guard, which does 5. restore placeholders chunk by chunk, and your app or agent gets the answer with the real values. The placeholder-to-value mapping never leaves PII Guard.

The gateway speaks the OpenAI Chat Completions and Responses APIs. It masks messages, tool definitions, tool-call arguments, tool results and images, restores placeholders in streams, keeps one placeholder per person per session, and fails closed on anything it can't mask; the mapping never leaves PII Guard. Every document in the hand-written set unmasks back to its original exactly.

Mode Detection Leaves your machine
fast (default): everyday traffic Rules + on-device NER; no LLM call Only the masked request, to your LLM provider
fast with PII_GUARD_DETECTORS=rules: offline runs, CI, structured data Rules only; free-text names only as known values your app registers Only the masked request
escalate (opt-in, X-PII-Guard-Mode: escalate): an LLM second look only where fast mode is unsure Fast, then Muse Spark Open on the uncertain spans only; the LLM can add or retype spans, never unmask; a failed call masks the uncertain words Nothing when nothing is uncertain; else small windows of the original text (about 200 characters around each uncertain word, every value already found replaced by a placeholder), to the LLM endpoint you configure (measured)
audit (opt-in, X-PII-Guard-Mode: audit): measuring the upper bound, training labels Rules + NER + an LLM detector; fails closed if that call fails The original text, to the audit endpoint you configure

Grammar-aware. In Hungarian a name changes with every case; the ending stays outside the placeholder:

Hungarian (real output), with an English gloss
You send Kérem, írjon Próba Zsuzsannának a zsuzsa.proba@example.com címre.
Please write to Zsuzsanna Próba at …
The model sees Kérem, írjon [[PERSON_968e29]]nak a [[EMAIL_e7038b]] címre.
… to [[PERSON]] …
You get back Megírtam a levelet Próba Zsuzsannának.
I wrote to Zsuzsanna Próba.

Language packs. Hungarian, English, German, Polish, French, Spanish, Italian, Dutch and Romanian each ship validated identifier rules (TAJ, Steuer-ID, PESEL, NIR, DNI/NIE, codice fiscale, BSN, CNP ...), cue words and, where names inflect, case handling; a new language is one package (how to add a language, what each pack finds).

What it detects: people, addresses, emails, phone numbers, dates of birth, national and document IDs (including Hungarian tax and TAJ numbers), bank accounts and payment cards, vehicle plates, parcel numbers and secrets such as API keys, plus faces, ID photos and signatures in images (full entity list).

Real agents behind it. On 2026-10-29 four unmodified coding agents (pi, OpenCode, Codex, Muse Code) worked a fictional customer file through the gateway: 115 requests, none of the file's 9 personal values reached the model, and the answers and the files on disk came back with the real values. No turn was refused; in an earlier run on 2026-10-28, 7 of 15 were, before two gateway fixes (a system note saying the placeholders are intentional, and the NER no longer masking file names). Two Codex turns stopped on gateway bugs; ten turns is a sample, not a benchmark (harness matrix).

Architecture · sessions and modes · threat model · GDPR context

Thirteen differentiators, each with evidence.

Use it from code

Round trip

Mask, then unmask with the session PII Guard keeps. With pii-guard up running, this script takes the session id and the person placeholder from the first response (output from fast mode):

R=$(curl -sS http://127.0.0.1:8787/v1/mask \
  -H 'Content-Type: application/json' \
  -d '{"text": "Email Jane Placeholder, jane.placeholder@example.net, about the refund.", "language": "en"}')
echo "$R"
# {"masked_text":"Email [[PERSON_604260]], [[EMAIL_84bb25]], about the refund.",
#  "session_id":"s_a925963eb8819a782a68c56f41265424","entities":[...],"kept":[]}
SID=$(echo "$R" | python3 -c 'import json,sys; print(json.load(sys.stdin)["session_id"])')
P=$(echo "$R" | grep -o '\[\[PERSON_[0-9a-f]*\]\]' | head -1)
curl -sS http://127.0.0.1:8787/v1/unmask \
  -H 'Content-Type: application/json' \
  -d "{\"text\": \"Done: I emailed $P about the refund.\", \"session_id\": \"$SID\"}"
# {"text":"Done: I emailed Jane Placeholder about the refund.","unresolved":[]}

The session id and the placeholders differ on every run: each new session gets its own.

Python client

Put it in front of an existing client; only the base URL changes:

# pip install openai
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8787/v1", api_key="unused")  # mock upstream
stream = client.chat.completions.create(
    model="mock",
    stream=True,
    messages=[
        {"role": "user", "content": "Refund Jane Placeholder, jane.placeholder@example.net."}
    ],
)
for chunk in stream:  # the mock echoes the masked text it got; the gateway restores the values
    print(chunk.choices[0].delta.content or "", end="")
# Echo: Refund Jane Placeholder, jane.placeholder@example.net.

What the model actually received: curl -sS 'http://127.0.0.1:8787/v1/traffic?limit=1' shows the last gateway request as sent upstream, with placeholders (pii-guard up keeps a masked-only traffic log). API guide · agents and harnesses.

Console tour

Console tour in the dark theme, recorded in fast mode (rules + NER) with fictional English data: Overview, a support email masked in the Playground with names replaced by placeholders, the same session in Chat, Logs and Connect

Console tour, recorded in fast mode (rules + NER) with fictional data. More screens · Video walkthrough.

Run the console locally: make demo from a checkout.

Contributing, security, license

The contributing guide has the rule that matters most: never commit real personal data, not even in a test.

Security: found a way to make personal data leak past PII Guard? Report it privately: SECURITY.md.

Licensed Apache-2.0.

Metadata

Release files for wattmind-pii-guard 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wattmind-pii-guard 0.2.0
File Size Uploaded Provenance Actions
wattmind_pii_guard-0.2.0.tar.gz 20.0 MB Without provenance Details for wattmind_pii_guard-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for wattmind-pii-guard 0.2.0
File Interpreter ABI Platform Actions
wattmind_pii_guard-0.2.0-py3-none-any.whl Python 3 none any Details for wattmind_pii_guard-0.2.0-py3-none-any.whl

Total release size: 24.0 MB

Release files / wattmind_pii_guard-0.2.0.tar.gz

Download URL wattmind_pii_guard-0.2.0.tar.gz
Size 20.0 MB
Tags Source
SHA-256 checksum
How to use checksums
34b5448adb080c2649bb7336a5725f0b217d0c2ee5d9fdc05f8683da6d02442c
BLAKE2b-256 checksum
How to use checksums
c77a983861984682f1446d0e4221794d0df890c6d6162a63aaa0cca73fb9c2b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / wattmind_pii_guard-0.2.0-py3-none-any.whl

Download URL wattmind_pii_guard-0.2.0-py3-none-any.whl
Size 3.9 MB
Tags Python 3
SHA-256 checksum
How to use checksums
6b4ce711625f21d86b2c9bc54f053626de791cdea43739afa9ff29db02dd6660
BLAKE2b-256 checksum
How to use checksums
4f3bb1938a96d8d70415b7be0530ed13513fab4590c830e63f1654cedc92eca6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for wattmind-pii-guard 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page