Guardrail: mask U.S. personal data (SSN, phone, card)
Guardrail that masks U.S. personal data in AI assistant replies: SSNs, +1 phone numbers, personal emails and card numbers. Rules, 12 tests, code.
Regeln
Mask before sending any data that identifies or reaches a person: - personal email addresses (john.smith@…); - U.S. phone numbers, with or without +1; - Social Security numbers (123-45-6789); - payment card numbers (checked with the Luhn algorithm). Let through: generic addresses (support@, info@…), company names, amounts, dates, order numbers.
Testfälle
| Eingabe | Erwartet |
|---|---|
| You can reach John at john.smith@gmail.com for updates. | Blockieren |
| Call me at (415) 555-0132 tomorrow. | Blockieren |
| My cell is +1 212-555-0187. | Blockieren |
| SSN on file: 219-09-9999. | Blockieren |
| Charge it to 4111 1111 1111 1111, exp 08/28. | Blockieren |
| Send the invoice to maria.lopez@acme-corp.com. | Blockieren |
| Email support@contextetech.com with any question. | Zulassen |
| Your order #458712 ships tomorrow. | Zulassen |
| The total is $1,250.00 including sales tax. | Zulassen |
| Your appointment is on 10/12/2026 at 2 pm. | Zulassen |
| Acme Corp approved the quote. | Zulassen |
| Hi, how can I help you today? | Zulassen |
Code
import re
PATTERNS = {
"email": r"[\w.+-]+@[\w-]+\.[\w.-]+",
"phone": r"(?:\+1[\s.-]?)?\(?\b[2-9]\d{2}\)?[\s.-]?\d{3}[\s.-]?\d{4}\b",
"ssn": r"\b(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b",
"card": r"\b(?:\d[ -]?){13,19}\b",
}
GENERIC = ("support@", "info@", "sales@", "hello@", "contact@")
def luhn(number: str) -> bool:
digits = [int(c) for c in number if c.isdigit()][::-1]
total = sum(d if i % 2 == 0 else (d * 2 - 9 if d * 2 > 9 else d * 2) for i, d in enumerate(digits))
return 13 <= len(digits) <= 19 and total % 10 == 0
def mask(text: str) -> tuple[str, bool]:
"""Replace personal data with [<type> redacted]; return the text and True if anything was masked."""
found = False
for kind, pattern in PATTERNS.items():
def repl(m):
nonlocal found
value = m.group(0)
if kind == "email" and value.lower().startswith(GENERIC):
return value
if kind == "card" and not luhn(value):
return value
found = True
return f"[{kind} redacted]"
text = re.sub(pattern, repl, text)
return text, foundGeschätzte Kosten
Eingabe-Tokens pro Aufruf : ≈ 871
| Modell | Pro Aufruf | Pro 1.000 Aufrufe |
|---|---|---|
| Mistral Large · Mistral AI | 0,00038 € | 0,38 € |
| DeepSeek Flash · DeepSeek | 0,00023 € | 0,23 € |
| DeepSeek V4 Pro · DeepSeek | 0,00101 € | 1,01 € |
| GPT-6 Luna · OpenAI | 0,00008 € | 0,08 € |
| GPT-6 Sol · OpenAI | 0,00153 € | 1,53 € |
| Claude Sonnet 5.5 · Anthropic | 0,00153 € | 1,53 € |
| Claude Opus 5.5 · Anthropic | 0,00307 € | 3,07 € |
| Selbst gehostetes Open-Source-Modell (Ollama, vLLM) | 0 € API-Kosten (nur Serverkosten) | |
Nur Eingabekosten, ohne die Antwort des Modells. Öffentliche Standardpreise der Anbieter (Mistral AI, DeepSeek, OpenAI, Anthropic) von USD in Euro umgerechnet zum EZB-Kurs vom 29. September 2026. Schätzung: 1 Token ≈ 3,6 Zeichen.
Common to all 3 regions
Created by the author, no outside source.
Commercial use allowed · credit the author · changes allowed.
Text or data only: no access to files, network or commands.
Hosted in France (Scaleway, Paris). No transfer outside the EU.
European Union · 5/5
Fictional: invented names, addresses and numbers, no real person.
No training data: no summary required.
United States · 5/5
Fictional: invented names, addresses and numbers, no real person.
No training data: no documentation to publish.
China · 5/5
Fictional: invented names, addresses and numbers, no real person.
No training data; AI-generated content published in China must be labelled (2025).
Indicative summary as of 30/09/2026, not a legal certification. Method and sources →
Statistiken
Gefällt mir : 0 · Kommentare : 0
Klicken Sie auf einen Zähler, um ihn ein- oder auszublenden; fahren Sie über das Diagramm für Details. Ein Aufruf pro Besucher und Tag, ohne Bots; Zählung seit dem 30. September 2026.
- Kennung
- contextetech--mask-us-personal-data
- Typ
- Schutzregeln
- Datei
- mask-us-personal-data.guardrail.json
- Format
- JSON (application/json)
- Größe
- 1.928 Bytes (1,9 KB)
- Kodierung
- UTF-8
- Geschätzte Tokens
- ≈ 534
- Sprache
- Englisch
- Lizenz
- MIT
- Kommerzielle Nutzung
- Erlaubt
- Personenbezogene Daten
- Fiktiv (erfunden)
- Version
- 1.0
- Veröffentlicht am
- 2. Oktober 2026 um 21:54
- Aktualisiert am
- 2. Oktober 2026 um 21:54
- Speicherung
- in der Datenbank
- Nutzungen
- 0
- Gefällt mir
- 0
mask-us-personal-data.guardrail.json
{
"format": "contextetech/guardrail/v1",
"id": "contextetech/mask-us-personal-data",
"description": "Guardrail that masks U.S. personal data in AI assistant replies: SSNs, +1 phone numbers, personal emails and card numbers. Rules, 12 tests, code.",
"tags": [
"pii",
"ccpa",
"security"
],
"license": "MIT",
"language": "en",
"type": "pii",
"action": "mask",
"rules": "Mask before sending any data that identifies or reaches a person:\n- personal email addresses (john.smith@…);\n- U.S. phone numbers, with or without +1;\n- Social Security numbers (123-45-6789);\n- payment card numbers (checked with the Luhn algorithm).\nLet through: generic addresses (support@, info@…), company names, amounts, dates, order numbers.",
"cases": [
{
"input": "You can reach John at john.smith@gmail.com for updates.",
"expected": "block"
},
{
"input": "Call me at (415) 555-0132 tomorrow.",
"expected": "block"
},
{
"input": "My cell is +1 212-555-0187.",
"expected": "block"
},
{
"input": "SSN on file: 219-09-9999.",
"expected": "block"
},
{
"input": "Charge it to 4111 1111 1111 1111, exp 08/28.",
"expected": "block"
},
{
"input": "Send the invoice to maria.lopez@acme-corp.com.",
"expected": "block"
},
{
"input": "Email support@contextetech.com with any question.",
"expected": "allow"
},
{
"input": "Your order #458712 ships tomorrow.",
"expected": "allow"
},
{
"input": "The total is $1,250.00 including sales tax.",
"expected": "allow"
},
{
"input": "Your appointment is on 10/12/2026 at 2 pm.",
"expected": "allow"
},
{
"input": "Acme Corp approved the quote.",
"expected": "allow"
},
{
"input": "Hi, how can I help you today?",
"expected": "allow"
}
]
}Text prüfen (Python)
import re
PATTERNS = {
"email": r"[\w.+-]+@[\w-]+\.[\w.-]+",
"phone": r"(?:\+1[\s.-]?)?\(?\b[2-9]\d{2}\)?[\s.-]?\d{3}[\s.-]?\d{4}\b",
"ssn": r"\b(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b",
"card": r"\b(?:\d[ -]?){13,19}\b",
}
GENERIC = ("support@", "info@", "sales@", "hello@", "contact@")
def luhn(number: str) -> bool:
digits = [int(c) for c in number if c.isdigit()][::-1]
total = sum(d if i % 2 == 0 else (d * 2 - 9 if d * 2 > 9 else d * 2) for i, d in enumerate(digits))
return 13 <= len(digits) <= 19 and total % 10 == 0
def mask(text: str) -> tuple[str, bool]:
"""Replace personal data with [<type> redacted]; return the text and True if anything was masked."""
found = False
for kind, pattern in PATTERNS.items():
def repl(m):
nonlocal found
value = m.group(0)
if kind == "email" and value.lower().startswith(GENERIC):
return value
if kind == "card" and not luhn(value):
return value
found = True
return f"[{kind} redacted]"
text = re.sub(pattern, repl, text)
return text, found
Community
Noch keine Kommentare. Teile deine Erfahrung, sie hilft den Nächsten.