A local read-only, PII-redacting proxy that lets AI agents query your database safely.
Project description
veil
A local read-only, PII-redacting proxy that lets AI agents query your database safely.
Point Claude Code (or any MCP client) at veil instead of your database. Every query is
forced through three deterministic guarantees before a single row reaches the model:
- Read-only guard — the query is parsed with Postgres's real grammar (
libpg_query). OnlySELECT/SHOW/EXPLAINsurvive. Writes, DDL, multi-statements, data-modifying CTEs,SELECT INTO, and row locks are rejected before execution — not by asking the model nicely, by refusing to run them. - PII redaction — results are scrubbed before they leave your machine: deterministic column rules + always-on regex for structured PII (emails, phones, cards, SSNs), with an optional NER/LLM backstop for free-text.
- Audit — every query and verdict is appended to a log you can tail live in a TUI.
A guarded chokepoint in front of the DB, shrunk to a single open-source command with zero infrastructure to stand up.
Claude Code ──MCP──▶ veil ──READ ONLY txn──▶ your database
│
├─ guard: parse → allow SELECT only
├─ redact: column rules + regex + (optional) NER/LLM
└─ audit: veil-audit.jsonl
Why
You want an agent to act as a data analyst over real tables — "compare what we drafted vs what
was actually sent" — without (a) risking a destructive query or (b) shipping customer PII to a
model provider. Handing an agent raw DB credentials and hoping it only writes SELECT is not a
control. veil makes the unsafe paths impossible at the layer the agent can't talk its way past.
Install
pip install dbveil # or: uv pip install dbveil
# optional extras:
pip install 'dbveil[ner]' # Presidio NER backstop for names/addresses
pip install 'dbveil[llm]' # local-LLM (Ollama) redaction
Quickstart
veil init # interactive: DB URL + auto-detect PII columns → writes veil.yaml
veil doctor # verify guard, connectivity, and that READ ONLY actually blocks writes
veil test-query "SELECT email, created_at FROM users LIMIT 5" # try it without an agent
veil up # run the MCP proxy on stdio (what Claude Code connects to)
Try a write to see the guard refuse it:
veil test-query "DELETE FROM users"
# BLOCKED — write or DDL operation detected: DELETE
Connect Claude Code
claude mcp add veil -- veil up
or commit a .mcp.json so your whole team gets it:
{
"mcpServers": {
"veil": { "command": "veil", "args": ["up"], "env": { "VEIL_CONFIG": "veil.yaml" } }
}
}
Now the agent has three tools — query, list_tables, describe_table — and physically
cannot write or see raw PII.
Watch it live
veil monitor # TUI tailing veil-audit.jsonl: allowed / blocked / redaction counts
Configuration
veil init writes a commented veil.yaml. Full reference in
examples/veil.example.yaml. The essentials:
database:
url: ${DATABASE_URL} # env refs kept out of the file
guard:
allow_select_star: false # block SELECT * on PII tables; force explicit columns
max_rows: 1000
statement_timeout_ms: 15000
pii_tables: [contacts, users]
redact:
builtin_patterns: { email: true, phone: true, credit_card: true, ssn: true, ip: false }
columns:
- { column: email, strategy: hash } # sha256, still join-able
- { column: full_name, strategy: mask } # -> [redacted]
- { column: ssn, strategy: partial, keep: 4 }
ner: { enabled: false, engine: presidio } # optional backstop
How redaction is layered (and its honest limits)
veil defends from the deterministic side first, because that's the only kind you can trust
not to leak:
| Layer | What it catches | Deterministic? |
|---|---|---|
| Column rules | Known PII columns (email, ssn, …) by name |
✅ yes |
| Built-in regex | Emails, phones, Luhn-valid cards, SSNs, IPs — even aliased or in free-text | ✅ yes |
| NER (Presidio) | Names / addresses in free-text the above miss | ⚠️ probabilistic |
| LLM (Ollama) | Same, via a local model | ⚠️ probabilistic, experimental |
Use the probabilistic layers only as a backstop. ML/NER will eventually miss a name or an oddly-formatted address — that's a leak. For columns you already know are sensitive, the column rules are the real control. The LLM redactor fails closed: if the model errors, the cell is masked, never passed through.
Security model
- Two independent read-only layers. The parser rejects non-reads, and every query runs
inside a
SET TRANSACTION READ ONLYtransaction — so even a parser gap can't write. - Give veil a least-privilege credential. Best practice is a
GRANT SELECT-only database role (ideally on a read replica). Then "read-only" is enforced by the database itself, and the credentialveilholds is low-blast-radius: a leak exposes already-masked reads and can write nothing.veil doctorconfirms the READ ONLY transaction rejects writes against your DB. - PII never leaves your machine unmasked. Redaction happens in-process, before results are serialized to the MCP client.
Secure connectivity
veil connects to whatever DSN you give it, so the network path is yours to choose:
- Tailscale — put your DB behind a tailnet and point
database.urlat the tailnet host. No public DB port. - Short-lived credentials —
${DATABASE_URL}is expanded at load, so you can inject an ephemeral token (RDS IAM auth, Cloud SQL IAM, a Vault dynamic user) instead of a static password. - Railway / managed PaaS — use the provided TLS endpoint with a dedicated read-only role.
Roadmap
- Postgres wire-protocol frontend — so
psql, BI tools, and any client (not just MCP) get the same guard + redaction. The pipeline is already frontend-agnostic. - More engines — MySQL, SQLite (the guard's parser is the only Postgres-specific piece; it's a pluggable backend).
- Schema-aware lineage — resolve aliased PII columns back to their source table.
Development
uv venv && source .venv/bin/activate
uv pip install -e '.[dev]'
pytest
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dbveil-0.1.0.tar.gz.
File metadata
- Download URL: dbveil-0.1.0.tar.gz
- Upload date:
- Size: 17.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0f1d4f53fbd8d1f9b8c33ae86ef90e14eb1a2346ee06744417c4f76e6b6fc7a
|
|
| MD5 |
0527e5e8cb14f89b7eb080d84a9aa880
|
|
| BLAKE2b-256 |
89ab1ab8a9ba8ddabd69c1c3d45c9da5f61967b49c38dc195bb3d69125974bb8
|
Provenance
The following attestation bundles were made for dbveil-0.1.0.tar.gz:
Publisher:
publish.yml on mathu97/dbveil
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dbveil-0.1.0.tar.gz -
Subject digest:
f0f1d4f53fbd8d1f9b8c33ae86ef90e14eb1a2346ee06744417c4f76e6b6fc7a - Sigstore transparency entry: 1796848074
- Sigstore integration time:
-
Permalink:
mathu97/dbveil@0b3f24e8372d46340a35030d281e9cf2434d2380 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mathu97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0b3f24e8372d46340a35030d281e9cf2434d2380 -
Trigger Event:
release
-
Statement type:
File details
Details for the file dbveil-0.1.0-py3-none-any.whl.
File metadata
- Download URL: dbveil-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0fc9acfca57f32b3a59eb5d23007ed6cbf357f2c7c5fb2016bada7d183754c05
|
|
| MD5 |
804d14d3a367e3150e509d4ca9ecb029
|
|
| BLAKE2b-256 |
c2b183fa74847b5c9b50be394d590b18adbeb0c23b7ec1edf7a71c937d3e5131
|
Provenance
The following attestation bundles were made for dbveil-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on mathu97/dbveil
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dbveil-0.1.0-py3-none-any.whl -
Subject digest:
0fc9acfca57f32b3a59eb5d23007ed6cbf357f2c7c5fb2016bada7d183754c05 - Sigstore transparency entry: 1796848229
- Sigstore integration time:
-
Permalink:
mathu97/dbveil@0b3f24e8372d46340a35030d281e9cf2434d2380 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mathu97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0b3f24e8372d46340a35030d281e9cf2434d2380 -
Trigger Event:
release
-
Statement type: