LLM-Shield-Proxy
This repository is two things, and the first one matters more:
pii-leak-benchmark- a neutral harness that measures whether any OpenAI-compatible streaming gateway sends raw personal data to its upstream, and whether it gives the values back to the client.- LLM-Shield-Proxy - a streaming privacy gateway. It is one of the things the benchmark
measures, labelled in the table as the reference implementation, and its row carries the same
unreplicatedcaveat as everyone else's.
The first result exposed a biased fixture
All current comparison rows come from the initial project-run measurement set, not independent
reproductions. Each row is therefore labelled unreplicated and published with its configuration
and raw report for other people to check.
The prompt used to carry three fixed values, chosen to be safe to publish: person@example.invalid,
123-45-6789, 4532-1234-5678-9012. Every one is a value a validating detector is built to
reject: .invalid has no public suffix, that SSN is a blacklisted sequence, and the card fails its
Luhn checksum (sum 68). Measured against a pinned Presidio at score_threshold: 0.0, stock Presidio
returned no EMAIL_ADDRESS, no US_SSN and no CREDIT_CARD for any of them.
The reference implementation used regexes without those validation checks, so it caught all three.
The fixture therefore favored shape matching over validated detection. A run against LiteLLM + Presidio
reported leaked: ["SSN"] on the shipped fixture and leaked: [] on the same run with valid
specimens. That row was withheld rather than published, the fixture was replaced, and every row was
re-run. Full measurement: fixture threat model.
The benchmark also exposed two defects in this proxy's streaming hot path: an OpenTelemetry
span opened per SSE delta even with export disabled, and the data line and its terminating blank
line yielded as two separate ASGI writes. Both are fixed and pinned by
tests/test_streaming_write_efficiency.py. No speed
multiplier is published for that fix: the original runner and its raw samples were not retained, so
there is no auditable evidence to cite. See the record.
Known limitation: a roughly 35-line str.replace shim with no general detector can pass all
five checks. The formats remain fixed because broader format randomization produced false leak
findings in two of six variants against a correctly redacting gateway. Values now vary within those
formats, and submitted CI reports can carry detached provenance over the finished JSON bytes.
Run it yourself, in about a minute
pip install pii-leak-benchmark
# The negative control: no gateway at all, raw pass-through. MUST report outcome=fail.
pii-leak-benchmark \
--target-base-url capture://self \
--target-name raw-pass-through-negative-control --target-version 1 \
--redaction-claimed claimed \
--redaction-claim-citation https://github.com/ninadphalak/LLM-Shield-Proxy/blob/main/website/docs/conformance/reproducing.md \
--redaction-enabled \
--redaction-config-reference "synthetic control: declared redaction intentionally absent"
# Your gateway, already configured to send upstream traffic to http://127.0.0.1:8765/v1
pii-leak-benchmark --target-base-url http://127.0.0.1:4000/v1 --target-name your-gateway
Standard library plus httpx - you should not have to install one gateway to measure another. The
harness stands a capture server in front of the gateway's configured upstream and inspects every
channel it could arrive through: request line, method, headers, chunk extensions, trailers and the
decoded JSON body. Anything it cannot inspect fails closed. Ten adversarial rounds are recorded in
the conformance docs; the rule that survived them is enumerate
the channel, not the encoding.
A measurement is not a verdict. fail means one thing only: protected data reached the capture. A
gateway that never claimed to redact anything, or that anonymizes one-way and leaks nothing, gets a
non-verdict outcome instead. This avoids calling a product a privacy failure for a capability it
never claimed to provide.
Results
| Target | Outcome | Runs / distinct submitters |
|---|---|---|
| Raw capture endpoint (control) | fail - three literal matches |
1 / 1 - control, not a product |
| LLM-Shield-Proxy (reference implementation) | pass - 5/5 |
1 / 1 - unreplicated |
| LiteLLM 1.99.0, default | redaction-not-enabled |
1 / 1 - unreplicated |
| LiteLLM 1.99.0 + Presidio | no-leak-profile-not-met (no leak) |
1 / 1 - unreplicated |
| Portkey OSS 1.15.2, default | redaction-not-enabled |
1 / 1 - unreplicated |
| Portkey OSS 1.15.2 + regexReplace | no-leak-profile-not-met (no leak) |
1 / 1 - unreplicated |
Every row is unreplicated. A gateway needs 3 runs from 3 distinct submitters before its result is treated as replicated. The current rows are project-run measurements, not independent reproductions. Each one includes the pinned configuration and raw artifact so others can verify it or report a different result. Full table, method and evidence · submit a run.
The reference implementation
pip install llm-shield-proxy
llm-shield-proxy --host 0.0.0.0 --port 8000
curl http://localhost:8000/healthz
For the container path:
docker compose up -d
curl http://localhost:8000/healthz
python examples/demo.py
LLM-Shield-Proxy is a self-hosted privacy gateway for OpenAI-compatible streaming APIs. It applies
configured PII, PHI, PCI and secret transformations before the upstream, then rehydrates the masked
values incrementally as SSE events arrive. Point an existing client at it by changing base_url:
from openai import OpenAI
client = OpenAI(api_key="your-shield-virtual-key", base_url="http://localhost:8000/v1")
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Contact Sarah at sarah@example.com."}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
A real completion needs an upstream key and a client-auth configuration. Copy
.env.example and follow the deployment guide rather
than treating the health check as a production validation.
How the reference implementation works
Inbound, the proxy detects configured sensitive values and replaces them before the request crosses the upstream boundary. Outbound, a bounded prefix-aware buffer reconstructs placeholders split across SSE chunks and restores registered values as the stream continues. Structured JSON payloads take a separate syntax-preserving mutation path and still require provider/tool schema testing.
The maintained component map and deployment diagrams live in the architecture guide, architecture whitepaper, and deployment guide.
| Area | What is implemented | Where the evidence stops |
|---|---|---|
| Detection | 10 native Tier 1 patterns, Tier 2 Shannon entropy, optional Tier 3 ONNX NER, BYOR rules | Supported types · no recall guarantee on unlabeled traffic |
| Streaming privacy | Sliding-window SSE rehydration, bounded streaming JSON lexer | Architecture · conformance method |
| Masking | Synthetic, structural-tag, scrub, operator-keyed stateless crypto | Masking guide · plaintext still exists in process memory |
| Security controls | SSRF/DNS-rebinding egress checks, request policy, rate and blast-radius limits, canary tripwires | Security · not a substitute for network policy |
| Evidence plane | Hash-linked audit records, Ed25519 receipts, OSCAL output, compliance packs | Compliance overview · tamper-evident, not WORM without immutable retention |
| MCP governance | Scoped JSON-RPC subset with RBAC and egress policy | Research-scoped; MCP guide · not a complete MCP transport |
Deployment choices
Standard mode keeps detection, masking, policy and rehydration inside the operator-controlled gateway, then sends only the transformed request to the selected external provider:
Air-gapped mode instead sends transformed traffic to an operator-controlled internal model gateway. Network policy must still prevent bypass, telemetry and other unintended egress:
See deployment topologies, air-gapped egress, and the Kubernetes/Helm deployment guide.
Every catalogued feature carries a Supported / Beta / Experimental / Research badge naming
its verification boundary: feature catalog ·
stability policy · limitations.
It supports SOC 2, HIPAA, GDPR, EU AI Act and NIST/ISO evidence programs by supplying technical controls and artifacts. It does not certify a deployment, guarantee complete detection, or make network policy optional.
Verifying this repository
git clone https://github.com/ninadphalak/LLM-Shield-Proxy.git
cd LLM-Shield-Proxy
python -m pip install -e ./pii-leak-benchmark -e ".[dev]"
python -m pytest
The benchmark is a separate distribution in this repo, so it installs first; nothing in it imports the proxy and a test fails if that ever changes. CI provisions real Redis, an HTTP/2 ALPN server, a checksum-pinned ONNX export, Docker, Helm and promtool. A missing dependency fails those jobs rather than skipping them, so a green build cannot mean "nothing ran".
Documentation
- Start here: interactive docs and playground · configuration · deployment · operations · troubleshooting
- Understand the design: architecture · architecture whitepaper · feature catalog · stability · limitations
- Integrate it: integration index · LiteLLM and Ollama recipe · Open WebUI and LangChain recipe · migration from Presidio
- Operate securely: security model · policy as code · compliance evidence mapping · immutable retention
- Verify and participate: conformance method and results · submit a reproduction · 30-day design-partner pilot
Contributing, license, and citation
Contributions are welcome through issues, discussions and CONTRIBUTING.md. The most valuable contribution is an independent benchmark run against a gateway you operate, whether it matches or differs from a row above.
Source code is Apache 2.0; documentation and diagrams may carry CC BY 4.0 terms. See LICENSE.
The author identifies U.S. application numbers 64/126,730 and 64/139,263 as pending filings related to streaming transformation and structured stateless masking. Pending applications are not issued patents; verify status with counsel and official records before relying on them.
If you reference the architecture or benchmark methodology, use CITATION.cff or:
Phalak, N. (2026). Quantifying Latency and Token Overhead in Real-Time LLM Stream Sanitization: A Tiered Detection Approach. https://doi.org/10.5281/zenodo.21955770
Release files for llm-shield-proxy 1.3.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| llm_shield_proxy-1.3.5.tar.gz | 241.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| llm_shield_proxy-1.3.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:398.3 kB
Release files / llm_shield_proxy-1.3.5.tar.gz
| Download URL | llm_shield_proxy-1.3.5.tar.gz |
|---|---|
| Size | 241.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d17f30d2cc7ff4fcf0bd6eab9b54767c978cbda1b2ed680b4fbde6d4f8f5235c
|
|
BLAKE2b-256 checksum How to use checksums |
590fc017e93145c013a65d5ccffda4713d0be76921e35975c2a968c8bda78aff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / llm_shield_proxy-1.3.5-py3-none-any.whl
| Download URL | llm_shield_proxy-1.3.5-py3-none-any.whl |
|---|---|
| Size | 156.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8033a27ff7cbacf535d34f9bdec9fa3349749893740ee5cea09ba6fce529a1ed
|
|
BLAKE2b-256 checksum How to use checksums |
871ee52e93166b41da5d6c381c735d0def235a66ea6abaade4d852847c13ace6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|