Skip to main content
Learn how to build RAG pipelines where personal data never reaches your LLM provider. Blindfold provides two protection layers: selective ingestion redaction (strip contact info before indexing, keep names for searchability) and query-time tokenization (protect context and questions before the LLM, restore real data in responses).

Why RAG Needs PII Protection

RAG pipelines are the #1 pattern where PII leaks into LLMs. Documents retrieved from your knowledge base — support tickets, customer records, internal reports — often contain personal data. When those documents are embedded, stored, and retrieved, the PII flows through multiple systems:
  1. Retrieval results — documents with PII are injected into LLM prompts
  2. LLM provider logs — your provider sees the full prompt, including retrieved PII
The privacy boundary is at the LLM API call, not the vector store. Your vector store is internal infrastructure; the LLM provider is an external third party. Blindfold protects data at both layers: selectively strip contact info from documents before they enter the vector store, and tokenize everything before it reaches the LLM.

Security Trade-offs

There is no one-size-fits-all approach to PII in RAG pipelines. The right choice depends on your threat model: Redact contact info (emails, phones, IBANs) at ingestion — keep person names for searchability. At query time, search with the original question (names match), then tokenize context + question in a single call before the LLM. This is the approach used in all cookbook examples and described below.

Full Redaction

Redact all PII at ingestion. Strongest privacy — no personal data anywhere — but you lose the ability to search by name. The vector store can only match based on surrounding content.

Tokenize with Stored Mapping (Advanced)

Tokenize at ingestion and store the mapping. Build a reverse lookup to translate real names in queries to tokens. No PII in the vector store and name-based search works. See the advanced section below for details.

Two Protection Layers

Layer 1: Selective Ingestion Redaction

Redact contact info from documents before embedding and indexing. Names are kept so the vector store can match name-based queries.
Why keep names? At ingestion, person names are replaced with [PERSON]. At query time, names are tokenized to <Person_1>. Neither placeholder matches the other — so searching for “Hans Mueller” cannot find [PERSON] in the vector store. Keeping names at ingestion solves this and lets users search by name. Contact info (emails, phones) is rarely searched for and should always be redacted.

Layer 2: Query-Time Tokenization

After retrieval, tokenize the context and question in a single call before they reach the LLM. Then detokenize the response to restore real data.
Why a single tokenize call? If you tokenize the context and question separately, each call produces independent token numbering. Context might map <Person_1> to “Hans Mueller” while the question maps <Person_1> to “Marie Dupont” — creating mapping conflicts. A single call on the combined text ensures consistent numbering.

Protection Method Comparison

Choose the right protection method for your RAG use case:
Recommended pattern: Use redact with entities at ingestion time (Layer 1) to strip contact info while keeping names. At query time (Layer 2), search with the original question and tokenize the combined context + question before the LLM call. This gives you searchability by name and full PII protection at the LLM boundary.

Advanced: Tokenize with Stored Mapping

For the strongest privacy with full searchability — no PII in the vector store and name-based search — tokenize at ingestion and store the mapping. This is the most complete architecture but requires managing a mapping store. How it works:
  1. Ingestion: tokenize() each document → store tokenized text in vector store + store mapping securely
  2. Query: Build a reverse lookup from stored mappings. Replace real names in the query with their tokens before searching
  3. LLM: Tokenized context + tokenized query → LLM sees only tokens
  4. Response: Detokenize using stored mappings
Trade-offs:
  • Requires managing a mapping store (encrypted DB, secrets manager)
  • Reverse lookup needs exact string matching (partial names may not match)
  • More complex than the selective-redaction approach
  • But: strongest privacy with full searchability — no PII in the vector store at all
detokenize() is a free local operation — no API call. This means the mapping store is the only infrastructure you need to manage.

Policy Recommendations

Match your compliance policy to your use case:

Performance Tips

  • Batch redaction at ingestion — use blindfold.redact_batch() for processing multiple documents in one API call
  • Async processing — use AsyncBlindfold for concurrent document processing during ingestion
  • Detokenization is freedetokenize() is a local string replacement, no API call required
  • Cache redacted documents — once documents are redacted and indexed, no further Blindfold calls are needed for retrieval

Cookbook Examples

Complete, runnable examples for every RAG framework:

OpenAI + ChromaDB (Python)

Selective redaction + search-first tokenization

OpenAI + ChromaDB (Node.js)

TypeScript OpenAI + ChromaDB RAG pipeline

LangChain + FAISS (Python)

BlindfoldPIITransformer + retrieve-then-tokenize

LangChain + FAISS (Node.js)

LangChain.js RAG with inline PII protection

LlamaIndex (Python)

Retrieve-then-tokenize with LlamaIndex

LlamaIndex (Node.js)

LlamaIndex.TS with single tokenize call

GDPR Customer Support (Python)

Multi-turn EU support chatbot with gdpr_eu policy

GDPR Customer Support (Node.js)

TypeScript multi-turn EU support chatbot

Strategy Deep-Dives

Standalone examples for each ingestion strategy — compare trade-offs side by side:

Selective Redact (Python)

Keep names, redact contact info — simplest approach

Selective Redact (Node.js)

TypeScript version of the selective redact strategy

Stored Mapping (Python)

Tokenize everything, store per-document mappings

Stored Mapping (Node.js)

TypeScript version of the stored mapping strategy

Consistent Registry (Python)

Same person = same token everywhere — best search quality

Consistent Registry (Node.js)

TypeScript version of the consistent registry strategy

Strategy Comparison (Python)

All 3 strategies side by side with CLI selection

Strategy Comparison (Node.js)

TypeScript version — all 3 strategies with CLI selection

Role-Based Access Control (RBAC)

Use Blindfold policies to implement role-based PII control — same vector store, different privacy levels per user role:

RBAC with Policies (Python)

Doctor, nurse, billing, researcher — each role sees different PII levels

RBAC with Policies (Node.js)

TypeScript version of the role-based PII control example