PII detection
The personal-data entity types CID222 detects, how regex and the NER model divide the work, and how a detection becomes a placeholder.
- Version: 0.4
- Role: admin_user, normal_user, viewer
CID222 detects personal data in a prompt before it reaches a provider, and in a reply before it reaches the user. Two layers do the work: deterministic patterns with checksums, and a multilingual ONNX NER model.
The problem
Personal data does not arrive labelled. It arrives inside a paragraph a person typed, in a language you did not choose, sometimes as a scan of a document. A pattern catches what has a shape — a card number, an IBAN, a national ID — and misses a name it has never seen. A model catches the name and cannot prove the card number is real. Running only one of them means either leaking or blocking the work; running both means reconciling two opinions about the same fifteen characters.
How CID222 does it
Two layers, one catalogue
The regex layer loads its patterns from the input-filter rows in the database, so the catalogue is data an administrator can extend rather than code. The NER layer is a single multilingual ONNX DistilBERT model that emits 21 native labels. Both layers report against the same entity type where the concept overlaps, so detections of the same span merge instead of double-counting.
What is detected
Every row below is a shipped filter. Default action is what the seed sets; an administrator changes it per rule, and a per-tenant or per-group override can change it for one department without touching the rule.
Personal identifiers
| Entity | Description | Detected by | Default action |
|---|---|---|---|
PERSON_NAME | Person names | NER, plus one regex for uppercase names on Turkish ID cards | mask |
EMAIL | Email addresses | Regex, NER | mask |
PHONE | Phone numbers — US, international, Turkish mobile and landline, six patterns | Regex, NER | mask |
SSN | US Social Security number | Regex with the us.ssn checksum, NER | mask |
TC_KIMLIK, ID_NUMBER | Turkish national ID, including OCR-tolerant and context-anchored forms | Regex with the tr.tckimlik checksum, NER | mask |
ID_DOCUMENT_NO | Turkish ID card document and serial number | Regex | mask |
PASSPORT | Passport numbers — US, generic EU, Turkish | Regex, NER | mask |
DATE_TIME | Dates and times | NER only | flag |
Financial data
| Entity | Description | Detected by | Default action |
|---|---|---|---|
CREDIT_CARD | Card numbers, plain, spaced and dotted | Regex with Luhn plus brand and length validation, NER | mask |
IBAN, ACCOUNT_NUMBER | IBAN, Turkish IBAN and Turkish domestic account numbers | Regex with ISO 13616 mod-97, NER | mask |
VKN, TAX_ID | Turkish tax number | Regex with the tr.vkn checksum | mask |
CRYPTO_ADDRESS | Crypto wallet addresses | NER only | mask |
Location and network data
| Entity | Description | Detected by | Default action |
|---|---|---|---|
LOCATION, STREET_ADDRESS, CITY, ZIPCODE | Addresses, cities, regions, postal codes | NER, plus a Turkish postal-code regex with a province-range check | flag |
IP_ADDRESS | IPv4 and IPv6 | Regex, NER | mask |
LICENSE_PLATE | Turkish vehicle plates, province-range checked | Regex, NER | mask |
URL | URLs | NER only | flag |
ORGANIZATION | Company and institution names | NER only | flag |
mac_colon, mac_hyphen | MAC addresses | Regex only | mask |
Health data
| Entity | Description | Detected by | Default action |
|---|---|---|---|
mrn_labeled, mrn_format | Medical record numbers, labelled and bare formats | Regex only | mask |
Warning
Medical record numbers and MAC addresses are regex-only rules with no model label, so their
type in an API response is the pattern name shown above, not a tidy MRN or
MAC_ADDRESS. Their value still carries the placeholder — [MRN], [MAC_ADDRESS]. Match on
the placeholder if you need a stable category. There is no broader health-data detector: CID222
does not classify conditions, medications or diagnoses.
Note
Beyond personal data, a separate DLP rule set detects leaked secrets — API keys, passwords, private keys, database connection strings, OAuth and refresh tokens, webhook secrets, CVE identifiers. The full catalogue is on the content detection page.
Validation before trust
A detection is not acted on until it survives validation. Rules come in eight categories —
length, boundary, context, confidence, pattern, content, checksum and intent_gate —
and are attached to filter rows as data.
The checksum category is the strongest of them and returns a three-state verdict:
| Verdict | Meaning | Effect |
|---|---|---|
valid | Right shape and the check passed | The detection stands |
invalid | Right shape, check failed | Dropped, or demoted from mask to flag where the rule says so |
unknown | Not this validator's shape at all | Treated as if no validator ran |
That third state matters more than it looks. A national-ID field also carries document numbers such as B1234567, and a bank-account field also carries bare account numbers. A digit-only checksum cannot read either, and failing open is what stops it from silently discarding them.
A validated entity passes only if its final confidence reaches 0.6. A detection is boosted by
0.1, clamped at 1.0, when a context keyword registered for its entity type appears within 50
characters of the span — the email rule registers contact and communication, for example.
The keyword-to-type map is loaded from the active filter rows.
Masking
A masked entity is replaced by a placeholder, and the placeholder comes from one of two places.
- A regex rule uses its own
replacement_text.[EMAIL],[PHONE],[SSN],[CREDIT_CARD],[IBAN],[NATIONAL_ID],[DOCUMENT_NO],[PASSPORT],[LICENSE_PLATE],[POSTAL_CODE],[BANK_ACCOUNT],[TAX_ID],[NAME],[IP_ADDRESS],[MRN]. - A model detection with no rule-supplied action gets a generated placeholder: the entity type
with underscores as spaces, four asterisks appended.
PERSON_NAMEbecomes[PERSON NAME****].
Neither of those is what the provider finally sees. The masking step takes that base label and
numbers it per distinct value, so the text that leaves the gateway carries [EMAIL_1],
[EMAIL_2], [PERSON_NAME_1]. The number is stable for the whole conversation: one address stays
[EMAIL_1] from turn one to turn ten. See
pseudonymization and masking.
The masked text is what the provider receives. Where reversible masking is configured, the mapping back to the original is encrypted per session with AES-256-GCM; without the key, responses stay masked and no mapping is written at all.
Adding your own patterns
An organisation-specific identifier is a new filter row with one or more regex patterns. The shape is the same as the shipped rules:
ml_entity_types[0] becomes the type reported to callers. context_types are the keywords that
earn the confidence boost. Test the pattern with the dashboard's filter-testing tool before you
activate it.
Limits and known gaps
- Accuracy is conditional and is not one number. It varies with entity type, language, text length and filter configuration. Structured identifiers with a checksum behave close to deterministically; names, locations and organisations depend on the NER model and its context. Quote the qualified figures in performance and accuracy, never a bare percentage.
- Detection is CID222's own models and rules. There is no Presidio, no spaCy, and no OPA/Rego policy engine anywhere in the path. The catalogue is filter rows in PostgreSQL and one ONNX NER model.
- Free-text entities are flagged, not masked, by default.
LOCATION,ORGANIZATION,DATE_TIMEandURLship as flag rules, because masking them at NER precision breaks ordinary prompts. If you need them masked, change the action deliberately and measure the false positives you accept. - Turkish coverage is deeper than any other national coverage. Turkish national ID, tax number, IBAN, domestic account, postal code, licence plate and ID-card document number all have dedicated patterns and validators. Another country's identifiers have none unless you add them.
- A broken pattern protects nothing, silently. A stored regex that fails to compile is skipped at load time. The gateway logs it loudly and marks the filter broken in the dashboard, but the filter still reads as active.
- The pattern cache is up to five minutes stale. The regex layer refreshes its compiled patterns on a five-minute TTL, so a new or edited rule is not necessarily live on the next request.
- No health-data classifier exists. Medical record numbers are matched by two regex patterns. Conditions, medications and diagnoses in free text are not detected as health data.
- PII inside a fragmented upload is not inspected. Nothing buffers a body across requests, so chunked and resumable uploads are refused rather than scanned.
Related
- The content safety pipeline — where PII detection sits and what decides the action.
- Content detection API — call the detector directly and read its response.
- Document analysis — the same catalogue applied to files.
Last updated on