open source · localhost · apache-2.0

Use any AI on your private data —
without handing it the data.

veil sits between your app and any third-party model. It swaps real identifiers for stable code-names before the prompt leaves your machine, and swaps them back in the reply. The model is useful. It just never learns who anyone is — and your secrets never leave at all.

No API token. Runs on a model you already pay for.

A real Claude fixes a real bug — seeing only pseudonyms. Captured, not mocked:

./demo-live.sh
What we send to Claude (your `claude` login — no API token; tools off)
  // owner: EMAIL_1  (escalations: EMAIL_2)
  const TOKEN_TTL = 15 * 60; // <-- should be 24 * 60 * 60 * 1000
  …  (config.ts → URL_1 / IP_1 / PATH_1; .env withheld)

  calling the real model…

Claude's answer (reverse-mapped for you)
  The TTL is 15 * 60 (15 minutes in seconds) but the store reads it as
  milliseconds — wrong unit and duration. Fix:
    const TOKEN_TTL = 24 * 60 * 60 * 1000;

  A frontier model fixed your bug and never saw an email, path, URL, or your .env.

The one idea

on the wire
you type    remind alice@acme.com about /Users/baris/q3.pdf
model sees  remind EMAIL_1 about PATH_1          ← pseudonymized
you get     Reminded alice@acme.com.                ← restored locally

The same person is always EMAIL_1 within a conversation, so the model can still reason about "who you mentioned earlier" — without ever learning the real name.

Four levels. Two rules, enforced in code.

public
general knowledge → sent as-is.
internal
mildly identifying → sent as-is.
private
emails, names, paths → pseudonymized before the wire (optionally k-anonymized: hidden among look-alike decoys).
secret
keys, credentials, IDs → run on a local model only. No local model? Withheld — never sent to a third party.

How it's built

A Rust engine

Does the swap-and-restore round-trip and holds the mapping. Binds localhost only — the one part that sees real data.

A TypeScript shell

Sorts the tiers, talks to the model (Anthropic, Ollama, …), streams the reply back, restoring names as tokens arrive.

A name detector

Regex for emails / paths / IPs / URLs; a small local model (GLiNER) for the people, places and companies regex can't catch.

An MCP server

Drop it in front of a coding agent (Claude, Cursor) and every file it reads is sanitized before the model sees it.

Honest about its edges

The numbers are public — including the unflattering one. On PUPA (the PAPILLON benchmark), veil's default detector leaks 33.5% of PII — fixed entity kinds miss the phones, dates and IDs in messy real prompts. Swapping in an LLM detector behind the same boundary drops that to ~4% — PAPILLON-class or better, and unlike PAPILLON, still exactly reversible. That's the bet: LLM-grade coverage with a perfect round-trip, as auditable shipped code. It keeps the truly sensitive on your machine — and tells you exactly where it stands.