A self-hosted PII redaction engine that sits between your text and your LLM.
Redax is a small Python service that answers one question:
"How do I make sure no email, phone number, name, or other personally identifiable information ever leaves my server in cleartext?"
You send it a document; it sends back the same document with every PII span replaced by a typed placeholder. The redaction is deterministic: the same input always produces the same output, so two replicas of the service behind a load balancer will return byte-for-byte identical results. The original text is never logged.
It runs entirely on your own hardware. No data leaves the box. The
default detector (fastino/gliner2-privacy-filter-PII-multi,
0.3B parameters) is small enough to run on CPU, and the heavier
OpenMed/OpenMed-PII-SuperClinical-Large-434M-v1
detector fits in a single server's RAM. A regex safety net is
always on top of the model so a high-confidence pattern never gets
through even if the model misses it.
You, even if:
- You've never run a model before — the regex detector is built in and works out of the box.
- You don't know what PII stands for — it means personally identifiable information: anything that could be used to identify a real person.
- You've never deployed a model service.
If you can run docker compose up and curl, you can use Redax.
When the docs use a term you don't know, look it up in
docs/architecture.md.
If you've deployed ML services before, you'll be productive in five minutes.
- Deterministic regex detection — Seven structured PII types (email, phone, IP, IBAN, SSN, credit card with Luhn check, URL) with no model round-trip.
- GLiNER2 zero-shot NER — A 0.3B encoder that catches PERSON, organisation, and contextual entities the regex misses.
- OpenMed-PII (optional) — A 434M clinical-grade encoder with
54 entity types; swap in by setting
REDAX_DETECTOR=openmed. - Multi-stage pipeline — Regex gate + encoder + circuit-broken fallback + consensus fusion. See docs/architecture.md.
- Reversible typed placeholders —
[EMAIL_0001]instead of[REDACTED], so the same entity gets the same placeholder everywhere it appears. - Hiding-in-Plain-Sight relexicalization — Replace names with plausible lookalikes so the output still reads like English.
- Versioned YAML policies — Field-by-field redaction rules checked into git. See docs/policies.md.
- Append-only audit log — Records what was redacted, never the values. Configurable rotation, retention, and fsync.
- Prometheus + OpenTelemetry —
/metricsfor scraping, optional OTLP gRPC exporter for traces. - Rate limit + idempotency + response cache — Per-API-key token
bucket in Redis;
Idempotency-Keyshort-circuits retries. - RedactionBench R-Score — Quantified against the
ai4privacy/pii-masking-200kbenchmark. See docs/bench.md and docs/benchmark-results.md. - WASM bundle — Same model, INT8-quantised, runs in the browser via Transformers.js. See docs/deployment.md.
- RFC 7807 error responses — Every error path returns a problem-details JSON body.
You'll need Python 3.11 or newer installed on your computer, and git for downloading the code.
If you don't know what Python is or whether you have it:
- Open a terminal (on macOS:
Cmd + Space, type "Terminal"; on Windows: open "PowerShell"; on Linux: open your usual terminal). - Type
python3 --versionand press Enter. - If you see a version number starting with
3.11or higher, you're set. - Otherwise, follow the official Python installer guide.
You'll also need git (a tool for downloading code). Same drill:
type git --version in your terminal.
If you want the one-command path, you'll also need Docker and Docker Compose.
Pick whichever option fits your setup.
git clone https://github.com/sachncs/redax.git
cd redax
docker compose upThe image bundles Python + Redax + its pinned dependencies + the
GLiNER2 model snapshot. Redis comes up alongside the service so
rate limiting, idempotency, and the response cache work out of the
box. Wait for redax | Application startup complete. in the logs.
docker run --rm -p 8000:8000 ghcr.io/sachncs/redax:latestNo git clone, no local build. Useful on servers or in CI.
A "virtual environment" is an isolated Python sandbox that keeps this package's stuff from interfering with your other Python projects.
# 1. Download the code
git clone https://github.com/sachncs/redax.git
cd redax
# 2. Make a sandbox for it
python3 -m venv .venv
source .venv/bin/activate # macOS / Linux
# .venv\Scripts\activate # Windows (PowerShell)
# 3. Install Redax and its dev tools
pip install -r requirements.lock
pip install -e '.[dev]'💡 The dot in
.[dev]is intentional. It means "install this package and also the dev extras." The square brackets are part of the command, not punctuation.
After this, your terminal prompt will probably have (.venv) at the
front. That tells you the sandbox is active. To leave the sandbox
later, type deactivate.
The fastest way to see Redax work. No Python required:
curl -s -X POST http://localhost:8000/v1/redact \
-H 'Content-Type: application/json' \
-d '{"text": "Email me at alice@example.com or +1-415-555-2671."}'You'll see something like:
{
"text": "Email me at [EMAIL_0000] or [PHONE_E164_0000].",
"spans": [
{"start": 12, "end": 29, "type": "EMAIL", "confidence": 1.0},
{"start": 33, "end": 47, "type": "PHONE_E164", "confidence": 1.0}
],
"relex_map": {
"alice@example.com": "[EMAIL_0000]",
"+1-415-555-2671": "[PHONE_E164_0000]"
},
"used_pipeline": false,
"used_fallback": false,
"digest": "8f1d2c..."
}The audit log recorded the same information in ./audit.jsonl —
without the email or the phone number themselves.
Other endpoints you can hit with curl:
curl http://localhost:8000/healthz # liveness
curl http://localhost:8000/readyz # readiness
curl http://localhost:8000/metrics # Prometheus
curl http://localhost:8000/v1/policies # built-in policies
curl http://localhost:8000/v1/stats # detectors + breakerSee docs/api.md for the full HTTP surface.
Open a Python interpreter (python3 in your terminal) and try this:
import asyncio
from app.redaction.redactor import Redactor
from app.redaction.strategy import Hash, Mask, Skip
from app.inference.regex import RegexDetector
# Build a redactor with the regex detector + a couple of strategies.
detector = RegexDetector()
redactor = Redactor(
detector=detector,
strategies={
"passThrough": Skip(),
"mask": Mask(),
"hash": Hash(salt="change-me"),
},
replacement="[REDACTED]",
)
# Run a single redaction.
text = "Email me at alice@example.com or +1-415-555-2671."
result = asyncio.run(redactor.redact(text))
print(result.text)
# -> "Email me at [REDACTED] or [REDACTED]."
print(result.spans)
# -> [Span(start=12, end=29, type='EMAIL', confidence=1.0), ...]You can also pass a policy that maps field names to strategies:
result = asyncio.run(
redactor.redact(
text,
policy={
"fields": {
"free_text": {"strategy": "mask", "format": "[EMAIL]"},
"phone": {"strategy": "hash"},
}
},
)
)The full Python surface (Detector, Strategy, Pipeline, the Relex helper, the OpenMed / GLiNER2 wrappers) is documented in docs/api.md.
All settings are read from environment variables prefixed with
REDAX_ (and optionally a .env file in the working directory).
A typical .env looks like:
REDAX_LOG_LEVEL=INFO
REDAX_API_KEYS=prod-key-1,prod-key-2
REDAX_HASH_SALT=change-me-to-a-random-string
REDAX_REDIS_URL=redis://localhost:6379/0
REDAX_AUDIT_PATH=/var/lib/redax/audit.jsonl
REDAX_DETECTOR=gliner2 # or "regex" for the regex-only path
REDAX_MODEL_NAME=fastino/gliner2-privacy-filter-PII-multiWhat each field means:
| Variable | Default | Plain English |
|---|---|---|
REDAX_LOG_LEVEL |
INFO |
How chatty Redax should be: DEBUG (very chatty), INFO (normal), WARNING (only problems), ERROR (only failures). |
REDAX_API_KEYS |
empty | Comma-separated list of valid API keys. Empty = auth disabled (don't do this in production). |
REDAX_HASH_SALT |
change-me |
Salt for the hash strategy and the response cache. Pick a per-deployment random string. |
REDAX_REDIS_URL |
redis://localhost:6379/0 |
Redis URL for jobs, rate limit, and the response cache. |
REDAX_AUDIT_PATH |
./audit.jsonl |
Append-only JSONL audit log. Mount this on durable storage. |
REDAX_AUDIT_FSYNC |
true |
fsync the audit log after every line. Set false only if you're shipping logs to a separate sink. |
REDAX_DETECTOR |
gliner2 |
Which detector to use. regex runs the deterministic path only; gliner2 adds zero-shot NER. |
REDAX_MODEL_NAME |
fastino/gliner2-privacy-filter-PII-multi |
HuggingFace model id for the active detector. Pinned revision lives in MODEL_HASHES.txt. |
REDAX_INFERENCE_CONCURRENCY |
2 |
Maximum number of in-flight model calls. Increase for GPUs. |
REDAX_RATE_LIMIT_PER_MINUTE |
60 |
Per-API-key fixed-window rate limit. 0 disables. |
REDAX_MAX_TEXT_CHARS |
100000 |
Reject inputs longer than this with a 413. |
REDAX_OTLP_ENDPOINT |
empty | OTLP gRPC endpoint for OpenTelemetry traces. |
The full list lives in app/config.py; see
docs/deployment.md for the production
checklist.
For users:
- docs/api.md — Full HTTP surface: request and response shapes, headers, error codes. Bookmark this once you start integrating.
- docs/integration.md — Drop-in patterns for the common LLM SDKs: how to wrap the prompt, stream the response, handle retries.
- docs/policies.md — Authoring redaction policies; the field-strategy mapping; relex vs format strings.
- docs/architecture.md — How the multi-stage pipeline is put together and why.
- docs/bench.md — The RedactionBench R-Score metric, the worked example, and the scoring API.
- docs/benchmark-results.md — The
per-corpus comparison table against
ai4privacy/pii-masking-200k.
For operators / maintainers:
- docs/deployment.md — Docker, Compose, Kubernetes, behind a load balancer, observability hooks.
- docs/models-survey.md — Why the default detector is GLiNER2 and what to swap in if you need higher recall on healthcare / multilingual text.
- CHANGELOG.md — Per-release notes.
- AGENTS.md — Conventions for anyone editing the codebase.
The project page lives under site/ and deploys to GitHub Pages
via .github/workflows/pages.yml.
cd site
npm install
npm run dev # http://localhost:4321/redax/
npm run build # static output in site/distWant to improve Redax? See CONTRIBUTING.md for how to set up a development environment and submit changes.
We expect everyone to follow our Code of Conduct.
Found a security issue? See SECURITY.md — please don't open a public GitHub issue for security problems.
Apache 2.0 — see LICENSE. Use it, fork it, ship it.