DSGVO/GDPR PII scanner & anonymizer for German text — regex-based, no ML inference at runtime
1.9K
GDPR/DSGVO-compliant PII anonymisation for LLM workflows.
privacy-guard reliably detects personal data in German-language text, replaces it with stable placeholders, and enables clean restoration after processing. No ML-inference overhead at runtime for most detectors — clear results, API-ready.

Highlights
[NAME_1], [IBAN_1]ScanResult.restore()SECRET > IBAN > SOCIAL_SECURITY > EMAIL > …)| Type | Example | Method |
|---|---|---|
NAME | Dr. Anna Schmidt | spaCy NER (de_core_news_sm) |
IBAN | DE89 3704 0044 0532 0130 00 | Regex + ISO 7064 check digit |
CREDIT_CARD | 4111 1111 1111 1111 | Regex + Luhn algorithm |
PERSONAL_ID | C22990047 | Regex — Personalausweis & Reisepass (same format) |
SOCIAL_SECURITY | 12 345678 X 123 | Regex — Rentenversicherungsnummer |
KVNR | T123456780 | Regex + §290 SGB V modified-Luhn check digit |
TAX_ID | 12 345 678 903 | Regex + mod-11 check digit (§ 139b AO) |
VAT_ID | DE123456789 | Regex — Umsatzsteuer-Identifikationsnummer |
PHONE | +49 89 12345678 | Regex — DACH formats |
EMAIL | [email protected] | Regex |
ADDRESS | Hauptstraße 12, 79100 Freiburg | Regex built from data files |
LICENSE_PLATE | B-AB 1234, HH-XY 12E | Regex — Kfz-Kennzeichen inkl. E/H-Suffix |
DRIVER_LICENSE | MU010185A1 | Regex + Kontext-Fenster (±200 Zeichen) |
SECRET | AWS key, GitHub PAT, … | 100+ pattern rules (TOML) |
URL_SECRET | ?token=abc123def456 | Regex — query parameter values |
Overlap priority: SECRET = URL_SECRET > IBAN = CREDIT_CARD = SOCIAL_SECURITY = KVNR > PERSONAL_ID = TAX_ID = VAT_ID = EMAIL = DRIVER_LICENSE > PHONE = LICENSE_PLATE > ADDRESS > NAME
Public figures are excluded from masking by default via an internal whitelist (~1,000 entries).
pip install privacy-guard-scanner
The name detector requires a spaCy model:
pip install "de_core_news_sm @ https://github.com/explosion/spacy-models/releases/download/de_core_news_sm-3.8.0/de_core_news_sm-3.8.0-py3-none-any.whl"
# or:
python -m spacy download de_core_news_sm
pip install -e ".[api]"
uvicorn api.main:app --reload --port 8000
from privacy_guard import PrivacyScanner
scanner = PrivacyScanner()
result = scanner.scan(
"Bitte überweise 500 EUR an Hans Müller, "
"IBAN DE89 3704 0044 0532 0130 00. "
"Rückfragen an [email protected] oder +49 89 123456."
)
print(result.anonymised_text)
# Bitte überweise 500 EUR an [NAME_1], IBAN [IBAN_1]. Rückfragen an [EMAIL_1] oder [PHONE_1].
print(result.mapping)
# {'[NAME_1]': 'Hans Müller', '[IBAN_1]': 'DE89 3704 0044 0532 0130 00', ...}
llm_answer = "Vielen Dank, [NAME_1]. Die Daten zu [IBAN_1] sind verarbeitet."
print(result.restore(llm_answer))
# Vielen Dank, Hans Müller. Die Daten zu DE89 3704 0044 0532 0130 00 sind verarbeitet.
from privacy_guard import PiiType, PrivacyScanner
scanner = PrivacyScanner(extra_whitelist_names=["Erika Musterfrau"])
scanner.disable_detector(PiiType.NAME)
scanner.enable_detector(PiiType.NAME)
result = scanner.scan("Contact: [email protected]")
Filtering specific findings:
from privacy_guard import PiiType
secrets = [f for f in result.findings if f.pii_type == PiiType.SECRET]
for finding in secrets:
print(finding.rule_id, finding.text, finding.confidence)
The API server includes a built-in HTMX interface — no separate process, no CDN dependencies.

uvicorn api.main:app --reload
# → http://localhost:8000
An admin account with password admin is created by default (change via UI_ADMIN_PASSWORD).
After login three tabs are available:
| Tab | Description |
|---|---|
| Live Test | Enter text, select detectors, run a scan — view original and anonymised text side by side |
| History | All your own scans (admins see all users); click a row to see finding details |
| Dashboard | Overall statistics, PII-type bar chart, scans-per-day line chart (Chart.js) |
Admins additionally see the API Keys tab.
Use the 🔑 API Keys tab to create and revoke any number of API keys:
pg_…) — it is shown only oncepg_xxxxxxxxx…) remains visibleThe key set via the API_KEY environment variable remains valid in parallel (backwards compatibility).
docker run -p 8000:8000 noxway/privacy-guard:latest
Or via Compose:
docker compose up
| Method | Path | Description |
|---|---|---|
GET | /health | Liveness check |
POST | /scan | Full scan (findings + mapping + anonymised text) |
POST | /anonymize | Return anonymised text only |
{
"text": "Hans Müller, IBAN DE89370400440532013000",
"detectors": ["IBAN", "EMAIL"],
"whitelist": ["Hans Müller"]
}
curlcurl -X POST http://localhost:8000/scan \
-H "Content-Type: application/json" \
-d '{"text": "Contact: [email protected], IBAN DE89370400440532013000", "detectors": ["EMAIL", "IBAN"]}'
With API key authentication:
curl -X POST http://localhost:8000/scan \
-H "Content-Type: application/json" \
-H "X-API-Key: pg_…" \
-d '{"text": "[email protected]"}'
| Variable | Default | Description |
|---|---|---|
API_KEY | empty | If set, X-API-Key must be sent with every request (env-var key or DB key) |
CORS_ORIGINS | * | Comma-separated origins, e.g. https://app.example.com |
UI_DB_PATH | ui.db | Path to the SQLite database (users, scans, API keys) |
UI_ADMIN_PASSWORD | admin | Password for the automatically created admin account |
Example:
services:
api:
image: noxway/privacy-guard:latest
ports:
- "8000:8000"
environment:
API_KEY: my-secret-key
CORS_ORIGINS: https://app.example.com
UI_DB_PATH: /data/ui.db
UI_ADMIN_PASSWORD: secure123
volumes:
- ui_data:/data
volumes:
ui_data:
MIT. See LICENSE.
Content type
Image
Digest
sha256:099d42d01…
Size
175.9 MB
Last updated
7 months ago
docker pull noxway/privacy-guard