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.
Regole
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.
Casi di test
| Input | Atteso |
|---|---|
| You can reach John at john.smith@gmail.com for updates. | Bloccare |
| Call me at (415) 555-0132 tomorrow. | Bloccare |
| My cell is +1 212-555-0187. | Bloccare |
| SSN on file: 219-09-9999. | Bloccare |
| Charge it to 4111 1111 1111 1111, exp 08/28. | Bloccare |
| Send the invoice to maria.lopez@acme-corp.com. | Bloccare |
| Email support@contextetech.com with any question. | Consentire |
| Your order #458712 ships tomorrow. | Consentire |
| The total is $1,250.00 including sales tax. | Consentire |
| Your appointment is on 10/12/2026 at 2 pm. | Consentire |
| Acme Corp approved the quote. | Consentire |
| Hi, how can I help you today? | Consentire |
Codice
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, foundCosto stimato
Token di input per chiamata : ≈ 871
| Modello | Per chiamata | Per 1.000 chiamate |
|---|---|---|
| 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 € |
| Modello open source self-hosted (Ollama, vLLM) | 0 € di API (solo costo del server) | |
Solo costo di input, esclusa la risposta del modello. Prezzi pubblici standard dei fornitori (Mistral AI, DeepSeek, OpenAI, Anthropic) convertiti da USD in euro al cambio BCE del 29 settembre 2026. Stima: 1 token ≈ 3,6 caratteri.
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 →
Statistiche
Mi piace : 0 · Commenti : 0
Clicca su un contatore per mostrarlo o nasconderlo; passa sul grafico per i dettagli. Una visualizzazione per visitatore al giorno, bot esclusi; conteggio iniziato il 30 settembre 2026.
- Identificativo
- contextetech--mask-us-personal-data
- Tipo
- Salvaguardie
- File
- mask-us-personal-data.guardrail.json
- Formato
- JSON (application/json)
- Dimensione
- 1928 byte (1,9 KB)
- Codifica
- UTF-8
- Token stimati
- ≈ 534
- Lingua
- Inglese
- Licenza
- MIT
- Uso commerciale
- Consentito
- Dati personali
- Fittizi (inventati)
- Versione
- 1.0
- Pubblicato il
- 2 ottobre 2026 alle ore 21:54
- Aggiornato il
- 2 ottobre 2026 alle ore 21:54
- Archiviazione
- nel database
- Utilizzi
- 0
- Mi piace
- 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"
}
]
}Verificare un testo (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
Ancora nessun commento. Condividi la tua esperienza, aiuterà chi viene dopo.