wardcat
wardcat finds PII and secrets in text before it reaches an LLM and replaces them — checksum-validated regex out of the box, optional SpaCy NER and an on-prem LLM layer; Turkish, English, German and French. Nothing leaves your machine.
📖 Documentation: https://docs.wardcat.com
Disclaimer. wardcat is a best-effort PII detector — it does not catch everything (false negatives and positives are expected) and is not legal advice or a substitute for compliance review (e.g. GDPR/KVKK); using it does not by itself make a system compliant. Validate it against your own data and requirements. Provided "as is" (MIT — see LICENSE).
Install
Python 3.11+.
pip install wardcat # regex layer + Ollama / OpenAI-compatible LLM backend
pip install "wardcat[ner]" # + SpaCy NER (names, organisations, places)
pip install "wardcat[phone]" # + national phone formats worldwide (libphonenumber)
pip install "wardcat[all]" # everything, including the in-process Transformers backend
Details and SpaCy models: Installation.
First scan
Detection is opt-in: a bare Wardcat() detects nothing. Name the entities you
care about and the action for each. This runs offline, with no model:
from wardcat import Wardcat, Entity, Action
guard = (
Wardcat(salt="change-me") # your app supplies the salt
.add_entities([Entity.TC_ID, Entity.CREDIT_CARD], action=Action.HASH)
.add_entities([Entity.IBAN, Entity.PHONE], action=Action.REDACT)
.add_entity(Entity.EMAIL, Action.MASK)
)
text = "Müşteri TC 10000000146, IBAN TR33 0006 1005 1978 6457 8413 26, tel +90 532 123 45 67. Card 4111 1111 1111 1111, contact ayse.kaya@example.com"
result = guard.scan(text)
print(result.sanitized_text)
# Müşteri TC [TC_ID:80767a764888c13d], IBAN [IBAN], tel [PHONE]. Card [CREDIT_CARD:e2292d1b12e86559], contact a********@example.com
print(result.is_clean, [v.entity_type for v in result.violations])
# False ['TC_ID', 'IBAN', 'PHONE', 'CREDIT_CARD', 'EMAIL']
The Quickstart adds the YAML form,
entity groups and batches. Add .with_ner(language="tr") for names and
.with_llm(model=...) for contextual and semantic detection — both are on the
Detection layers page, with the async API.
The same scan from a shell:
echo "mail ali@example.com, card 4111 1111 1111 1111" | wardcat scan --entity EMAIL --entity CREDIT_CARD=mask
# mail [EMAIL], card ************1111
Why wardcat
- A match is proof, not a guess. Cards (Luhn, Troy included), IBANs (mod-97), TC IDs, Bitcoin addresses, NHS and ABA numbers, IMEIs, Spanish, French, Dutch and Polish national IDs are checksum-verified before they are flagged. Weak evidence is scored below a confidence floor and left alone until you lower it.
- Three layers, one policy. Regex is always on; SpaCy NER and an on-prem LLM (Ollama, vLLM, any OpenAI-compatible server, or in-process Transformers) are opt-in. A deterministic regex span always wins an overlap, and the LLM can adjudicate the other layers' candidates in one call.
- Six actions, two of them reversible.
warn,hash(salted SHA-256),redact,mask,tokenizeandsurrogate(a realistic stand-in of the same shape). Tokens and surrogates come back withrestore()after the model has answered. - Turkish first, then Europe. TC ID, IBAN, Vergi No, plates and postcodes beside SSN, NIN, DNI/NIE, INSEE, BSN, PESEL, UK postcodes and ZIP+4; names, addresses, birth dates and phone numbers in TR/EN/DE/FR; API keys for a dozen providers, PEM blocks and credentials written into a sentence. Lookalike characters (Cyrillic, fullwidth digits) are folded before matching.
- It never fails quietly. A layer that cannot run is reported on
result.warnings, or refused outright withwith_strict(); a dead backend trips a circuit breaker instead of costing every scan its timeout. The library reads no environment variables and its log lines never carry a value.
With the LLM layer on, is_sensitive()
is a yes/no gate for text with no typed entity — a leaked forecast, a deal
term — and classify() says which kind. A reproducible benchmark
scores wardcat against Microsoft Presidio on public corpora.
Learn more
- Quickstart — programmatic and YAML APIs, entity groups, batches, the examples.
- Detection layers — regex, NER, the LLM backends, adjudication, the semantic gate.
- Entity types — every entity, its default action and which layer finds it.
- Configuration & policy — the confidence floor, propagation, allow/deny lists, strict mode, the circuit breaker, YAML reference.
- Presets —
kvkk,gdpr,pci_dss,hipaa_lite,secrets_onlyas starting policies. - Command line —
wardcat scan,check-config,entities. - Reversible masking — tokens, surrogates,
restore()and the reverse map. - Extending — custom actions and detectors.
- MCP server — wardcat as a tool for an agent.
- Known limitations — what is not caught, and what to do about it.
- API reference · Examples · Changelog
Security
Sensitive values are replaced with [TYPE:16hex] by default. Deterministic
hashing is not anonymization: the same value always hashes to the same token,
and because SHA-256 is fast, low-entropy PII (phone numbers, SSNs, TC IDs) can be
recovered by brute force by anyone who has the salt and knows the value format.
Treat hashed output as pseudonymized: keep the salt secret, and use
redact/mask when you need values that cannot be reversed.
import os
from wardcat import Wardcat
guard = Wardcat(salt=os.environ["WARDCAT_SALT"]).add_entity("CREDIT_CARD", "hash")
Never hardcode the salt in source code or config files. ScanResult.original_text
and violations[].original contain raw PII; use result.redacted() for logs and
API responses. Inputs exceeding 500 KB raise a ValueError — split large
documents into smaller chunks before scanning. Plaintext HTTP to a remote LLM
backend is blocked (loopback is fine); the LLM layer is best-effort against
prompt injection, so keep regex and NER in front of it.
The security guide has the details. To report a vulnerability, follow SECURITY.md.
Contributing
Bug reports and pull requests are welcome on GitHub. CONTRIBUTING.md covers the development setup, the quality gates and how to add an entity type.
License
MIT — see LICENSE.
Metadata
Release files for wardcat 1.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| wardcat-1.2.1.tar.gz | 1.9 MB | Without provenance | Details for wardcat-1.2.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| wardcat-1.2.1-py3-none-any.whl | Python 3 | none | any | Details for wardcat-1.2.1-py3-none-any.whl |
Total release size: 2.1 MB
Release files / wardcat-1.2.1.tar.gz
| Download URL | wardcat-1.2.1.tar.gz |
|---|---|
| Size | 1.9 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
73d8c95e9d8b7ebcecbffa5d680e699f43609b1636295dd3e61bbe0da98d060f
|
|
BLAKE2b-256 checksum How to use checksums |
0dce143c83c4167d3109c6d52b916eb78441643b6d3c5396bec649f3e65795af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / wardcat-1.2.1-py3-none-any.whl
| Download URL | wardcat-1.2.1-py3-none-any.whl |
|---|---|
| Size | 157.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0f9e3510ecb2e1bdd2bb0407842845a2555e6942b231965f3f78b9284753ea0b
|
|
BLAKE2b-256 checksum How to use checksums |
1f2fc6d5716ea2cf46e78612c6c1a53e64028f6b866257fc3daa07c5e835abdd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for wardcat 1.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.