GDPR-skyddsregel: maskera personnummer, telefon och IBAN
GDPR-skyddsregel som maskerar personnummer (med Luhn-kontroll), telefonnummer +46, IBAN SE och privata e-postadresser i en AI-assistents svar. 12 tester.
Regler
Maskera före varje utskick alla uppgifter som identifierar en person eller gör det möjligt att kontakta hen: - privata e-postadresser (anna.lindgren@…); - svenska telefonnummer, med eller utan +46; - svenska IBAN-nummer (SE..); - personnummer (kontrollsiffran kontrolleras). Tillåt: allmänna adresser (info@, kundtjanst@, kontakt@…), företagsnamn, belopp, datum, ordernummer.
Testfall
| Indata | Förväntat |
|---|---|
| Du kan mejla anna.lindgren@gmail.com för uppdateringar. | Blockera |
| Mitt mobilnummer är 070-174 06 05. | Blockera |
| Ring mig på +46 8 465 004 12 i morgon. | Blockera |
| Överför till SE45 5000 0000 0583 9825 7466. | Blockera |
| Personnummer: 851103-1232. | Blockera |
| Fakturan ska skickas till erik.berg@foretag.se. | Blockera |
| Mejla kundtjanst@contextetech.com om du har frågor. | Tillåt |
| Din order nr 458712 skickas i morgon. | Tillåt |
| Beloppet är 1 250,00 kr inklusive moms. | Tillåt |
| Möte den 2026-10-12 klockan 14. | Tillåt |
| Byggföretaget Nordbygg AB har godkänt offerten. | Tillåt |
| Hej, hur kan jag hjälpa dig? | Tillåt |
Kod
import re
MONSTER = {
"epost": r"[\w.+-]+@[\w-]+\.[\w.-]+",
"telefon": r"(?:\+46[\s.-]?|\b0)(?:7[02369][\s.-]?\d{3}[\s.-]?\d{2}[\s.-]?\d{2}|8[\s.-]?\d{3}[\s.-]?\d{3}[\s.-]?\d{2}|[1-69]\d[\s.-]?\d{3}[\s.-]?\d{2}[\s.-]?\d{2})\b",
"iban": r"\bSE\d{2}(?:\s?\d{4}){5}\b",
"personnummer": r"\b(?:\d{2})?\d{6}[-+]\d{4}\b",
}
ALLMANNA = ("info@", "kundtjanst@", "support@", "kontakt@")
def personnummer_giltigt(pnr: str) -> bool:
"""Sista siffran är en kontrollsiffra (Luhn) beräknad på de nio föregående (ÅÅMMDDNNN)."""
siffror = re.sub(r"\D", "", pnr)[-10:]
summa = 0
for i, s in enumerate(siffror[:9]):
d = int(s) * (2 if i % 2 == 0 else 1)
summa += d - 9 if d > 9 else d
return (10 - summa % 10) % 10 == int(siffror[9])
def maskera(text: str) -> tuple[str, bool]:
"""Ersätter personuppgifter med [<typ> maskerad]; returnerar texten och True om något maskerades."""
hittad = False
for typ, monster in MONSTER.items():
def ersatt(m):
nonlocal hittad
varde = m.group(0)
if typ == "epost" and varde.lower().startswith(ALLMANNA):
return varde
if typ == "personnummer" and not personnummer_giltigt(varde):
return varde
hittad = True
return f"[{typ} maskerad]"
text = re.sub(monster, ersatt, text)
return text, hittadUppskattad kostnad
Indatatokens per anrop : ≈ 958
| Modell | Per anrop | Per 1 000 anrop |
|---|---|---|
| Mistral Large · Mistral AI | 0,00042 € | 0,42 € |
| DeepSeek Flash · DeepSeek | 0,00025 € | 0,25 € |
| DeepSeek V4 Pro · DeepSeek | 0,00111 € | 1,11 € |
| GPT-6 Luna · OpenAI | 0,00008 € | 0,08 € |
| GPT-6 Sol · OpenAI | 0,00169 € | 1,69 € |
| Claude Sonnet 5.5 · Anthropic | 0,00169 € | 1,69 € |
| Claude Opus 5.5 · Anthropic | 0,00338 € | 3,37 € |
| Egendriftad öppen källkodsmodell (Ollama, vLLM) | 0 € i API-avgifter (endast serverkostnad) | |
Endast kostnaden för indata, utan modellens svar. Leverantörernas offentliga standardpriser (Mistral AI, DeepSeek, OpenAI, Anthropic) omräknade från USD till euro enligt ECB:s kurs den 29 september 2026. Uppskattning: 1 token ≈ 3,6 tecken.
Gemensamt för de 3 regionerna
Skapad av författaren, utan extern källa.
Kommersiell användning tillåten · ange upphovsperson · ändringar tillåtna.
Endast text eller data: ingen åtkomst till filer, nätverk eller kommandon.
Driftas i Frankrike (Scaleway, Paris). Ingen överföring utanför EU.
Europeiska unionen · 5/5
Fiktiva: påhittade namn, adresser och nummer, inga verkliga personer.
Inga träningsdata: ingen sammanfattning krävs.
USA · 5/5
Fiktiva: påhittade namn, adresser och nummer, inga verkliga personer.
Inga träningsdata: ingen dokumentation att publicera.
Kina · 5/5
Fiktiva: påhittade namn, adresser och nummer, inga verkliga personer.
Inga träningsdata; AI-genererat innehåll som sprids i Kina måste märkas (2025).
Vägledande sammanfattning per 30/09/2026, utan värde som juridisk certifiering. Metod och källor →
Statistik
Gillamarkeringar : 0 · Kommentarer : 0
Klicka på en räknare för att visa eller dölja den, för muspekaren över diagrammet för detaljer. En visning per besökare och dag, robotar undantagna; räkningen startade den 30 september 2026.
- Identifierare
- contextetech--maskera-personuppgifter-gdpr
- Typ
- Skyddsregler
- Fil
- maskera-personuppgifter-gdpr.guardrail.json
- Format
- JSON (application/json)
- Storlek
- 2 019 byte (2 KB)
- Teckenkodning
- UTF-8
- Uppskattade tokens
- ≈ 555
- Språk
- Svenska
- Licens
- MIT
- Kommersiell användning
- Tillåten
- Personuppgifter
- Fiktiva (påhittade)
- Version
- 1.0
- Publicerad
- 8 oktober 2026 kl. 19:48
- Uppdaterad
- 8 oktober 2026 kl. 19:48
- Lagring
- i databasen
- Användningar
- 0
- Gillamarkeringar
- 0
maskera-personuppgifter-gdpr.guardrail.json
{
"format": "contextetech/guardrail/v1",
"id": "contextetech/maskera-personuppgifter-gdpr",
"description": "GDPR-skyddsregel som maskerar personnummer (med Luhn-kontroll), telefonnummer +46, IBAN SE och privata e-postadresser i en AI-assistents svar. 12 tester.",
"tags": [
"gdpr",
"personuppgifter",
"sakerhet"
],
"license": "MIT",
"language": "sv",
"type": "pii",
"action": "mask",
"rules": "Maskera före varje utskick alla uppgifter som identifierar en person eller gör det möjligt att kontakta hen:\n- privata e-postadresser (anna.lindgren@…);\n- svenska telefonnummer, med eller utan +46;\n- svenska IBAN-nummer (SE..);\n- personnummer (kontrollsiffran kontrolleras).\nTillåt: allmänna adresser (info@, kundtjanst@, kontakt@…), företagsnamn, belopp, datum, ordernummer.",
"cases": [
{
"input": "Du kan mejla anna.lindgren@gmail.com för uppdateringar.",
"expected": "block"
},
{
"input": "Mitt mobilnummer är 070-174 06 05.",
"expected": "block"
},
{
"input": "Ring mig på +46 8 465 004 12 i morgon.",
"expected": "block"
},
{
"input": "Överför till SE45 5000 0000 0583 9825 7466.",
"expected": "block"
},
{
"input": "Personnummer: 851103-1232.",
"expected": "block"
},
{
"input": "Fakturan ska skickas till erik.berg@foretag.se.",
"expected": "block"
},
{
"input": "Mejla kundtjanst@contextetech.com om du har frågor.",
"expected": "allow"
},
{
"input": "Din order nr 458712 skickas i morgon.",
"expected": "allow"
},
{
"input": "Beloppet är 1 250,00 kr inklusive moms.",
"expected": "allow"
},
{
"input": "Möte den 2026-10-12 klockan 14.",
"expected": "allow"
},
{
"input": "Byggföretaget Nordbygg AB har godkänt offerten.",
"expected": "allow"
},
{
"input": "Hej, hur kan jag hjälpa dig?",
"expected": "allow"
}
]
}Kontrollera en text (Python)
import re
MONSTER = {
"epost": r"[\w.+-]+@[\w-]+\.[\w.-]+",
"telefon": r"(?:\+46[\s.-]?|\b0)(?:7[02369][\s.-]?\d{3}[\s.-]?\d{2}[\s.-]?\d{2}|8[\s.-]?\d{3}[\s.-]?\d{3}[\s.-]?\d{2}|[1-69]\d[\s.-]?\d{3}[\s.-]?\d{2}[\s.-]?\d{2})\b",
"iban": r"\bSE\d{2}(?:\s?\d{4}){5}\b",
"personnummer": r"\b(?:\d{2})?\d{6}[-+]\d{4}\b",
}
ALLMANNA = ("info@", "kundtjanst@", "support@", "kontakt@")
def personnummer_giltigt(pnr: str) -> bool:
"""Sista siffran är en kontrollsiffra (Luhn) beräknad på de nio föregående (ÅÅMMDDNNN)."""
siffror = re.sub(r"\D", "", pnr)[-10:]
summa = 0
for i, s in enumerate(siffror[:9]):
d = int(s) * (2 if i % 2 == 0 else 1)
summa += d - 9 if d > 9 else d
return (10 - summa % 10) % 10 == int(siffror[9])
def maskera(text: str) -> tuple[str, bool]:
"""Ersätter personuppgifter med [<typ> maskerad]; returnerar texten och True om något maskerades."""
hittad = False
for typ, monster in MONSTER.items():
def ersatt(m):
nonlocal hittad
varde = m.group(0)
if typ == "epost" and varde.lower().startswith(ALLMANNA):
return varde
if typ == "personnummer" and not personnummer_giltigt(varde):
return varde
hittad = True
return f"[{typ} maskerad]"
text = re.sub(monster, ersatt, text)
return text, hittad
Gemenskap
Inga kommentarer ännu. Dela din erfarenhet, det hjälper nästa person.