Skip to main content
Apache-2.0 benchmark and self-hosted privacy gateway

Does your LLM gateway send raw personal data upstream?

pii-leak-benchmark tests any OpenAI-compatible SSE gateway in about a minute. Its only third-party Python dependency is httpx. You do not need to install one gateway to test another.

LLM-Shield-Proxy is listed by name with the other tested gateways. Its result follows the same evidence and replication rules as every other result.

Open, reproducible streaming privacy and audit evidence for enterprise LLM infrastructure Follow the links below to inspect the method, reports, and source.

6
Gateway configurations tested with reports published
0
Independent repetitions so far
1
Third-party Python dependency: httpx
Apache 2.0
Open-source license
What the first runs found

The first test values were biased

All six current results were produced by this project on one workstation. No outside contributor has repeated them, so every product result is marked unreplicated.

The first prompt used invalid examples of all three data types:

Old test valueWhy Presidio rejected it
[email protected].invalid is not a public domain suffix
123-45-6789Presidio blocks this well-known invalid SSN sequence
4532-1234-5678-9012The number fails the Luhn card-number checksum

LLM-Shield-Proxy matched the patterns without checking whether the values were valid. This gave it an unfair advantage over validating detectors. A LiteLLM and Presidio run revealed the problem. The affected result was not published, the values were replaced, and all six configurations were tested again.

The benchmark also found two streaming bugs in LLM-Shield-Proxy. Both are fixed and covered by regression tests.

Known limitation: the test uses three fixed data formats. A small program written specifically for those formats can pass without being a general PII detector. The values change on every run, but the formats do not. Testing more formats caused two false failures in six trials.

Run it against your own gateway

Standard library plus httpx. You should not have to install one gateway to measure another, and nothing in the harness imports the proxy.

pip install pii-leak-benchmark

# negative control: boundary check MUST report a leak
pii-leak-benchmark --target-base-url capture://self

# your gateway, pointed at the capture upstream
pii-leak-benchmark --target-base-url http://127.0.0.1:4000/v1
TargetOutcomeRuns / submittersStatus
Raw capture endpoint
negative control, not a product
fail1 / 1control
LLM-Shield-Proxy
this project
pass1 / 1unreplicated
LiteLLM 1.99.0
default configuration, no guardrail attached
redaction-not-enabled1 / 1unreplicated
LiteLLM 1.99.0 + Presidio
no leak; the values are not restored to the client
no-leak-profile-not-met1 / 1unreplicated
Portkey Gateway OSS 1.15.2
default configuration, no guardrails
redaction-not-enabled1 / 1unreplicated
Portkey Gateway OSS 1.15.2 + redaction
no leak; one-way replacement, tester-authored patterns
no-leak-profile-not-met1 / 1unreplicated

Every product result was run once by this project's maintainer. A result becomes replicated only after three different people each submit a run of the same gateway and configuration. fail means the gateway sent an unmasked test value to the benchmark's capture server. Products that do not advertise PII redaction are marked not applicable, not failed.

The results wall

6 of 10 measured setups sent data to the provider

Each row is one gateway at one pinned version in one stated configuration, measured with the same check you can run yourself. “Reached the provider” means raw test values arrived at a capture server standing in for OpenAI.

LLM-Shield-Proxy (ours)1.6.6, response scan on
nothing reached the provider
LLM-Shield-Proxy (ours)1.6.6, default settings
nothing reached the provider
LLM-Shield-Proxy (ours)1.6.0, default settings
nothing reached the provider
LLM-Shield-Proxy (ours)1.6.0, response scan on
nothing reached the provider
LLM Guard0.3.16, scanned per chunk
sent phone
LLM Guard0.3.16, whole response buffered
sent phone
Guardrails AI0.10.2, sentence retention
sent all 4 types
LiteLLM1.99 with Presidio
sent all 4 types
NeMo Guardrails0.24.0
sent all 4 types
PortkeyOSS gateway
sent all 4 types

Every row above was measured by this project, including our own, and none has been replicated by anyone unaffiliated yet. A row measures the configuration named in it, not the product’s best possible configuration. The full table carries the rest of the columns, who ran each one, and how to disagree with it.

Browser preview with fictional data

See how each masking mode changes a request

This preview runs in your browser. It does not contact a model or send data anywhere. It demonstrates the regex and synthetic-value paths with fictional examples. The server can also use an optional local ONNX model and real AES-256-GCM encryption. Choose Agent tool call to see how string values in JSON are handled.

Fictional values with a similar format.

You type this
Sent to LLM
Waiting for you to stop typing…
Received from LLM (rehydrated)
The rehydrated reply will appear here.
Type a prompt, or load an example below, to begin
Client setup

Change the client address after configuring the proxy

OpenAI-compatible clients can keep the same request format. Set base_url to the proxy address and configure provider and client keys on the server.

OpenAI-compatible client setup
# Before - talking straight to the provider
$ curl https://api.openai.com/v1/chat/completions \
    -H "Authorization: Bearer $OPENAI_API_KEY" \
    -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Update record for John Doe, SSN 456-12-7890"}]}'

# After - point the same SDK at LLM-Shield-Proxy. Nothing else changes.
$ curl https://shield.internal.acme.corp/v1/chat/completions \
    -H "Authorization: Bearer $OPENAI_API_KEY" \
    -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Update record for John Doe, SSN 456-12-7890"}]}'

→ the LLM only ever sees: "Update record for Michael Ito, SSN 839-14-2207"
→ your agent still receives: "Update record for John Doe, SSN 456-12-7890"
Engineered around technical safeguards for

These are technical controls mapped to each framework's requirements - see how in the docs. Deploying this proxy is one control among many a full compliance program requires; it is not a certification.

Reports you can inspect

Check the behavior and the evidence yourself

Run the tests, inspect the JSON reports, and verify signed audit records. The results state what was tested and what remains outside the test.

Audit records

Signed records with ordering checks

Each record links to the previous record. Sequence numbers reveal missing or reordered entries. Ed25519 signatures can be checked offline. OSCAL 1.2 export and optional confirmed JSONL writes are also available.

Local files can reveal later changes, but they are not storage-level WORM. You must provide immutable storage, external copies, and secure key management if your deployment requires them.
Inspect the audit contract → · See what remains →
Gateway test suite · v1.0.0

Six checks in one JSON report

Split-event handlingUnmasked values sent upstreamSSE validityOriginal-value restorationAudit integrityMemory bounds
pii-leak-benchmark --target-base-url http://127.0.0.1:4000/v1
Read the public specification →
Apache-2.0 open source.
Chat and structured requests

Protect text prompts and tool-call data

LLM requests can contain plain text or structured JSON. LLM-Shield-Proxy has separate paths for both. Configure and test the path used by your provider or tool.

💬

Human ↔ LLM (chat & prompts)

For chat text, choose whether to replace a sensitive value with a synthetic value, a tag, an empty value, or encrypted text. The selected policy can vary by request.

See the 4 masking modes →
🤖

Machine ↔ Machine (agent tool calls)

For supported JSON-RPC requests, the proxy parses the JSON and checks its string values. It can replace selected values and restore them in the response. Test the exact schemas and response behavior used by your integration.

See the stateless agent firewall →
How it works

Four steps from client to provider and back

01

Point your SDK at the proxy

After configuring the proxy, change your client base_url to the proxy address. OpenAI-compatible clients can keep using the same request format.

02

Find configured data types

Local regex rules, secret scanning, and optional ONNX name detection inspect the request before it is sent to the model provider.

03

Mask and forward

The proxy replaces selected values with synthetic data, tags, empty values, or encrypted text. It then sends the changed request to the model provider.

04

Restore allowed values

As SSE events arrive, the proxy joins replacement tokens that were split between events and restores values the client may receive. Measure the added delay in your own deployment.

MCP tool governance

Decide which tools each client may call

LLM-Shield-Proxy provides an experimental /v1/mcp endpoint for a documented subset of Model Context Protocol JSON-RPC calls. It checks each supported tool call against the client's policy, replaces selected values in arguments and results, and can record the decision. It is not a complete MCP implementation.

ALLOWEDsearch_kb
  1. Agent calls tools/call "search_kb"
  2. Proxy checks the client key and allowed_tools policy
  3. Proxy parses the arguments and replaces selected PII
  4. Request goes to your internal tool server
  5. Proxy checks the result and returns it to the agent
FORBIDDENshell_exec
  1. Agent calls tools/call "shell_exec"
  2. Proxy checks the client key and blocked_tools policy
  3. Proxy blocks the request before contacting the tool server
  4. Configured audit logging records the denial
  5. Agent receives JSON-RPC error -32003
Design choices

Compare the available approaches

LLM-Shield-Proxy is one option for self-hosted, streaming traffic. These are the main differences to test when comparing it with batch or hosted DLP tools.

Batch or hosted DLPLLM-Shield-Proxy
StreamingA batch scanner may wait for the full response before it can inspect it.Checks and restores values as SSE events arrive. Measure the total delay in your deployment.
Where scanning happensA hosted DLP service receives the text that it scans.Runs in your environment and can test the request sent to the configured model provider.
Detection costNLP detection uses memory and compute that vary by model and runtime.Makes the ONNX detector optional. Measure its memory and request time with your selected model.
Agent / tool-call trafficReplacing text directly in raw JSON can damage its syntax or change a field unexpectedly.Parses supported JSON-RPC messages and changes selected string values. Test your actual schemas.
Data retentionStorage and logging behavior varies by service and configuration.Offers encrypted in-band values or time-limited Redis mappings. The documentation states where plaintext can remain.

LLM-Shield-Proxy is not a model router or orchestration framework. It can run alongside LangChain, LiteLLM, Portkey, and similar tools.

Examples of systems you can test with the proxy

Orchestration
LangChainLlamaIndexSemantic KernelAutoGenCrewAI
AI Gateways
LiteLLMPortkeyKong AI GatewayCloudflare AI Gateway
Inference
vLLMOllamaNVIDIA NIMHugging Face TGI
Providers
OpenAIAnthropicGoogle GeminiDeepSeekMistral

Test the gateway with your own configuration

Run the assessment locally, or apply for a private 30-day design-partner pilot.

Prefer to inspect the code first? View the repository.