Deterministic, offline Swiss/EU (FADP/GDPR) PII gateway: tokenize personal data before a cloud LLM sees it, restore it on the way back.
Project description
Sovereign Shield
Use any cloud LLM. Keep the personal data in Switzerland.
A deterministic, offline gateway for Swiss/EU (FADP / GDPR) personal data. It
tokenizes structured identifiers locally — turning 756.1234.5678.97 into
[AHV_1] — so a prompt can go to Gemini, Claude, or any model without a real
identifier ever crossing the border, then restores the real values in the reply
on the way back. Detection is regex + checksum, not ML: zero dependencies, zero
latency, and it cannot be talked out of a match.
⚠️ Disclaimer. Sovereign Shield is an engineering utility that aids programmatic privacy mitigation. It is not an automated guarantee of regulatory compliance under the Swiss Federal Act on Data Protection (FADP) or the EU GDPR, and it is not legal advice. Context-dependent leak vectors (free-text names, encoded data, semantics) can still slip past a structural, deterministic layer. Use it alongside a DPIA where required, audit logs, and human review — as the outer, deliberately-dumb layer of a defence-in-depth stack.
See it live → shield.ars.md · deterministic, in-browser, no API key.
Install
pip install sovereign-shield-ch # core: stdlib-only, zero dependencies
pip install "sovereign-shield-ch[gateway]" # + the optional LangChain proxy
Requires Python 3.12+.
Quickstart
from sovereign_shield import SovereignShield
shield = SovereignShield()
raw = ("Guten Tag. Meine AHV-Nummer ist 756.1234.5678.97. Bitte die Praemie auf "
"IBAN CH9300762011623852957 zurueckerstatten. Erreichbar unter "
"+41 79 214 88 03 oder hans.muster@bluewin.ch.")
# 1. De-identify locally. `safe` is all that crosses the border.
safe, ctx = shield.sanitize(raw)
# safe -> "Guten Tag. Meine AHV-Nummer ist [AHV_1]. Bitte die Praemie auf
# IBAN [IBAN_1] zurueckerstatten. Erreichbar unter [PHONE_1] oder [EMAIL_1]."
print(ctx.audit()) # {'ch_ahv': 1, 'iban': 1, 'ch_phone': 1, 'email': 1}
# 2. Call any cloud LLM on the placeholders (it never sees a real value).
answer = call_your_llm(safe)
# 3. Restore the real values locally before serving the user.
result = shield.rehydrate(answer, ctx)
print(result.text) # real AHV / IBAN / phone / email swapped back in
print(result.clean) # True if the model didn't mangle a placeholder
sanitize is fail-closed: if any structured identifier would survive into
safe, it raises DataLeakError instead of leaking. rehydrate is strict and
deterministic, and reports any placeholder the model mangled or invented
(result.leftover) so you never ship a broken [AHV_1 to a user.
Transparent LangChain proxy
With the [gateway] extra, wrap any LangChain chat model and call it as usual —
sanitize-out and rehydrate-in happen under the hood:
from langchain_google_genai import ChatGoogleGenerativeAI
from sovereign_shield.gateway import ShieldedChatModel
llm = ShieldedChatModel(ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0.3))
reply = llm.invoke("Refund AHV 756.1234.5678.97 to IBAN CH9300762011623852957.")
print(reply.content) # real values restored
print(reply.additional_kwargs["sovereign_shield"]) # {'kept_on_shore': 2, 'leftover': []}
Run it as a drop-in proxy
No code changes: run a stateless, OpenAI-compatible reverse proxy and point any OpenAI-compatible client at it. It sanitizes the prompt, forwards it to the real provider, and rehydrates the reply — your API key flows straight through and nothing is stored.
pip install "sovereign-shield-ch[proxy]"
sovereign-shield-proxy # serves on :8000, forwards to https://api.openai.com/v1
Point your client's base URL at it (the key still goes to the real provider):
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1")
Front a different provider, or run it as a container sidecar:
SOVEREIGN_UPSTREAM_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai sovereign-shield-proxy
docker build -t sovereign-shield-proxy . && docker run -p 8000:8000 sovereign-shield-proxy
Stateless (the token↔value map lives only for the request) and keyless (your
Authorization header is forwarded upstream). v1 covers non-streaming
/v1/chat/completions; streaming (SSE) is rejected with a clear error for now.
What it detects
Deterministic shape regex + checksum — the checksum rejects look-alikes so the
guard never trips on a random 13-digit string. Separators are stripped first, so
756.1234.5678.97 and 756 1234 5678 97 validate identically.
| Category | Identifier | Validation |
|---|---|---|
ch_ahv |
Swiss AHV / AVS number | EAN-13 check digit |
iban |
CH / LI IBAN | ISO-7064 mod-97 |
credit_card |
Card PAN | Luhn |
ch_phone |
Swiss phone | shape only |
email |
shape only | |
dob |
Date of birth | off by default (bare dates false-positive) |
Scope: structured identifiers only. Person names and street addresses are
not detected — they need an NER model, which would forfeit the deterministic,
zero-dependency guarantee. Plug your own via SovereignShield(extra_detectors=[...])
(see SpanDetector); overlapping spans are dropped fail-closed.
Not encoding-robust. A model that base64s or ciphers an identifier defeats the regex. Separator/whitespace reformatting is handled; encoding is not.
How it works
The thesis, proven in the K.E.V.I.N. red-team research this is extracted from: you can't close a data leak from inside the model — a jailbreak, a pretext, or a forced output schema will make it disclose. So you put a deterministic, offline boundary around the model instead. Sovereign Shield is that boundary, as a library: detect → tokenize → (model) → restore.
The browser demo ships a TypeScript port of the exact same detectors, kept byte-for-byte in parity with this Python source by a generated vector suite — so redaction on the client and on the server can never silently drift.
Development
pip install -e ".[dev]"
pytest # unit + round-trip suite
ruff check . && ruff format --check . && mypy
python scripts/gen_shield_vectors.py --check # Python parity vectors current
cd web && npm install && npm run parity # TS shield reproduces them exactly
The web/ directory is the live demo (Next.js). See web/README.md.
Credits & license
Extracted from the K.E.V.I.N. adversarial-testing project; background in the FADP AI-gateway write-up. Licensed under Apache-2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sovereign_shield_ch-0.2.0.tar.gz.
File metadata
- Download URL: sovereign_shield_ch-0.2.0.tar.gz
- Upload date:
- Size: 33.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1614504830c16ef978f114ece4d2e61139c4493da3586bc6d2fc69c51a321bf
|
|
| MD5 |
6075c303c894830fcf51bf126e72d8f7
|
|
| BLAKE2b-256 |
97043bb0013fe2989135feb7cd8cbffe909f54c13cd451aebd38e4b0852d5855
|
Provenance
The following attestation bundles were made for sovereign_shield_ch-0.2.0.tar.gz:
Publisher:
release.yml on acoseac/sovereign-shield
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sovereign_shield_ch-0.2.0.tar.gz -
Subject digest:
a1614504830c16ef978f114ece4d2e61139c4493da3586bc6d2fc69c51a321bf - Sigstore transparency entry: 2126812918
- Sigstore integration time:
-
Permalink:
acoseac/sovereign-shield@a34088af7513cc223aa183901aff5a14b9f70f95 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/acoseac
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a34088af7513cc223aa183901aff5a14b9f70f95 -
Trigger Event:
release
-
Statement type:
File details
Details for the file sovereign_shield_ch-0.2.0-py3-none-any.whl.
File metadata
- Download URL: sovereign_shield_ch-0.2.0-py3-none-any.whl
- Upload date:
- Size: 26.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23df890e01e331cd13de78b8d4f1129f320d9aaf4c1d869665900f58a582ab86
|
|
| MD5 |
4ac5307947b7fc1744d9a67f09f8c00a
|
|
| BLAKE2b-256 |
adca564f1e53dc784821a10901a3bb1e1532e0ec9bd181ac57e8879107d2892c
|
Provenance
The following attestation bundles were made for sovereign_shield_ch-0.2.0-py3-none-any.whl:
Publisher:
release.yml on acoseac/sovereign-shield
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sovereign_shield_ch-0.2.0-py3-none-any.whl -
Subject digest:
23df890e01e331cd13de78b8d4f1129f320d9aaf4c1d869665900f58a582ab86 - Sigstore transparency entry: 2126813117
- Sigstore integration time:
-
Permalink:
acoseac/sovereign-shield@a34088af7513cc223aa183901aff5a14b9f70f95 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/acoseac
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a34088af7513cc223aa183901aff5a14b9f70f95 -
Trigger Event:
release
-
Statement type: