Redact Turkish Personal Data Before It Reaches an LLM
Short answer: To keep Turkish personal data out of LLM prompts, run a masking step before every API call. Catch structured identifiers (TCKN, IBAN, phone, email) with patterns plus checksums, then use a Turkish PII model for names and addresses. Replace each rule hit with a typed placeholder like [IBAN_1], and keep the mapping only on your side.
Why Turkish text needs its own PII step
Most off-the-shelf PII tools are built around English and US identifiers: SSNs, US phone formats, ZIP codes. Turkish text breaks them in predictable ways. National ID numbers (TCKN) are 11 digits with their own checksum, IBANs start with TR and have 26 characters, phone numbers appear as 0532 …, +90 532 … or 532…, and Turkish names take suffixes (Ayşe'nin, Mehmet'e) that English-centric tools often miss.
If your product serves Turkish users, such as a support chatbot, a document summarizer or an internal assistant, you need a redaction step that understands these formats before any text reaches a third-party model.
What counts as personal data under KVKK
Turkey's Personal Data Protection Law (KVKK) defines personal data broadly: any information relating to an identified or identifiable person. Sending prompts to an LLM hosted abroad can also raise cross-border transfer questions. Masking does not replace a legal basis, but it supports data minimization: the model only receives what the task actually needs. Our personal data guide covers what to mask and why. This article is engineering guidance, not legal advice.
The five entities to mask first
| Entity | Example shape | Best detector |
|---|---|---|
| TCKN | 11 digits, first ≠ 0, two check digits | Regex + checksum |
| IBAN | TR + 24 digits, often spaced in groups of 4 | Regex + mod-97 |
| Phone | +90 5xx xxx xx xx, 05xx… | Regex with normalization |
name@domain.tld | Regex | |
| Person name | Ayşe Demir, Demir Bey'e | PII model |
The first four are structured, so rules catch them reliably and explainably. Names are not: there is no pattern that separates "Gül" the person from "gül" the flower. That is where a model earns its place.
A three-layer pipeline: rules, checksums, model
Layer 1: patterns. Use broad regexes to collect candidates. Normalize whitespace and the +90/0 phone prefixes first.
Layer 2: checksums. Reject candidates that fail validation. TCKN: the 10th digit is ((d1+d3+d5+d7+d9)*7 − (d2+d4+d6+d8)) mod 10, and the 11th is the sum of the first ten digits mod 10. IBAN: move the first four characters to the end, convert letters to numbers (A=10 … Z=35), and check that the result mod 97 equals 1. This step is what keeps order numbers and tracking codes from being masked by mistake.
Layer 3: model. Run a Turkish PII model over the text the rules have already masked, to catch names and identifiers the rules missed (OCR noise, unusual spacing). Our Turkish PII detection model takes the text plus a short Turkish instruction saying what to mask and returns the rewritten text with labels such as [AD], [TCKN] and [TEL]. It is not a NER model: it does not return entity spans or offsets, so what it masks cannot be mapped back. Use the rule layers for anything you need to restore.
def redact(text: str):
mapping, counters = {}, {}
spans = merge_overlaps(sorted(rule_spans(text))) # (start, end, label)
out, last = [], 0
for start, end, label in spans:
counters[label] = counters.get(label, 0) + 1
tag = f"[{label}_{counters[label]}]"
mapping[tag] = text[start:end]
out += [text[last:start], tag]
last = end
out.append(text[last:])
masked = mask_with_model("".join(out)) # names, missed identifiers
return masked, mapping # send the text, keep the mapping
Check in your tests that the model leaves your own placeholders untouched.
Reversible vs. irreversible masking
- Irreversible (
[TCKN]): best for analytics, logging and anything you store. - Reversible (
[TCKN_1]+ local mapping): best for assistants that must answer the user, such as "I've sent the refund to [IBAN_1]". After the LLM responds, swap placeholders back. Keep the mapping in memory or in a store separate from the prompt logs, and expire it with the session.
Never put the mapping into the prompt, and never log the raw text "just for debugging". That log is exactly the leak you were trying to prevent.
Running it locally
If the text must not leave your infrastructure at all, run detection on your own hardware. The model is also published as a GGUF build (Q8_0 and BF16) for Ollama and llama.cpp, which runs on CPU. For example, with Ollama:
ollama pull hf.co/halilneed/turkish-pii-detection-GGUF:Q8_0
The GGUF page has the Modelfile, the prompt format and the llama.cpp command. Not sure which of our models you need? The model map explains which one fits which job.
Testing your redaction
- Build a synthetic test set: generate valid TCKNs and IBANs with your own checksum code. Do not use real people's data.
- Include hard cases: spaced IBANs,
+90and0phone prefixes, names with suffixes, ID numbers inside longer digit strings. - Measure two things separately: missed PII (leaks) and over-masking (broken text). For privacy, leaks matter more; for product quality, over-masking does.
- Re-run the suite whenever you change patterns or update the model.
Masking also pairs well with input screening. If your assistant reads documents or web pages, check them for instructions aimed at the model; the Turkish prompt injection classifier covers that step. For written guidance on what to send, mask or keep in-house, the full Personal data and AI guide (PDF) is on Gumroad.
You can try masking without writing code in the masking tool.
FAQ
Is regex enough for Turkish PII?
For TCKN, IBAN, phone and email, regex plus checksums works well. For names and addresses, you need a model.
Does masking make LLM use KVKK-compliant?
Not by itself. It supports data minimization, but you still need a legal basis and a usage policy. This is not legal advice.
Will the LLM's answer quality drop?
Typed placeholders keep the sentence structure, and numbered tags let the model refer to “customer 1” and “IBAN 1” consistently. Test it on your own tasks.
Can I run this in the browser?
The rule layers, yes. The model layer runs best on a server or locally via the GGUF build.