Open Source · Apache 2.0

PII Firewall
for LLM apps

Detect, anonymize and rehydrate sensitive data before it reaches OpenAI, Anthropic or any LLM provider. 55+ languages, domain-aware keep rules, GDPR-ready vault — drop into your stack in 3 lines of code.

55+
Languages
7
Disposition actions
7
Detection backends
4
Domain profiles
pii_demo.py
from privacy_firewall import create_firewall

# Create a domain-aware firewall
firewall = create_firewall("healthcare")

result = firewall.process(
    text="Patient John Doe, SSN 123-45-6789,
          diagnosed with hypertension.",
    context={...},
)

# → "Patient PERSON_1, [REDACTED], diagnosed
#    with hypertension. Prescribed lisinopril 10mg."
# Medical terms KEPT. PII is stripped.
✓ Medical terms preserved
Architecture

Detect · Anonymize · Rehydrate

PII Firewall acts as a transparent privacy layer between your application and any LLM — with zero changes to your existing prompt logic.

01Input
"Patient Ana García, DNI 12345678A, diagnosed with hypertension."

Raw text with PII enters your system from the user or another service.

02Detect
PERSON ── Ana García
NATIONAL_ID ── 12345678A
DIAGNOSIS ── hypertension ✓ (keep)

One or more detection backends (regex, Presidio, GLiNER, Transformers) identify PII entities. Domain rules decide what to keep.

03Anonymize
"Patient PERSON_1, NATIONAL_ID_1, diagnosed with hypertension."

Entities are replaced according to their disposition: pseudonymize, redact, mask, generalize, or suppress. A vault stores reversible mappings.

04LLM
LLM never sees
real personal data.

The sanitized prompt is forwarded to any LLM provider — OpenAI, Anthropic, Mistral, local models, etc.

05Rehydrate
"Patient Ana García, DNI 12345678A, diagnosed with hypertension."

The vault restores original values in the model's response. The end-user sees real names — the LLM never did.

🔐
The LLM never sees real personal data

Anonymization happens in your infrastructure. Real values never leave your trust boundary. Rehydration runs after the model responds, so end-users still see meaningful output.

Capabilities

Everything you need for privacy-first AI

Built for production. Designed for compliance. Zero compromise on LLM utility.

🌍

55+ Language Auto-Detection

Zero config

Language is detected automatically per message with thread-level caching for zero latency after the first call. Locale-specific patterns for Spanish DNI, US SSN, French INSEE, German Steuernummer, Italian CF, Portuguese NIF, and global fallbacks.

🔄

Reversible Pseudonymization

GDPR ready

A secure in-memory (or SQLite) vault stores original↔token mappings per tenant/case/thread. PERSON_1 always maps back to the same real name within a conversation. Rehydration is automatic.

🏥

Domain-Aware Keep Rules

Smart

Medical terms (diagnoses, medications, procedures) are kept — not redacted — in the healthcare profile. Finance keeps amounts. Legal keeps case numbers. The LLM still receives the context it needs.

📋

Full Audit Trace

Compliance

Every call produces a TraceRecord with detected entities, their types, confidence scores, applied replacements, and residual PII warnings. Build compliance dashboards or feed your SIEM.

🗑️

GDPR Right to Forget

Art. 17

A single API call wipes all vault mappings for a case or thread, satisfying Art. 17 GDPR right to erasure. Per-tenant isolation ensures data never crosses customer boundaries.

🔌

Any LLM, Any Framework

Provider-agnostic

Works with OpenAI, Anthropic, Mistral, local models, or any HTTP LLM endpoint. Deploy as a Python SDK, FastAPI microservice, or Docker container. Node.js integration example included.

🧩

Custom PII Detectors

Extensible

Add regex rules at runtime with add_custom_regex(). Plug in any HuggingFace NER model as a Presidio recognizer subclass. Zero config files — just code.

< 1ms Regex Mode

High performance

Pattern-based detection has effectively zero overhead. Presidio NER runs in 50–200ms. All modes are compatible with async FastAPI services and production workloads.

🔁

Streaming Support

Real-time

secure_call_stream() yields rehydrated tokens as soon as they arrive from the LLM — no buffering. Plug into SSE or WebSocket endpoints for real-time privacy-safe chat applications.

Domain Profiles

Built-in presets for your industry

Each domain profile decides what's sensitive and what the LLM must see to do its job. Fully customizable.

🏥

Healthcare Profile

Keep clinical context. Anonymize patient identifiers and account data.

✓ Keeps (pass-through)
  • Diagnoses (hypertension, diabetes)
  • Medications (enalapril, lisinopril)
  • Procedures & observations
Transforms
PSEUDONYMIZE
PERSON
Ana García → PERSON_1
REDACT
SSN / DNI / NATIONAL ID
123-45-6789 → [REDACTED]
GENERALIZE
AGE
43 → [AGE_40-49]
MASK
PHONE
555-1234 → 555-****
PSEUDONYMIZE
ACCOUNT / IBAN
ES12345678 → ACCOUNT_1
REDACT
EMAIL
ana@clinic.es → [REDACTED]
Live example
Input
"Paciente Ana García, DNI 12345678A, 43 años,
hipertensión. Email: ana@clinic.es.
IBAN: ES12345678. Prescripción: enalapril 10mg."
↓ PII Firewall
Output (sanitized)
"Paciente PERSON_1, [REDACTED], [AGE_40-49],
hipertensión. Email: [REDACTED].
IBAN: ACCOUNT_1. Prescripción: enalapril 10mg."
firewall = create_firewall("healthcare")
Detection Backends

Mix and match detection engines

From zero-dependency regex to GPU-accelerated biomedical NER. Switch with one parameter.

Regexbase
< 1 ms
Install: No extras
  • Structured IDs
  • Emails & phones
  • Credit cards
  • Zero ML deps

Zero-dependency environments or fast structured-data pipelines.

🎯
Presidiorecommended
50–200 ms
Install: [presidio, langdetect]
  • Named entities (persons, orgs)
  • Multi-language NER
  • Best speed/accuracy balance
  • Extensible

General-purpose production workloads with NER requirements.

🔀
Hybridmax coverage
50–250 ms
Install: [presidio, langdetect]
  • Regex + Presidio combined
  • Maximum entity coverage
  • Catches structured IDs NER misses

High-risk domains where maximum recall matters more than latency.

🧠
GLiNERzero-shot
100–400 ms
Install: [gliner]
  • Zero-shot NER
  • No fine-tuning needed
  • Custom entity types on the fly

Custom entity types without labeled training data.

🤗
Transformersbiomedical
100–500 ms
Install: [transformers]
  • Biomedical NER (d4data, BC5CDR)
  • Highest medical accuracy
  • GPU acceleration
  • HuggingFace catalog

Clinical NLP where biomedical entity accuracy is critical.

🔬
OPFtoken-level
50–200 ms
Install: [opf]
  • Token-level PII classifier
  • Language-agnostic
  • Structured output with spans
  • Viterbi decoding

Environments needing a lightweight yet precise token classifier without Presidio.

🟢
Nemotronnvidia
100–300 ms
Install: [opf]
  • NVIDIA privacy-filter fine-tune
  • OPF-compatible runtime
  • High recall on free text
  • GPU acceleration

Maximum precision on unstructured free-form text using NVIDIA's fine-tuned model.

Disposition Actions

7 ways to handle PII

Each entity type in a domain profile gets a disposition — the precise transformation applied when that entity is detected.

ActionResultReversible
KEEPPass through unchangedN/A
PSEUDONYMIZEReplace with stable token + vault✅ Yes
REDACTReplace with constant marker❌ No
GENERALIZEKeep category, drop precision❌ No
MASKPartial reveal❌ No
HASHSHA-256 one-way hash❌ No
SUPPRESSRemove entirely❌ No
Only PSEUDONYMIZE is reversible — it stores a token↔original mapping in the vault for rehydration. All other actions intentionally destroy or alter the original value. If you need recoverability for an entity, choose PSEUDONYMIZE.
Integrations

Drop into your stack in minutes

Works with any LLM provider. Wrap any callable — no SDK lock-in.

OpenAI
from openai import OpenAI
from privacy_firewall import create_firewall

client = OpenAI()
firewall = create_firewall("healthcare", detector_backend="presidio")
context = {"tenant_id": "acme", "case_id": "c1", "thread_id": "t1", "actor_id": "u1"}

def openai_llm(prompt: str) -> str:
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}],
    )
    return resp.choices[0].message.content

# PII is stripped before reaching OpenAI, restored in the reply
result = firewall.secure_call(text=user_input, context=context, llm_client=openai_llm)
print(result.final_text)  # real names restored

No SDK lock-in — wrap any HTTP LLM endpoint, local model with Ollama, or LiteLLM proxy the same way.

Language Support

55+ languages, zero configuration

Language is detected automatically per message with thread-level caching — zero latency after the first call. Locale-specific patterns ensure country IDs and formats are recognized correctly.

Auto-detect or force a language:
# Auto-detect (default)
firewall = create_firewall("healthcare")

# Force Spanish, skip langdetect dependency  
firewall = create_firewall("healthcare", language="es")
🇪🇸
Spanish
es
DNI, NIE, IBAN-ES, phone
🇺🇸
English
en
SSN, EIN, ZIP, US phone
🇫🇷
French
fr
INSEE, SIREN, phone
🇩🇪
German
de
Steuernummer, IBAN-DE
🇮🇹
Italian
it
Codice Fiscale, phone
🇵🇹
Portuguese
pt
NIF, NIS, phone
🌍
Others
55+
Global patterns & auto-detect
GDPR Compliance

Built for regulatory compliance

GDPR Art. 17 right to erasure, tenant isolation, and full audit traces — out of the box.

🗑️

Right to Erasure (Art. 17)

firewall.forget() wipes all vault mappings for a thread or case. After deletion, rehydration will not restore any original values for that scope.

deleted = firewall.forget(
    tenant_id="hospital-001",
    case_id="patient-123",
    thread_id="consultation-1",
)
# → 14 mappings deleted
🏢

Tenant Isolation

Token mappings are scoped by tenant_id. The same PERSON_1 token in different tenants never shares a mapping. Hard isolation at the data layer.

context = {
    "tenant_id": "hospital-001",  # hard boundary
    "case_id":   "patient-123",
    "thread_id": "consultation-1",
    "actor_id":  "doctor-456",
}
🔐

Audit Trail

Every call produces a TraceRecord with entity types, confidence scores, and applied replacements — ready for your compliance dashboard or SIEM.

result.trace.detected_entities
# → [{type: "PERSON", text: "Ana García",
#      confidence: 0.97}, ...]

result.trace.replacements
# → [{"original": "Ana García",
#      "token": "PERSON_1"}, ...]
Quick Start

Up and running in minutes

Three steps from install to your first anonymized LLM call.

1

Install the package

pip install "pii-firewall[presidio,langdetect]"

# Download a spaCy language model
python -m spacy download en_core_web_sm  # English
python -m spacy download es_core_news_sm # Spanish
💡 Tip: Use [all] for all backends or just pip install pii-firewall for regex-only (no ML deps).
2

Create a firewall

from privacy_firewall import create_firewall

# Pick a domain profile
firewall = create_firewall("healthcare")  # or "finance", "legal", "generic"
3

Anonymize & rehydrate

context = {
    "tenant_id": "hospital-001",
    "case_id":   "patient-123",
    "thread_id": "consultation-1",
    "actor_id":  "doctor-456",
}

# Anonymize before sending to LLM
anon = firewall.anonymize(text=user_input, context=context)
llm_response = my_llm(anon.sanitized_text)

# Rehydrate — restore real names in the response
final = firewall.rehydrate(text=llm_response, context=context)
print(final)  # End-user sees real values
💡 Tip: Or use firewall.process() for a single-call anonymize→LLM→rehydrate round-trip.

More examples

As a FastAPI microservice
from fastapi import FastAPI
from privacy_firewall import PrivacyFirewallSDK

app = FastAPI()
sdk = PrivacyFirewallSDK.create(domain="healthcare", detector_backend="presidio")

@app.post("/privacy/sanitize")
async def sanitize(req: dict):
    result = sdk.anonymize_text(text=req["text"], context=req["context"])
    return {"sanitized_text": result.sanitized_text}
Custom regex entity at runtime
firewall.add_custom_regex(
    entity_type="EMPLOYEE_ID",
    regex=r"\bEMP-\d{6}\b",
    locales=["GLOBAL"],
    confidence=0.95,
    context_words=["employee", "staff"],
    disposition_action="redact",
)
GDPR — forget a thread
deleted = firewall.forget(
    tenant_id="hospital-001",
    case_id="patient-123",
    thread_id="consultation-1",
)
print(f"Deleted {deleted} mappings")
# Art. 17 GDPR right to erasure satisfied