Blind Data Vault — privilege-separated secret vault for AI agents
Project description
BDV (Blind Data Vault)
A privilege-separated, encrypted secret vault that lets AI agents verify, send, and act on sensitive data without ever seeing it.
The daemon runs in a Docker container. The AI agent talks to it via Unix socket or TCP. Even if the agent is fully compromised, it cannot decrypt secrets as the privilege boundary is at the OS/container level.
pip install bdv # client only
docker compose up -d # full daemon
For the paradigm rationale, see WHITEPAPER.md. For design decisions, see ARCHITECTURE.md.
Quick Start
Docker
docker compose up -d
docker exec bdv bdv list
docker exec bdv bdv write ssn --value "123-45-6789" --type numeric --desc "US SSN"
docker exec bdv bdv check ssn /data/inbox/form.pdf
pip
The client connects to a running BDV daemon. Start the daemon first (via Docker or manual setup), then:
pip install bdv
bdv list
bdv write ssn --value "123-45-6789" --type numeric --desc "US SSN"
TCP
# Daemon: BDV_SERVER_TCP_ENABLED=true BDV_SERVER_TCP_TOKEN=mytoken
bdv --tcp localhost:9652 --token mytoken list
What It Does
The agent sends commands. The daemon handles all crypto, file reading, and pattern matching internally. The agent only receives redacted results.
| Command | Parameters | What the agent gets back |
|---|---|---|
list |
Secret names + descriptions (never values) | |
check_document |
secret name, file path | MATCH/NO MATCH with redacted context |
send |
template, subject | "Sent. 2 secrets injected." (never the content) |
write |
name, value, type | "Encrypted and stored." |
delete |
name | "Deleted." |
describe |
name, description | "Updated." |
Secret Types
| Type | What it does | Examples |
|---|---|---|
numeric |
Pattern-based sweep with anomaly report | SSN, routing numbers, credit cards |
exact |
Case-sensitive exact match | Email addresses, API keys |
text |
Case-insensitive with component decomposition | Names, addresses |
Namespaced Secrets
Store multiple values under a single name using colon syntax:
bdv write email:personal --value "me@gmail.com" --type exact --desc "Personal"
bdv write email:work --value "me@corp.com" --type exact --desc "Corporate"
# Check all values at once
bdv check email /data/inbox/form.pdf
# RESULT: Checked 2 values under 'email'. 1 found, 1 not found.
# Or a specific label
bdv check email:work /data/inbox/form.pdf
# Delete one label or the entire group
bdv delete email:work # one label
bdv delete email # all values
Flat secrets (no colon) work exactly as before.
Supported Document Formats
PDF (text + scanned via OCR), images (Tesseract), plain text (txt/md/csv/json/xml).
Python Client
from client import BDVClient
# Unix socket (default: /run/bdv/bdv.sock)
client = BDVClient()
# Or TCP
client = BDVClient(host="localhost", port=9652, token="...")
client.list()
client.write("ssn", "123-45-6789", description="US SSN", secret_type="numeric")
client.check_document("ssn", "/data/inbox/form.pdf")
client.send(template="Your SSN is {{VAULT:ssn}}.", subject="Details")
client.delete("ssn")
client.describe("ssn", "Updated description")
Configuration
Three layers: defaults → YAML → env vars (env wins).
cp bdv.example.yml bdv.yml # see all options
| Variable | Default | Description |
|---|---|---|
BDV_CONFIG |
Path to YAML config file | |
BDV_SERVER_SOCKET_PATH |
/run/bdv/bdv.sock |
Unix socket path |
BDV_SERVER_TCP_ENABLED |
false |
Enable TCP listener |
BDV_SERVER_TCP_HOST |
127.0.0.1 |
TCP listen address |
BDV_SERVER_TCP_PORT |
9652 |
TCP port |
BDV_SERVER_TCP_TOKEN |
Required auth token for TCP | |
BDV_STORAGE_VAULT_DIR |
/data/secrets |
Encrypted secret storage |
BDV_STORAGE_INBOX_DIR |
/data/inbox |
Write-only document inbox |
BDV_STORAGE_OUTBOX_DIR |
/data/outbox |
Output directory (file channel) |
BDV_OUTPUT_TYPE |
file |
Output channel: file, smtp, webhook |
BDV_PLUGINS_DIR |
/plugins |
Directory for plugin .py files |
BDV_SOCKET |
Client-side override for the socket path (alternative to --socket) |
|
BDV_TOKEN |
Client-side override for the TCP auth token (alternative to --token) |
Extensibility
Custom commands
New agent-invocable actions can be added as plugins. Drop a .py file into the plugins directory and mount it as a volume. No fork or rebuild needed.
# docker-compose.yml
volumes:
- ./plugins:/plugins:ro
Each plugin defines a COMMANDS dict mapping command names to handlers:
# plugins/fill_form.py
from daemon.crypto import decrypt
from daemon.config import BDVConfig
from daemon.channels.base import OutputChannel
from pathlib import Path
def cmd_fill_form(params, config, channel):
vault_dir = Path(config.storage.vault_dir)
key_file = vault_dir / ".vault-key"
secret = decrypt(params["name"], vault_dir, key_file).strip()
# ... fill the form, write to outbox ...
return "Form filled. 3 fields populated. Output written to outbox."
COMMANDS = {"fill_form": cmd_fill_form}
The daemon loads plugins on startup. Built-in commands cannot be overridden.
Custom output channels
New delivery mechanisms for the send command work the same way. Subclass OutputChannel and export a CHANNELS dict:
# plugins/s3_channel.py
from daemon.channels.base import OutputChannel
class S3Channel(OutputChannel):
def __init__(self, config):
self.bucket = "my-vault-output"
def send(self, body, subject, *, original_message_id=""):
# ... upload to S3 ...
return f"Uploaded to s3://{self.bucket}/..."
CHANNELS = {"s3": S3Channel}
Then set BDV_OUTPUT_TYPE=s3 to use it. Built-in channels (file, smtp, webhook) cannot be overridden.
Available imports for plugins
Plugins run inside the daemon process, so all daemon.* modules are available. For local development outside the container, pip install bdv[daemon] makes these importable.
| Import | What it provides |
|---|---|
daemon.crypto.decrypt(name, vault_dir, key_file) |
Decrypt a secret by name |
daemon.crypto.encrypt(name, value, vault_dir, key_file) |
Encrypt and store a secret |
daemon.crypto.secret_path(name, vault_dir) |
Resolve a name to its .age file path |
daemon.document.extract_text(file_path) |
Extract text from PDF, image, or text file |
daemon.patterns.derive_patterns(name, secret) |
Auto-derive regex from a secret value |
daemon.patterns.build_context_window(...) |
Build a redacted context window around a match |
daemon.patterns.normalize_secret(secret) |
Strip delimiters from a secret for comparison |
daemon.config.BDVConfig |
Config dataclass (passed as config param) |
daemon.channels.base.OutputChannel |
Base class for output channels (passed as channel param) |
Every handler receives (params: dict, config: BDVConfig, channel: OutputChannel) and returns a str. The return value is sent back to the agent, so it must never contain plaintext secrets.
Security Model
| Threat | Mitigation |
|---|---|
| Prompt injection exfiltrates secrets | Agent can't decrypt (wrong container/user) |
Agent runs cat on vault files |
Permission denied (OS-level) |
| Malicious regex (ReDoS) | Patterns derived from trusted vault data only |
| Secrets leak into logs/memory/vector DB | Values never enter agent context |
| Adjacent PII in context windows | Aggressive redaction of 3+ digit sequences |
See WHITEPAPER.md for the paradigm rationale and FAQ.
Security Disclaimer
BDV protects secrets from the AI agent. It is not a general-purpose secrets management solution.
What it does NOT cover:
- Physical server security. Anyone with root/physical/disk access to the host can extract the private key. If you run on a VPS, your hosting provider has theoretical disk access.
- Transport security. The daemon protects secrets from the LLM, not during delivery to the end user. Use TLS, VPN (Tailscale/WireGuard), or PGP encryption for transport.
- Backup security. If your backup system captures the vault directory in plaintext, the secrets are exposed. Encrypt backups independently.
- Memory forensics. During active decryption (a few milliseconds per operation), the plaintext exists in the daemon's process memory.
Recommendation: Run BDV on a dedicated machine (not a shared VPS) behind a VPN mesh like Tailscale, with full-disk encryption (LUKS) enabled.
Requirements
Docker (recommended): All dependencies included in the image.
Manual: Python 3.10+, age, pdfplumber, pytesseract + tesseract-ocr, pdf2image + poppler-utils.
Client only: Python 3.10+, click (pip install bdv).
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 bdv-0.2.0.tar.gz.
File metadata
- Download URL: bdv-0.2.0.tar.gz
- Upload date:
- Size: 34.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
777e49c5735618aed43a258586b3b110a083091036c8c72e9646bc74d56ea862
|
|
| MD5 |
1164ff2c6b1512cd7b9fb1d102885d34
|
|
| BLAKE2b-256 |
c804432ea8c070d8a7276206c6bc4041ea9fd27b72dbf01f16e5fad8a0c23dad
|
Provenance
The following attestation bundles were made for bdv-0.2.0.tar.gz:
Publisher:
release.yml on calvinsienatra/bdv
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bdv-0.2.0.tar.gz -
Subject digest:
777e49c5735618aed43a258586b3b110a083091036c8c72e9646bc74d56ea862 - Sigstore transparency entry: 1154397831
- Sigstore integration time:
-
Permalink:
calvinsienatra/bdv@e2c733300ffca118b6289511646aa0d8dbda017d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/calvinsienatra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e2c733300ffca118b6289511646aa0d8dbda017d -
Trigger Event:
push
-
Statement type:
File details
Details for the file bdv-0.2.0-py3-none-any.whl.
File metadata
- Download URL: bdv-0.2.0-py3-none-any.whl
- Upload date:
- Size: 29.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4d799d064f4e145159930e73e438818df6abad8c47fde7cce2044a3ae2db68f
|
|
| MD5 |
9c3effcce4e488e60528cd067ffcbdae
|
|
| BLAKE2b-256 |
f36fe2592eb260e91f377c2fc3af9290bce6d68da0e1e66ca2b13ac40bb391d9
|
Provenance
The following attestation bundles were made for bdv-0.2.0-py3-none-any.whl:
Publisher:
release.yml on calvinsienatra/bdv
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bdv-0.2.0-py3-none-any.whl -
Subject digest:
a4d799d064f4e145159930e73e438818df6abad8c47fde7cce2044a3ae2db68f - Sigstore transparency entry: 1154397833
- Sigstore integration time:
-
Permalink:
calvinsienatra/bdv@e2c733300ffca118b6289511646aa0d8dbda017d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/calvinsienatra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e2c733300ffca118b6289511646aa0d8dbda017d -
Trigger Event:
push
-
Statement type: