wardcat¶
PII detection and anonymization for LLM inputs — a hybrid engine that scans text for personally identifiable information (PII) before it reaches an LLM, and warns about, hashes, redacts, masks, tokenizes or swaps the sensitive data for a realistic stand-in.
Three detection layers cooperate behind one interface:
| Layer | What it does | Cost |
|---|---|---|
| regex | Deterministic structural PII — email, card (Luhn), IBAN (mod-97), TC ID, secrets… | free, exhaustive |
| ner | SpaCy Named Entity Recognition — names, orgs, locations | fast, per-language model |
| llm | On-prem LLM — contextual/semantic PII (GDPR Art.9, contextual secrets) | slow, strongest context |
Detection is opt-in: a bare Wardcat() detects nothing — you configure the
layers and entities you want. The full picture — all three layers, a per-entity
action policy, the semantic guardrail, and anonymization:
import os
from wardcat import Wardcat, Backend, Entity, Action
# One guard, all three detection layers — regex + SpaCy NER + on-prem LLM.
guard = (
Wardcat(salt=os.environ["WARDCAT_SALT"]) # your app supplies the salt
.with_ner(language="tr") # names / orgs (needs wardcat[ner])
.with_llm(backend=Backend.OLLAMA, model="gemma3:12b") # contextual + semantic (needs Ollama)
# An action per entity: hash IDs, mask contact details, redact names, flag IPs.
.add_entities([Entity.CREDIT_CARD, Entity.IBAN, Entity.TC_ID], action=Action.HASH)
.add_entities([Entity.EMAIL, Entity.PHONE], action=Action.MASK)
.add_entity(Entity.PERSON, Action.REDACT)
.add_entity(Entity.IP_ADDRESS, Action.WARN)
)
text = "Ben Ahmet Yılmaz, kartım 4111 1111 1111 1111, e-posta ahmet@example.com."
# 1) Semantic guardrail: does the text contain sensitive info at all? (holistic LLM yes/no)
if guard.is_sensitive(text):
# 2) Anonymize the PII before the text is stored, logged, or forwarded.
result = guard.scan(text)
print(result.sanitized_text)
# Ben [PERSON], kartım [CREDIT_CARD:b22b36262d8d2769], e-posta a****@example.com.
print(result.redacted()) # PII-free dict, safe for logs / APIs
The NER and LLM layers are optional
Regex-only detection needs no models — just
Wardcat(salt=...).add_entity(Entity.CREDIT_CARD, Action.HASH) and .scan(...).
Add with_ner(...) / with_llm(...) when you want names or contextual/semantic
detection.
Highlights¶
- Hybrid detection across three cooperating layers, merged with a confidence-first overlap resolver (a deterministic regex span always wins).
- Checksum validation — TC_ID, IBAN, CREDIT_CARD, Bitcoin wallet addresses, NHS numbers, ABA routing numbers, IMEI and the EU national-ID schemes it knows (DNI/NIE, INSEE, BSN, PESEL) are mathematically verified before flagging, so a match is proof rather than a guess (Detection layers).
- A confidence floor — every detection is tiered by how strong the evidence
is, and
min_confidencedecides what is acted on. Weak-checksum matches with no supporting keyword are found but left alone until you lower it (Configuration). - Six actions —
warn,hash(salted SHA-256),redact,mask, and the reversibletokenizeandsurrogate— all pluggable via a registry. - Reversible masking — mask on the way out, restore the LLM's answer on the way back, with an ordered source list of what was put back (Reversible masking).
- Value propagation — once any layer detects a value, every occurrence of it can be anonymized (Configuration).
- Degraded-scan visibility — if a layer can't run (e.g. LLM backend down),
it's recorded on
ScanResult.warningsinstead of failing silently. - Safe logging —
result.redacted()returns a PII-free dict.
New in 1.2.1
A wardcat command line, presets for KVKK, GDPR,
PCI DSS and secrets, the reversible surrogate action, classify() beside
is_sensitive(), and scans that refuse to fail quietly: with_strict(),
a circuit breaker for a dead LLM backend, Violation.source naming the
layer that found each span, and a per-entity min_confidence. Batched
scan_batch runs SpaCy in one pass, and an opt-in HIGH_ENTROPY_STRING
catches secrets with no known prefix. The changelog has the
reasoning behind each one.
Where next¶
- Installation — base install and optional extras.
- Quickstart — the programmatic and YAML APIs.
- Detection layers — regex, NER, and the LLM layer.
- Reversible masking — the LLM round trip, and restoring the answer.
- API reference — generated from the source docstrings.
- Entity types — every entity, its default action and its layer.
- Known limitations — what is not caught, and what to do about it.