Skip to main content

Saudi-aware PII detection & redaction for LLM pipelines. Local-first, zero telemetry.

Project description

Tabayyan — Saudi-first PII detection & redaction for LLM pipelines

PyPI Python Tests License: Apache-2.0

Install · Quick start · Middleware & audit · Plugins · API stability · Changelog

16 detectors · 280+ tests (property · golden-regression · contract · fuzz) · Python 3.9–3.13 · zero-dependency, offline core

Generic PII libraries target international identifiers and either miss Saudi ones or flag them with no validation. Tabayyan adds first-class Saudi & Arabic identifier support, backed by real checksums — extensible and production-friendly.

pip install tabayyan-privacy

[!NOTE] A detection aid, not a compliance guarantee — deploy it as one layer in a defense-in-depth strategy, with human review for LOW-confidence findings. See Scope and honest limits.

How it works

Pipeline: input → Unicode normalization → detection → checksum validation → classification → redact/hash/tokenize → safe output

InputUnicode normalization (strip zero-width/bidi, fold Arabic-Indic & fullwidth digits) → detectionchecksum validationclassification (type · confidence · NDMO level) → redact / hash / tokenizesafe output. Pure-ASCII input is unchanged; matches map back to the original offsets.

Before / after

scan_and_redact(text, "mask") — detects, then rewrites the original text in place:

InputOutput
المريض محمد بن عبدالله
National ID: 1158813996
Phone: +966512345678
المريض [ARABIC_NAME]
National ID: [SAUDI_NATIONAL_ID]
Phone: [SAUDI_MOBILE]

Why Tabayyan?

Capability Tabayyan Generic PII tools
Saudi National ID / Iqama ✅ checksum-validated ❌ missed / unvalidated
Saudi IBAN (mod-97) ⚠️ partial
Saudi VAT · CR · passport · National Address
Arabic-Indic digits & Unicode-evasion aware ✅ normalized ⚠️ often missed
Checksum validation (not just format) ⚠️ rare
Offline · zero-dependency core ⚠️ varies
LLM guard + PDPL cross-border audit
Homograph / Arabic+Latin lookalike domains ✅ (opt-in) ⚠️ rare

Typical use cases

Sector Example
Healthcare Redact patient identifiers before notes reach an LLM
Government PDPL-aware document processing
Banking Detect Saudi financial identifiers (IBAN, VAT)
Insurance Sanitize claims & customer documents
Enterprise AI Prompt sanitization for LLM apps

Works with

Built-in adapters: OpenAI · Azure OpenAI · Anthropic · Microsoft Presidio. Any other stack (FastAPI, LangChain, batch jobs, your own SDK) plugs in via the provider-agnostic Guard.protect_messages / Guard.wrap — see middleware.

For FastAPI and Starlette apps, install the optional extra and add the reusable request middleware:

pip install "tabayyan-privacy[fastapi]"
from fastapi import FastAPI

from tabayyan.integrations.fastapi import TabayyanPrivacyMiddleware

app = FastAPI()
app.add_middleware(
    TabayyanPrivacyMiddleware,
    destination="https://api.openai.com",
    include_paths={"/chat", "/messages"},
    exclude_paths={"/health", "/metrics"},
    include_methods={"POST"},
)

Examples

Python API · CLI · Middleware & audit · Presidio · Plugins · Playground

Playground (demo web UI)

Tabayyan Playground — Saudi PII highlighted, classified, and listed as cards, with redaction preview

Highlight, classify, and redact PII in the browser — public API only, zero external calls. Editor + per-category highlighting, detection cards, JSON view, redaction preview, synthetic Arabic samples, .txt upload, light/dark.

▶ Run it locally:

pip install -e . && pip install -r playground/requirements.txt
uvicorn playground.app:app          # → http://127.0.0.1:8000

Details in playground/README.md.

Status

Current public release: v0.9.1. This release adds reusable FastAPI / Starlette middleware with route, method, and field filtering, request-body limits, structured +json media-type support, and expanded integration tests. Pre-1.0 version numbers track development milestones, and the CHANGELOG documents each one. API guarantees and experimental surfaces are described in docs/api-stability.md.

Install

pip install tabayyan-privacy     # core (zero dependencies)
pip install "tabayyan-privacy[crypto]"   # + encrypted tokenize vault
pip install "tabayyan-privacy[presidio]" # + Microsoft Presidio recognizers
# from source (dev): pip install -e ".[dev]"

Quick start

from tabayyan import scan, scan_and_redact, RedactionMode

for m in scan("call +966512345678 — National ID 1010864542 on file"):
    print(m.entity_type.value, m.confidence.value, m.category.value)

# Redact in one step
result = scan_and_redact("National ID 1158813996", RedactionMode.MASK)
print(result.text)  # National ID [SAUDI_NATIONAL_ID]

Each result is a Match with entity_type, category, confidence (HIGH / MEDIUM / LOW), character start/end, the matched value, and a .redacted() placeholder.

Prefer the shell? echo "National ID 1158813996" | tabayyan scan - — full CLI below.

Windows: if printing Arabic raises UnicodeEncodeError, set PYTHONIOENCODING=utf-8 (a console limitation, not the library) — see the FAQ.

CLI

# detect (table or --json); reads stdin, files, or directories
echo "National ID 1158813996" | tabayyan scan -
tabayyan scan ./docs --json --min-confidence high

# redact: mask | remove | hash | partial
cat note.txt | tabayyan redact - --mode mask
cat note.txt | tabayyan redact - --mode partial --keep-last 4
cat note.txt | tabayyan redact - --mode hash --salt "$SALT"

# CI / pre-commit gate: non-zero exit if anything is found
tabayyan scan ./src --fail-on-find

Filters: --min-confidence {low,medium,high}, --only TYPE..., --exclude TYPE....

Redaction modes

Mode Output for a National ID Use case
mask [SAUDI_NATIONAL_ID] default; keeps text readable
remove (deleted) strip entirely
hash [HASH:f999c93a6934] keyed (HMAC), deterministic; correlate without exposing
partial ******8153 keep last N for debugging

hash is HMAC-SHA256 keyed by --salt and requires a non-empty salt — a bare digest of a 10-digit identifier is reversible by brute force, so the key is what makes the token non-reversible. The same value maps to the same token under a given salt, so you can correlate occurrences without revealing the value; change the salt to break correlation across datasets. Treat hash output as pseudonymous, not anonymous.

In code:

from tabayyan import scan_and_redact, RedactionMode

result = scan_and_redact(text, RedactionMode.MASK)
print(result.text)   # sanitised
print(result.count)  # entities redacted
print(result.items)  # per-entity mapping

Confidence model

  • HIGH — passes a published checksum (National ID, Iqama, IBAN, credit card). Very low false-positive rate.
  • MEDIUM — strong, specific format match with no checksum available (+966 mobile, email).
  • LOW — format/context only, meaningful false-positive potential (CR, MRN). Confirm before acting.

Lookalike / homoglyph domains (opt-in)

Beyond PII, Tabayyan can flag domains that impersonate a watchlist using confusable characters (IDN homograph attacks), mixed scripts (including Arabic+Latin), or edit-distance typosquats.

tabayyan domains email.eml --watchlist my-domains.txt
from tabayyan.homoglyph import scan_text

scan_text("login at ex\u0430mple.com", ["example.com"])
# -> impersonation (Cyrillic 'a'), target example.com, HIGH

This is not in the default PII detector set — construct LookalikeDomainDetector(watchlist=...) or use the domains command.

Benchmarks

Reproducible on a synthetic corpus with hard negatives:

python benchmarks/run.py --write  # writes benchmarks/RESULTS.md

The headline is the false-positive contrast against a naive format-only regex — checksum validation removes the entire decoy class:

Entity type Naive regex FP Tabayyan FP
saudi_national_id 300 0
saudi_iqama 300 0
saudi_iban 300 0
credit_card 300 0

(300 invalid-checksum decoys per type. Synthetic data measures detectors against their design assumptions, not real-world traffic — see the honest caveat below.)

The run also reports an evasion-robustness section: recall on identifiers hidden behind zero-width, Arabic-Indic, or fullwidth characters, with the normalization pre-pass on vs off — recall stays 1.000 normalized and collapses without it. Full tables in benchmarks/RESULTS.md.

Validators are independently cross-checked: National ID against alhazmy13/Saudi-ID-Validator, and IBAN + Luhn against python-stdnum plus official card-network test PANs. See REFERENCES.md.

Docker & pre-commit

# Docker
docker build -t tabayyan:local .
echo "National ID 1158813996" | docker run --rm -i tabayyan:local scan -

# pre-commit: block accidental PII in commits
# add this repo to .pre-commit-config.yaml (see the file in this repo)

Middleware & audit (Azure / OpenAI)

Put a guard in front of your LLM endpoint: redact personal data before it leaves, and emit an audit trail — including cross-border transfer flagging (PDPL Art. 29) for endpoints outside the Kingdom.

from tabayyan import Guard, AuditLog, RedactionMode

guard = Guard(in_kingdom_hosts=["llm.myhospital.health.sa"],
              audit=AuditLog(path="audit.jsonl"))
pr = guard.protect("الهوية 1158813996", destination="https://contoso.openai.azure.com")
pr.text                         # redacted before send
pr.audit.cross_border_transfer  # True for external endpoints with personal data

Wrap any LLM client — OpenAI/Azure or Anthropic, auto-detected — with guard.wrap(client, destination=...), then call .create(...); PII is redacted before the request leaves. See docs/middleware.md.

Use it inside Presidio

Already on Microsoft Presidio? Add Tabayyan's validated Saudi/Arabic recognizers with one import:

pip install "tabayyan-privacy[presidio]"
from presidio_analyzer import AnalyzerEngine
from tabayyan.integrations.presidio import register_saudi_recognizers

analyzer = AnalyzerEngine()
register_saudi_recognizers(analyzer)  # SA_NATIONAL_ID, SA_IQAMA, SA_IBAN, ...

It complements Presidio (adds what it lacks, no duplication) and is parity-tested against the standalone engine. See docs/presidio.md.

Performance

Single-threaded, default detector set, on synthetic text:

python benchmarks/perf.py

Overlap resolution sorts in O(n log n) and accepts each match with two bisect lookups; keeping the disjoint set ordered uses list.insert, so the worst case is O(n²) for pathologically dense input (n = matches, not bytes). In practice n is tiny: a dense 5 MB sample (one entity per ~57 bytes) still scans in under 2 seconds on a typical CPU, and real prose is far sparser. For very large files, use streaming so memory stays flat:

tabayyan scan huge.log --stream

Reversible redaction (tokenize)

from tabayyan import scan_and_redact, restore, RedactionMode

r = scan_and_redact("ID 1158813996, again 1158813996", RedactionMode.TOKENIZE)
# "ID <SAUDI_NATIONAL_ID_1>, again <SAUDI_NATIONAL_ID_1>"  (repeats share a token)
assert restore(r.text, r.vault) == "ID 1158813996, again 1158813996"

The vault (token → original) is the reversal key — store it as securely as the source data.

Extending via config

{ "disable": ["saudi_cr"],
  "custom_detectors": [
    {"label": "employee_id", "pattern": "EMP-\\d{6}",
     "category": "organisation", "confidence": "medium"}] }
tabayyan scan note.txt --config my-config.json

See docs/config.md, docs/faq.md, docs/threat-model.md, and REFERENCES.md for algorithm provenance.

Scope and honest limits

Tabayyan is a detection aid, not a compliance guarantee.

  • Passing a checksum means a value is structurally valid, not that it was ever issued or belongs to a real person.
  • The National ID validator uses the de-facto community Luhn variant, cross-validated against an independent reference (100% agreement on 50k+ samples) but not an authoritative government spec. Confirm before production reliance (see docs/REFERENCES.md).
  • Arabic name detection is a heuristic, not ML NER: recall is limited by design to protect precision.
  • CR has no public checksum; detection is format + keyword context only.
  • MRN has no national format; detection is keyword-context only and is inherently lower precision. It is still tagged as health data, which carries the strictest handling obligations under PDPL/NDMO — weight it accordingly even at LOW detection confidence.
  • False negatives exist. Do not make this your sole control for personal or health data.

Project layout

tabayyan/
├── src/tabayyan/   core library (detectors · engine · redaction · middleware) + the `tabayyan` CLI
├── playground/     offline demo web UI
├── docs/           guides, ADRs, threat model, brand
├── examples/       runnable snippets
└── benchmarks/     precision/recall + evasion harness

Roadmap

  • v0.1 — detection core + Saudi/generic detectors + tests.
  • v0.2 — redaction modes (mask/remove/hash/partial) + CLI.
  • v0.3 — homoglyph/lookalike-domain detection, benchmark suite, Docker / pre-commit / PyPI / docs.
  • v0.4 — Arabic name detection, streaming large files, reversible tokenize redaction, JSON config + custom detectors, faster bisect-based overlap resolution, references + FAQ + threat-model docs.
  • v0.5 — middleware + audit (cross-border flagging) and Presidio integration (validated Saudi recognizers).
  • v0.6 — six new Saudi entities (VAT, landline, passport, border/visa, National Address, unified 700); offset-preserving anti-evasion normalization; provider-agnostic adapter layer (OpenAI + Anthropic); NDMO data classification in the audit; password-encrypted tokenize vault; expanded precision/recall + evasion-robustness benchmarks; and security hardening (HMAC-keyed hash, block-path leak fix, timezone-aware audit timestamps).
  • v0.7 — detector plugin system (register_detector + opt-in entry_points discovery); verification & governance: property-based tests, golden corpus + contract tests, frozen public-API + SemVer/deprecation policy; expanded threat model; scheduled fuzzing; and release-engineering docs (RELEASE, compatibility matrix, ADRs, detector guide).
  • v0.7.1 – v0.7.2 — community-review fixes (README ID checksum + regression test, keep_last alias, Windows/Arabic-name docs); richer PyPI metadata.
  • v0.8.0 — Playground demo web UI, README UX overhaul, and the official brand identity.
  • v0.8.1 (current) — FastAPI middleware hardening, request body limits, field filtering, CI cleanup, and release polish.
  • Toward 1.0 — the verification, API-stability, and governance foundations are in place; 1.0 is a stabilization milestone rather than a feature one.

After 1.0

A short list of priorities (not a wishlist):

  • improved homoglyph / letter-confusable handling in free text;
  • additional regional identifiers;
  • enterprise integrations;
  • performance and streaming improvements;
  • optional static typing (mypy) in CI;
  • optional prompt-injection heuristics (isolated module);
  • a "Trusted by" / adopters showcase — once there are public adopters to name (no placeholder logos until then).

Contributing

See CONTRIBUTING.md and the detector guide. One hard rule: synthetic data only — never commit real personal data. Releases follow RELEASE.md; supported environments are listed in docs/compatibility.md, and the design rationale lives in the ADRs.

License

Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tabayyan_privacy-0.9.1.tar.gz (392.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tabayyan_privacy-0.9.1-py3-none-any.whl (56.4 kB view details)

Uploaded Python 3

File details

Details for the file tabayyan_privacy-0.9.1.tar.gz.

File metadata

  • Download URL: tabayyan_privacy-0.9.1.tar.gz
  • Upload date:
  • Size: 392.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tabayyan_privacy-0.9.1.tar.gz
Algorithm Hash digest
SHA256 a946a9c049ceeb891839833f6787875096265d60ddefcb593cb89380ee9b6eb5
MD5 cabb4fd166bc3a067f64ab0b1a6caea4
BLAKE2b-256 e4e947cc50a27869acbb63ed999fa5b1f8a04c2e4e9b3b02fccec835c4ba5f34

See more details on using hashes here.

Provenance

The following attestation bundles were made for tabayyan_privacy-0.9.1.tar.gz:

Publisher: release.yml on blackice2090/saudi-ai-privacy-gateway

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tabayyan_privacy-0.9.1-py3-none-any.whl.

File metadata

File hashes

Hashes for tabayyan_privacy-0.9.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2322ca7a56ba22a856eac463a1744ae60b1b601f9a58603229a67949562ded43
MD5 adb9d7e07090dae9f979b8d2bbf2e257
BLAKE2b-256 9e8219015fda317f051648f04ee8f25af734179445cac3709caa8079902a9f78

See more details on using hashes here.

Provenance

The following attestation bundles were made for tabayyan_privacy-0.9.1-py3-none-any.whl:

Publisher: release.yml on blackice2090/saudi-ai-privacy-gateway

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page