Governed storage for encrypted data. Drop it next to Micro. Shrink your PCI scope.
VibeSQL Vault is a hardened storage service for encrypted data. It doesn't encrypt. It doesn't decrypt. It stores opaque blobs that were encrypted somewhere else — Azure Key Vault, your own crypto stack, doesn't matter — and governs access to them.
Think safety deposit box, not locksmith.
Paired with VibeSQL Micro, it becomes the smallest possible PCI cardholder data environment: one ~22MB database binary, one ~10MB vault API. Two processes, governed storage, complete audit trail. That's your entire CDE.
Current release: v0.1.0 — Linux x86_64 musl static-pie binary. See docs/logging.md for the pluggable-logging pattern (Graylog, Fluent, CloudWatch, syslog, journald, K8s).
Traditional PCI CDE:
App servers + load balancers + database cluster + key management
+ encryption middleware + audit logging + network segmentation
= months of scoping, dozens of components in scope
VibeSQL CDE:
┌─────────────────────────────────────┐
│ vsql-vault pod │
│ │
│ ┌───────────────┐ │
│ │ vsql-vault │ Rust binary, │
│ │ API │ FROM scratch, │
│ │ (port 8443) │ ~10MB │
│ └───────┬───────┘ │
│ │ │
│ ┌───────┴───────┐ │
│ │ VibeSQL Micro │ 77MB binary, │
│ │ (port 5432) │ pod-internal │
│ └───────────────┘ │
└─────────────────────────────────────┘
= two binaries, one pod, minimal attack surface
Micro's port is not exposed outside the pod. Only the vault API talks to it.
Why this matters for compliance: PCI DSS scopes every system that stores, processes, or transmits cardholder data. Fewer systems in scope = less audit surface = faster certification. Micro + Vault is the minimum viable CDE.
Store:
Your Encryption Stack vsql-vault
(Azure KV, PayEz Encryption, (governed storage)
any crypto you trust)
plaintext → encrypt → blob ──► PUT /vault/{purpose}/{id}
+ metadata, ownership,
retention policy, access
contract
Retrieve:
GET /vault/{purpose}/{id} ──► blob → decrypt → plaintext
(only if caller is (your crypto stack
authorized per contract) handles decryption)
vsql-vault never sees plaintext. It never parses the blob. It doesn't care what algorithm was used. The blob is opaque bytes with metadata.
- Access policies. Who stored this? Who can retrieve it? Under what contract?
- Retention policies. When does it expire? What's the max retention? Auto-purge scheduling.
- Audit trail. Every store, retrieve, and purge is logged with caller identity, timestamp, and grant/deny.
- Purge proof. SHA-256 hash of the entry at time of deletion. Cryptographic proof it existed and was destroyed.
- Compliance evidence. Point-in-time snapshots for your QSA: entries by purpose, key staleness, purge compliance, access summary.
./vibesql-micro
# Micro is running on :5432# vsql-vault.toml
[server]
listen_addr = "0.0.0.0:8443"
[storage]
database_url = "postgresql://vsql_vault@localhost:5432/vault"
[auth]
api_key_header = "X-Vault-Key"
api_key_env = "VSQL_VAULT_API_KEY"
[purge]
sweep_interval_hours = 24
purge_proof_hash = trueVSQL_VAULT_API_KEY=your-secret-key ./vsql-vault --config vsql-vault.toml
# Vault API is running on :8443curl -X PUT http://localhost:8443/admin/retention-policies/card \
-H "X-Vault-Key: your-secret-key" \
-H "Content-Type: application/json" \
-d '{
"max_retention_days": 365,
"default_ttl_days": 90,
"purge_method": "physical-delete",
"require_purge_proof": true,
"description": "Card tokens — max 1 year, default 90 days"
}'# Your encryption stack encrypts the data first.
# Then store the opaque ciphertext in the vault.
curl -X PUT http://localhost:8443/vault/card/payment-12345 \
-H "X-Vault-Key: your-secret-key" \
-H "Content-Type: application/json" \
-d '{
"encrypted_value": "vault:v1:AES256-GCM:keyid=7:base64ciphertext...",
"algorithm_hint": "AES-256-GCM",
"key_ref": "akv:payez-kv:card-key-v7",
"metadata": { "merchant_id": 42 },
"expires_at": "2027-02-16T00:00:00Z",
"access_policy": "payment-service-only"
}'curl http://localhost:8443/vault/card/payment-12345 \
-H "X-Vault-Key: your-secret-key"
# Returns the opaque blob + metadata. Your app decrypts it.| Method | Path | Purpose |
|---|---|---|
PUT |
/vault/{purpose}/{external_id} |
Store an encrypted blob |
GET |
/vault/{purpose}/{external_id} |
Retrieve (if authorized) |
DELETE |
/vault/{purpose}/{external_id} |
Manually purge an entry |
HEAD |
/vault/{purpose}/{external_id} |
Check existence + expiry (no blob) |
GET |
/vault/{purpose} |
List entries for a purpose (metadata only) |
PUT |
/admin/retention-policies/{purpose} |
Create/update retention policy |
PUT |
/admin/access-policies/{name} |
Create/update access policy |
GET |
/admin/access-log |
Query access log |
GET |
/admin/purge-log |
Query purge log |
POST |
/admin/purge/sweep |
Trigger manual purge sweep |
GET |
/compliance/report |
PCI Req 3 compliance evidence snapshot |
GET |
/health |
Health check |
GET |
/metrics |
Prometheus metrics |
Who can store and retrieve entries.
| Policy | Store | Retrieve | Description |
|---|---|---|---|
owner-only |
Any authenticated | Only the owner_app that stored it |
Default — only the storer can retrieve |
same-purpose |
Any authenticated | Any app with the same purpose | Shared access within a purpose |
open-retrieve |
Any authenticated | Any authenticated caller | Store controlled, retrieve open |
admin-only |
Admin apps | Admin apps | Locked down for regulatory data |
Custom policies define rules per operation with allowed_apps, require_identity, and max_retrievals.
How long data can be stored. What happens when it expires.
| Enforcement | What Happens |
|---|---|
Store request exceeds max_retention_days |
Rejected with 422 |
No expires_at provided |
Set to NOW() + default_ttl_days |
| Entry expires | Purge sweep deletes it per purge_method |
| Retrieve expired entry | Returns 410 Gone |
| Method | Action | Proof |
|---|---|---|
physical-delete |
Row deleted. SHA-256 proof written to purge_log. |
Hash of entry at time of purge |
crypto-shred |
Value zeroed in place. Row kept with purged_at set. |
Zeroed value + purge log entry |
retention-expire |
Same as physical-delete, reason marked as retention policy. | Purge log with policy reference |
Vault covers the storage governance portions of Req 3. Encryption and key management are out of scope — those are the responsibility of the upstream encryption service (e.g., a CryptAply plugin, Azure Key Vault, your own crypto stack).
| PCI Req | Requirement | Vault Control |
|---|---|---|
| 3.1.1 | Data retention policies | Retention policies per purpose with max_retention_days and enforced TTL |
| 3.1.2 | Data limited to what is needed | Purpose-scoped storage with expiry. Purge sweep enforces removal. |
| 3.3.2 | PAN rendered unreadable | Vault stores already-encrypted values. Never sees plaintext. Opacity guaranteed. |
| 3.5.1.2 | Restrict access to stored data | Per-entry access policies. Every retrieve logged with grant/deny and caller identity. |
Not in scope for vsql-vault: Key management (3.5.1, 3.6, 3.7), encryption algorithms, key rotation, key lifecycle. Those controls belong to the upstream encryption service.
GET /compliance/report generates a point-in-time snapshot:
{
"summary": {
"total_entries": 12847,
"active_entries": 11203,
"expired_pending_purge": 44,
"purged_last_30_days": 1600
},
"by_purpose": {
"card": {
"active": 8200,
"retention_policy": "365 days max, 90 day default TTL",
"entries_expiring_next_30_days": 320
}
},
"purge_compliance": {
"purge_sweep_last_run": "2026-02-16T04:00:00Z",
"entries_purged_last_sweep": 12,
"purge_proof_available": true
},
"access_summary": {
"total_accesses_last_30_days": 45000,
"denied_accesses_last_30_days": 23
}
}Storage governance evidence for your QSA — retention compliance, purge proof, access audit.
| Layer | Control |
|---|---|
| Container | FROM scratch — no shell, no package manager, no utilities |
| Network | Pod-internal PostgreSQL. Only vault API port exposed. TLS required. |
| Storage | VibeSQL Micro on dedicated disk. Not shared with application data. |
| Auth | mTLS, JWT with JWKS validation, or API key. No anonymous access. |
| Authorization | Per-entry access policies. Every operation logged. |
| Memory | Rust — no GC, zeroize for sensitive buffers, mlock to prevent swap |
| Audit | Every operation logged to vault.access_log. Immutable. |
| Operation | Target |
|---|---|
| Store (PUT) | < 5ms |
| Retrieve (GET) | < 3ms |
| HEAD (exists check) | < 2ms |
| Purge sweep (1000 entries) | < 10s |
| Compliance report | < 5s |
vault.entries — Encrypted blobs with metadata, TTL, access policy
vault.access_log — Every store/retrieve/purge logged with caller + grant/deny
vault.access_policies — Named access contracts (who can store/retrieve/purge)
vault.retention_policies — Per-purpose TTL, max retention, purge method
vault.purge_log — Proof of deletion (SHA-256 hash, method, reason)
┌────────────┐ ┌────────────┐ ┌──────────┐ ┌──────────────┐
│ VibeSQL │ │ VibeSQL │ │ VibeSQL │ │ VibeSQL │
│ Micro │ │ Vault │ │ Audit │ │ Edge │
│ (database) │ │ (governed │ │ (Req 10) │ │ (auth) │
│ 77MB │ │ storage) │ │ │ │ │
└────────────┘ └────────────┘ └──────────┘ └──────────────┘
│ │ │ │
└───── Micro + Vault = minimal CDE ────────────┘
- VibeSQL Micro — Single-binary PostgreSQL. Dev tool and CDE companion.
- VibeSQL Server — Production multi-tenant PostgreSQL server
- VibeSQL Edge — Authentication gateway
- VibeSQL Audit — PCI DSS compliant audit logging (Req 10)
- Vibe SDK — TypeScript ORM with live schema sync
Apache 2.0 License. See LICENSE.