Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pii-redaction-layer

Lizenz: MIT Python 3.10+ DSGVO

Personenbezogene Daten pseudonymisieren, bevor sie an das LLM gehen. Originale in der Antwort wiederherstellen. Eine schlanke Middleware zwischen App und Anthropic / OpenAI / Gemini / Bedrock.

Entwickelt für deutsche B2B-Anwendungsfälle, bei denen Kundennamen, Telefonnummern und E-Mails die EU nicht verlassen sollen — auch wenn der LLM-Anbieter es tut.

Status

v0.1.0. Aus produktivem Einsatz unter netzhandwerker.de extrahiert. Für regulierte Produktion (Medizin, Recht, Behörden) ohne eigene Prüfung nicht empfohlen.

Was funktioniert

  • Sechs Kategorien werden standardmäßig erkannt: Personennamen, E-Mails, Telefonnummern, Postadressen, IBAN, Kreditkartennummern
  • Bidirektionale Pipeline: redact() ersetzt durch Token, restore() setzt Originale zurück
  • Deutsche und internationale Telefon-Formate
  • IBAN mit ISO-13616-Checksum-Prüfung
  • Kreditkarten mit Luhn-Validierung
  • Optional spaCy-NER für höhere Namens-Genauigkeit
  • Konfigurierbare Ausschluss-Wörter gegen False Positives

Bekannte Grenzen

  • Namens-Erkennung per Heuristik ist nicht perfekt — False Positives bei „Hello World", Ländernamen, Produktnamen
  • Geburtsdaten werden nicht erkannt (zu nah an normalen Daten — bräuchte Kontext-Analyse)
  • Gesundheitsdaten (Diagnosen, Medikamente) werden nicht erkannt — Schicht ist keine medizinische Pseudonymisierung
  • KFZ-Kennzeichen-Pattern existiert, aber selten nützlich
  • Freitext-Adressen ohne PLZ werden nicht erkannt
  • Pseudonymisierung ist reversibel (Mapping vorhanden) — nicht Anonymisierung im DSGVO-Sinn

Was die Schicht macht

┌────────────────┐    ┌──────────────────┐    ┌─────────────┐
│  Eingabe       │──▶│   redact()       │──▶│    LLM      │
│ "Maria Schmidt │    │ "[PERSON_1] aus  │    │ (beliebig)  │
│  aus München"  │    │  [ADDRESS_1]"    │    │             │
└────────────────┘    └──────────────────┘    └──────┬──────┘
                                                     │
                                                     ▼
┌────────────────┐    ┌──────────────────┐    ┌─────────────┐
│  Endausgabe    │◀──│   restore()      │◀──│  LLM-Antwort│
│ "Hallo Maria,  │    │  Token-Lookup,   │    │ "Hallo      │
│  ich prüfe das │    │  Originale       │    │ [PERSON_1]" │
│  in München..."│    │  zurücksetzen    │    │             │
└────────────────┘    └──────────────────┘    └─────────────┘

Sechs Kategorien werden standardmäßig erkannt:

Kategorie Token-Format Detektor
Personennamen [PERSON_N] Großschreibungs-Pattern für deutsche und englische Namen, optional spaCy NER
E-Mail-Adressen [EMAIL_N] RFC-5322-orientierte Regex
Telefonnummern [PHONE_N] Deutsche und internationale Formate
Postadressen [ADDRESS_N] Deutsche und EU-Postleitzahl-Patterns
IBAN [IBAN_N] ISO 13616 mit Checksum-Validierung
Kreditkarte [CARD_N] Luhn-validiert

Warum es das gibt

Drei reale Szenarien, die diese Schicht motiviert haben:

  1. DSGVO + LLM-Anbieter in den USA. Eine Support-Anfrage enthält den Namen einer Person aus Deutschland. Direkt an OpenAI geschickt ist das eine Übermittlung personenbezogener Daten in ein Drittland. Pseudonymisierung davor macht den LLM-Aufruf zu Nicht-personenbezogenen-Daten — DSGVO Art. 4 Abs. 5.
  2. Telefon-Agent-Transkripte. Das Transkript eines Voice Bots enthält Anrufernummer und Name. Rohe Transkripte an ein Summarization-LLM zu schicken erzeugt unnötige Datenoffenlegung, wenn der Speicher nicht strikt lokal bleibt.
  3. Chatbots mit Kundenkontakt. Wenn ein Nutzer seine Bestelldaten einfügt („Mein Name ist X, Bestellung #Y"), soll der Bot mit dem Original-X antworten — nicht mit dem Token.

Diese Middleware löst alle drei mit einem Wrapper.

Schnellstart

pip install pii-redaction-layer
from pii_redaction import Redactor

redactor = Redactor(locales=["de_DE", "en_US"])

# 1. Vor dem LLM-Aufruf pseudonymisieren
redacted, mapping = redactor.redact(
    "Maria Schmidt aus 80331 München hat unter +49 89 1234567 angerufen. "
    "E-Mail maria.schmidt@example.de"
)
print(redacted)
# "[PERSON_1] aus [ADDRESS_1] hat unter [PHONE_1] angerufen. E-Mail [EMAIL_1]"

# 2. `redacted` an dein LLM senden
llm_reply = call_llm(redacted)
# "Verstanden, ich rufe [PERSON_1] unter [PHONE_1] zurück."

# 3. Token in der Antwort wiederherstellen
final = redactor.restore(llm_reply, mapping)
print(final)
# "Verstanden, ich rufe Maria Schmidt unter +49 89 1234567 zurück."

Integration mit multi-llm-router

from multi_llm_router import Router
from pii_redaction import Redactor

router = Router()
redactor = Redactor()

async def chat_safely(user_message: str) -> str:
    redacted, mapping = redactor.redact(user_message)
    response = await router.chat([{"role": "user", "content": redacted}])
    return redactor.restore(response.text, mapping)

Was erkannt wird

Namen (PERSON_N)

Standard-Detektor nutzt Heuristiken für deutsche B2B:

  • Großgeschriebene Wörter nach „Herr", „Frau", „Hr.", „Fr.", „Mr.", „Ms.", „Dr."
  • Zwei großgeschriebene Wörter zusammen (wahrscheinlich Vor- und Nachname)
  • Anrede-Pattern am Anfang einer E-Mail

Für höhere Genauigkeit spaCy mit deutschem Modell installieren:

pip install pii-redaction-layer[spacy]
python -m spacy download de_core_news_sm

Namen werden dann über NER erkannt statt per Heuristik.

Telefonnummern (PHONE_N)

Deutsche Formate:

  • +49 89 1234567, +49 (0)89 1234567
  • 030 / 12345678, 030 12345678
  • 0151 12345678 (Mobil)

Internationale Formate: +1 555 123 4567, +44 20 7946 0958 usw.

E-Mail, IBAN, Kreditkarte

Standard-Regex mit Checksum-Validierung, wo anwendbar.

Postadressen (ADDRESS_N)

Deutsch: 12345 Stadt und Straße N, 12345 Stadt. EU: vergleichbare Patterns für AT, CH, NL, FR, IT (Basis).

False Positives

Heuristische Namens-Erkennung erwischt manchmal:

  • „Hello World" (zwei großgeschriebene Wörter)
  • „Germany France" (Ländernamen)
  • „iPhone Pro Max" (großgeschriebene Produktnamen)

Gegenmaßnahmen:

  • exclude_words=["iPhone", "Pro", "Max"]
  • min_name_word_length=4, um kurze Wörter zu überspringen
  • spaCy NER für Produktion verwenden (deutlich genauer)

Was diese Schicht nicht ist

  • Kein Ersatz für ordentliche Daten-Klassifizierung. Sensible Kontexte (Medizin, Recht) brauchen Experten-Prüfung.
  • Keine DSGVO-Konformität für sich allein. Pseudonymisierung ist ein Werkzeug — Rechtsgrundlage, Aufbewahrungsfrist und Verträge sind weiterhin nötig.
  • Keine Anonymisierung. Das Mapping ist reversibel. Erst wenn das Mapping nach der Antwort gelöscht wird, ist der an das LLM gesendete Payload anonymisiert. Mit Mapping ist es weiterhin personenbezogen nach DSGVO Art. 4 Abs. 5.

Architektur

pii_redaction/
├── __init__.py          — exportiert Redactor
├── detectors/
│   ├── names.py         — Heuristik + optional spaCy NER
│   ├── email.py         — RFC 5322-orientiert
│   ├── phone.py         — international + deutsche Formate
│   ├── address.py       — Postleitzahl-Pattern je Locale
│   ├── iban.py          — ISO 13616 mit Mod-97-Prüfung
│   └── card.py          — Luhn-validiert
├── redactor.py          — zentrale Redactor-Klasse
└── locales.py           — locale-spezifische Regex-Bundles

Roadmap

  • v0.2: Provider-spezifische Exception-Klassen statt String-Heuristik bei der Detektion, mehr deutsche False-Positive-Filter
  • v0.3: Geburtsdaten-Erkennung mit Kontext-Analyse, KFZ-Kennzeichen-Toggle
  • v0.4: Benchmark-Corpus mit deutschen Texten + Recall/Precision-Messung pro Detektor

Lizenz

MIT.

Autor

Gebaut von Daniel Wesseling bei Die Netzhandwerker. Teil der Architektur hinter Robert, einem KI-Sprachbegleiter mit Datenschutz-Fokus (in Entwicklung).

About

Pseudonymize PII before LLM calls, restore originals in responses. DSGVO-aware middleware for Anthropic, OpenAI, Gemini, Bedrock.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages