See exactly what your LLM provider receives
Do not trust the proxy blindly: check it on your own data. Everything below runs on 127.0.0.1, uses no provider credential, and sends nothing anywhere.
1. One command, reads the wire
privaite verify
It starts a throwaway provider that records exactly what it receives, sends the
same agent-shaped request twice (straight to it, then through a real PrivAiTe
app), and prints both request bodies side by side. The planted values sit in the
three places an agent puts them: message text, tool-call arguments, and tool
output. It exits non-zero if any of them reached the wire, so it works as a gate
in a script, and --json gives the machine-readable form.
Since 0.7.2 the report ends with two timings: the startup, which is mostly the detection
model loading and happens once per process, and the one request sent through
the proxy. --json carries them as startup_ms and request_ms, next to
elapsed_ms for the whole run.
value where direct proxied
Marie Dupont message text LEAK clean
marie.dupont@example.invalid tool-call argument LEAK clean
4111 1111 1111 1111 tool-call argument LEAK clean
SERVICE_API_KEY=sk-demo-0000-not-a-real-key tool output LEAK clean
The direct column is the baseline: it is also what a text-only guardrail
forwards for the two structured fields, since those never pass through its
scrubber. --preset light skips the model download at the cost of recall, and
this demo shows the cost: with the default settings light leaves Marie Dupont
in the message text, so the command prints that line as LEAK and exits
non-zero. light has no contextual model, and it only trusts spaCy’s names for
the first configured language, which is French by default.
This output is what to paste into an issue when something leaks, and it is reproducible by anyone without your setup or your data.
2. See what the provider receives, on your own traffic
The command above uses planted values. For your own, turn de-anonymization off and send real test traffic:
pii:
deanonymization:
enabled: false
The response comes back without the real values restored, so what you read is
literally what left for the provider (<PERSON_1>, <EMAIL_ADDRESS_1>, …).
Diff it against your input: every placeholder is a catch, every real value still
visible is a miss. Works for tool-call arguments and streaming too.
3. Dry-run inspection endpoint
Enable it explicitly (off by default), then submit text and get the detections
back. Nothing is forwarded to any provider, nothing is logged, nothing is counted
in /stats:
pii:
inspect:
enabled: true
curl -s localhost:8400/v1/pii/inspect -H 'Content-Type: application/json' \
-d '{"text": "Contact Marie Dupont at marie@acme.com"}'
{
"language": "en",
"entities": [
{"type": "PERSON", "text": "Marie Dupont", "start": 8, "end": 20,
"score": 0.99, "source": "onnx", "replacement": "<PERSON_1>"},
{"type": "EMAIL_ADDRESS", "text": "marie@acme.com", "start": 24, "end": 38,
"score": 1.0, "source": "presidio", "replacement": "<EMAIL_ADDRESS_1>"}
],
"anonymized": "Contact <PERSON_1> at <EMAIL_ADDRESS_1>",
"would_block": []
}
anonymized is the exact string the provider would have seen, and would_block
lists any types your block_entities policy would have rejected outright. There
is deliberately no admin view of live traffic: the reversible map is per-request
and in-memory only, never logged or persisted.