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.
Règles
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.
Cas de test
| Entrée | Attendu |
|---|---|
| You can reach John at john.smith@gmail.com for updates. | Bloquer |
| Call me at (415) 555-0132 tomorrow. | Bloquer |
| My cell is +1 212-555-0187. | Bloquer |
| SSN on file: 219-09-9999. | Bloquer |
| Charge it to 4111 1111 1111 1111, exp 08/28. | Bloquer |
| Send the invoice to maria.lopez@acme-corp.com. | Bloquer |
| Email support@contextetech.com with any question. | Laisser passer |
| Your order #458712 ships tomorrow. | Laisser passer |
| The total is $1,250.00 including sales tax. | Laisser passer |
| Your appointment is on 10/12/2026 at 2 pm. | Laisser passer |
| Acme Corp approved the quote. | Laisser passer |
| Hi, how can I help you today? | Laisser passer |
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, foundCoût estimé
Tokens d'entrée par appel : ≈ 871
| Modèle | Par appel | Pour 1 000 appels |
|---|---|---|
| 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 € |
| Modèle open source hébergé chez soi (Ollama, vLLM) | 0 € d'API (coût du serveur seulement) | |
Coût d'entrée seulement, sans la réponse du modèle. Prix publics des fournisseurs (Mistral AI, DeepSeek, OpenAI, Anthropic), tarif standard en dollars converti en euros au taux BCE du 29 septembre 2026. Estimation : 1 token ≈ 3,6 caractères.
Commun aux 3 régions
Contenu créé par l'auteur, sans source extérieure.
Usage commercial autorisé · citer l'auteur · modifications permises.
Texte ou données seuls : aucun accès aux fichiers, au réseau ni aux commandes.
Hébergé en France (Scaleway, Paris). Aucun transfert hors UE.
Union européenne · 5/5
Fictives : noms, adresses et numéros inventés, aucune personne réelle.
Pas de données d'entraînement : pas de résumé à fournir.
États-Unis · 5/5
Fictives : noms, adresses et numéros inventés, aucune personne réelle.
Pas de données d'entraînement : pas de documentation à publier.
Chine · 5/5
Fictives : noms, adresses et numéros inventés, aucune personne réelle.
Pas de données d'entraînement ; les contenus générés par IA diffusés en Chine doivent être marqués (2025).
Synthèse indicative au 30/09/2026, sans valeur de certification juridique. Méthode et sources →
Statistiques
J'aime : 0 · Commentaires : 0
Cliquez sur un compteur pour l’afficher ou le masquer, survolez le graphique pour le détail. Une vue par visiteur et par jour, robots exclus ; décompte commencé le 30 septembre 2026.
- Identifiant
- contextetech--mask-us-personal-data
- Type
- Garde-fous
- Fichier
- mask-us-personal-data.guardrail.json
- Format
- JSON (application/json)
- Taille
- 1 928 octets (1,9 Ko)
- Encodage
- UTF-8
- Tokens estimés
- ≈ 534
- Langue
- Anglais
- Licence
- MIT
- Usage commercial
- Autorisé
- Données personnelles
- Fictives (inventées)
- Version
- 1.0
- Publié le
- 2 octobre 2026 à 21:54
- Mis à jour le
- 2 octobre 2026 à 21:54
- Stockage
- en base
- Utilisations
- 0
- J'aime
- 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"
}
]
}Vérifier un texte (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
Communauté
Pas encore de commentaire. Partagez votre retour, il aidera les suivants.