
This connects Claude Desktop to a local PII detection and tokenization engine that runs entirely on your infrastructure. It wraps the OCULTAR refinery, which uses a multi-tier pipeline including regex validators (Luhn for credit cards, MOD97 for IBANs), libphonenumber, entropy scoring, and an optional local LLM for named entity recognition. Tokens are deterministic SHA-256 hashes, so you can still run joins and aggregations on redacted data. The architecture is fail-closed: if the refinery is down, requests block rather than leaking plaintext. Reach for this when you're connecting Claude to customer data, support tickets, or financial records and need verifiable guarantees that sensitive fields never leave your network boundary.
Ocultar is an open-source local PII/PHI masking engine for AI workflows.
It runs as a local HTTP sidecar. Send it text before it reaches a cloud LLM; it returns the
same text with every piece of personal data replaced by a deterministic, reversible token
([EMAIL_9c8f7a1b], [PERSON_3a12b4cd], …). Originals are encrypted and stored in a
local vault. Callers with the auditor token can restore them.
No PII ever reaches the upstream model.
export OCU_MASTER_KEY=$(openssl rand -hex 32)
export OCU_SALT=$(openssl rand -hex 16)
export OCU_AUDITOR_TOKEN=$(openssl rand -hex 24)
docker run --rm -p 4141:4141 \
-e OCU_MASTER_KEY \
-e OCU_SALT \
-e OCU_AUDITOR_TOKEN \
ghcr.io/ocultar-dev/ocultar:latest -serve 4141
CGO_ENABLED=1 go build -o ocultar ./services/refinery/cmd/
OCU_MASTER_KEY=$(openssl rand -hex 32) \
OCU_SALT=$(openssl rand -hex 16) \
OCU_AUDITOR_TOKEN=$(openssl rand -hex 24) \
./ocultar -serve 4141
GET /api/healthReturns engine status. No authentication required.
{
"status": "healthy",
"version": "1.14",
"vault": { "status": "online" },
"slm": { "status": "online", "circuit": "closed" }
}
POST /api/refineMask PII in text or JSON. No authentication required.
Request body: raw text string or any JSON value.
Response:
{
"refined": "{\"message\":\"Hello [PERSON_3a12b4cd], your order [EMAIL_9c8f7a1b] is ready.\"}",
"report": {
"hits": 2,
"types": ["PERSON", "EMAIL"]
}
}
refinedis a JSON-encoded string — parse it once to get the masked payload.
POST /api/revealRestore vault tokens back to originals.
Authentication: Authorization: Bearer <OCU_AUDITOR_TOKEN> header required.
Returns 403 if OCU_AUDITOR_TOKEN is not set on the server.
Request body:
{ "tokens": ["[PERSON_3a12b4cd]", "[EMAIL_9c8f7a1b]"] }
Response:
{
"results": {
"[PERSON_3a12b4cd]": "Alice Martin",
"[EMAIL_9c8f7a1b]": "alice@example.com"
}
}
GET /api/entities · POST /api/entities · POST /api/entities/seedManage the persistent entity registry (pre-seed canonical names so all variants map to the
same token). Requires Authorization: Bearer <OCU_AUDITOR_TOKEN>.
Ocultar runs two detection tiers before any text leaves the machine:
| Sub-tier | Shield | What it catches |
|---|---|---|
| 0 | Dictionary | VIP names, org names from configs/protected_entities.json |
| 0.5 | Pattern + Entropy | High-entropy strings (API keys, secrets) via Shannon scoring |
| 1 | Rule Engine | EMAIL, SSN, IBAN, credit cards, 50+ national ID formats |
| 1.1 | Phone Shield | libphonenumber validation |
| 1.2 | Address Shield | Heuristic street address parser (EN/FR/ES/DE) |
| 1.5 | Contextual | Names in greetings, signatures, interrogative sentences |
Sends text to a local AI sidecar for named-entity recognition. The scanner is always
initialized but produces no results unless a compatible sidecar is running at SLM_SIDECAR_URL.
Point it at a privacy-filter or llama.cpp instance to activate NER.
SLM_SIDECAR_URL=http://localhost:8085 ./ocultar -serve 4141
Use SLM_ADAPTER=openai-chat for a llama.cpp / Qwen endpoint, or leave unset for the
privacy-filter protocol (default).
[EMAIL_9c8f7a1b], …) are the only data forwarded to the upstream model. Raw text is not transmitted.vault.db) on the local filesystem using AES-256-GCM with HKDF-SHA256. The vault file is never transmitted.OCU_AUDITOR_TOKEN — without an auditor token the reveal endpoint returns 403 and the diff view is inaccessible.5xx error and stops — it does not forward raw text as a fallback.| Variable | Required | Default | Purpose |
|---|---|---|---|
OCU_MASTER_KEY | Yes (production) | insecure dev key | 32+ byte AES key material for HKDF |
OCU_SALT | Yes (production) | built-in default | Per-deployment HKDF salt |
OCU_AUDITOR_TOKEN | Yes | — | Bearer token for /api/reveal and /api/entities |
OCU_VAULT_PATH | No | vault.db | DuckDB vault file path |
SLM_SIDECAR_URL | No | http://localhost:8085 | Tier 2 NER sidecar endpoint |
SLM_ADAPTER | No | privacy-filter | Sidecar protocol: privacy-filter or openai-chat |
Requires Go 1.24+ with CGO enabled (DuckDB and libphonenumber need a C compiler).
git clone https://github.com/ocultar-dev/ocultar.git
cd ocultar
make build
Run tests:
CGO_ENABLED=1 go test ./...
Apache 2.0 — see LICENSE.
OCULTAR_URLURL of your locally running Ocultar Refinery
OCULTAR_API_KEYOcultar API key (leave blank if not configured)
OCULTAR_AUDITOR_TOKENEnables reveal_tokens tool. Must match OCU_AUDITOR_TOKEN on the server.