Encrypted Sharing · Zero-Knowledge Vault
Technical Documentation

Wundervault Security White Paper

Version 1.1 • May 2026

Abstract

Wundervault is a zero-knowledge secret sharing and persistent vault system. This document describes the cryptographic model, threat model, key management architecture, agent access control system, and the security properties that follow. The core design principle is simple: the server handles storage and routing but is architecturally incapable of reading secret content — for both ephemeral one-time secrets and persistent vault entries.


1. Threat Model

Wundervault is designed to be secure against the following threat classes:

1.1 Passive server compromise

An attacker who gains read access to the database (via SQL injection, backup exposure, or insider access) should be unable to recover plaintext secret content. This is the primary threat. Wundervault's client-side encryption model means a database dump contains only ciphertext, salts, nonces, and HMAC values — no plaintext, no keys.

1.2 Active server compromise

An attacker who can modify server responses could inject malicious JavaScript into the web application. This attack is partially mitigated by Content Security Policy headers. Subresource Integrity (SRI) ensures script files must match published hashes before executing (protects against cache/CDN tampering). SRI does not protect against an attacker who controls the origin server and can update both files and hashes — that remains the fundamental limitation of browser-based cryptography, documented in section 9.

1.3 Network interception

All production traffic is served over TLS. Because encryption occurs client-side before transmission, a passive network observer receives only ciphertext. An active TLS-terminating attacker who can serve a modified page falls under the active server compromise threat above.

1.4 Credential brute force

PBKDF2 with 600,000 iterations makes offline brute force of captured passphrase hashes computationally expensive. Rate limiting at the API layer prevents online brute force. Timing-safe comparisons prevent oracle attacks.

1.5 Agent credential theft

Agent API keys are provisioned via Wundervault's own one-time secret mechanism and burn after first use, preventing replay. The server stores only an HMAC of the API key — not the key itself — so a database breach cannot be used to reconstruct valid credentials. Revocation is immediate at the API layer.

1.6 Agent SSH access and host-level secret exposure

When an agent is granted SSH access to a remote machine (via a key path accessible on the agent's filesystem), the agent effectively has general shell access to that host. This means the agent can read any file accessible to the SSH user — including .env files, config files, and other plaintext secrets — and may inadvertently or intentionally leak their contents into conversation history or logs.

This is a known architectural limitation: vault-level controls cannot restrict what an agent reads once it has general shell access. Mitigations:

Out of scope: Endpoint compromise of the user's device (malware, keyloggers, screen capture after reveal). Wundervault cannot protect secrets already displayed on a compromised device. General shell access granted to agents via SSH keys stored outside the vault.


2. Encryption Architecture

2.1 Primitives

2.2 One-Time Secrets

The one-time secret flow is fully server-blind:

  1. The user enters plaintext in the browser.
  2. The browser generates a random 16-byte salt and 12-byte nonce via crypto.getRandomValues().
  3. A random passphrase is generated client-side.
  4. The passphrase is processed through PBKDF2-HMAC-SHA256 (600,000 iterations, per-secret salt) to derive a 256-bit AES-GCM key.
  5. The plaintext is encrypted with AES-256-GCM using the derived key and random nonce.
  6. The browser computes a one-way verifier = SHA-256(content_key).
  7. The server receives and stores: {ciphertext, salt, nonce, verifier, ttl}. It never receives the passphrase, the derived key, or the plaintext. The verifier is one-way — it cannot be reversed to the key.
  8. The link and passphrase are delivered to the recipient via separate channels.
  9. On retrieval the recipient's browser fetches the per-secret salt, re-derives the content key from the passphrase, and sends only the recomputed verifier. The server does a timing-safe comparison against the stored verifier, atomically marks the secret burned, and returns the ciphertext bundle — which the browser decrypts locally. The server never sees the passphrase or key at any point.

The server is cryptographically incapable of decrypting one-time secrets: it holds only ciphertext and a one-way verifier, never key material. Recovering a secret from a database leak requires brute-forcing the unlock code through the full KDF (600,000-iteration PBKDF2, or GPU-resistant Argon2id) — the verifier provides no shortcut. The one residual exposure is a deliberately weak user-chosen custom code; randomly generated codes are not feasibly brute-forceable, and custom codes are strength-checked client-side.

2.3 Persistent Vault

The vault introduces a vault key K — a random 32-byte key generated client-side during account setup. All vault secret values are encrypted with K before being stored.

The server never receives K. Instead, authentication works via a zero-knowledge proof chain:

  1. On setup, the browser derives an account secret (128-bit random value stored in localStorage, never sent to server) and runs PBKDF2(passphrase, salt, 600,000) → base_key.
  2. Then: HKDF-Extract(salt=account_secret, ikm=base_key) → PRK (pseudorandom key).
  3. Then: HKDF-Expand(PRK, "wundervault-auth-v1") → auth_key and HKDF-Expand(PRK, "wundervault-enc-v1") → vault_enc_key.
  4. The server stores SHA256(auth_key) — a one-way hash that cannot be reversed to recover the passphrase or account secret.

Account Secret: A 128-bit random value generated at registration and stored in your browser's localStorage. Never sent to the server. Even with the database and your passphrase, an attacker must enumerate 2^128 Account Secret possibilities — GPU cracking advantage is eliminated.

  • The server stores H(proof_key) — a SHA-256 hash of the proof key. This is the only passphrase-derived value the server ever sees.
  • On login, the browser re-derives proof_key and sends it. The server hashes it and compares against the stored hash — constant-time comparison via HMAC.
  • The vault key K is encrypted with the proof key (AES-GCM) and stored as encrypted_vault_key. The browser decrypts K locally using the proof key after authentication succeeds.
  • All vault secret values are encrypted with K before upload. The server stores only ciphertext.
  • A server breach exposes the hash of the proof key, not the proof key itself, and not K. Even with H(proof_key), an attacker cannot decrypt vault content — they would need proof_key to decrypt encrypted_vault_key, and K to decrypt vault secrets.

    2.4 Recovery Codes

    Recovery codes allow vault access if the passphrase is lost. Each recovery code contains 192 bits of entropy (128-bit Account Secret + 64-bit random padding) and is presented in Base32 format as 6 groups of 8 characters, hyphen-separated (e.g., ABCD-EFGH-IJKL-MNOP-QRST-UVWX). The vault key K is encrypted under the recovery code's derived key and stored server-side.

    Security properties:


    3. WebAuthn / Biometric Authentication

    Wundervault supports WebAuthn for passwordless login and as a second-factor gate for Tier 2 vault secrets.


    4. Agent Security Model

    4.1 Credential Provisioning

    When an agent is registered, Wundervault generates an API key and an encryption key for that agent. These are delivered via Wundervault's own one-time secret mechanism — a setup URL that burns after the onboard script retrieves it. The onboard script (onboard.py) verifies its own Ed25519 signature before executing, exchanges credentials with the vault server, and registers the agent profile with the local daemon. This means:

    4.2 Request Authentication

    Every agent API request must include:

    The server verifies the HMAC against the stored value using a constant-time comparison. This means authentication requires both the API key and the encryption key — possession of either alone is insufficient.

    4.3 Zero-Knowledge Agent Vault

    The agent vault uses double-layer encryption:

    1. The human user generates a vault key for each agent (agent_vault_key) client-side.
    2. agent_vault_key is encrypted with the agent's encryption_key (AES-GCM) and stored as vault_key_for_agent.
    3. Each secret value sent to the agent's vault is encrypted with agent_vault_key before upload.
    4. On retrieval, the agent decrypts vault_key_for_agent using its encryption_key to obtain agent_vault_key, then decrypts each secret value. The server participates in neither decryption step.

    The server stores only ciphertext it cannot decrypt. An agent database breach exposes ciphertext, vault_key_for_agent (which requires encryption_key to decrypt), and HMACs — not plaintext secret values.

    4.4 Scope Isolation

    4.5 Tier 2 Biometric Gate

    Vault secrets can be designated Tier 2. Agents calling GET /agent/vault/secrets/{id} on a Tier 2 secret receive 403 Forbidden unless the human owner has performed a WebAuthn biometric challenge in the dashboard within the current session. This creates a human-in-the-loop approval requirement that cannot be bypassed at the API layer.

    4.6 MCP Server Isolation (CIP-017)

    The @wundervault/mcp-server package provides MCP tool access with an additional isolation guarantee: secrets are decrypted inside the MCP server process and the plaintext is never returned to the agent. The tool returns only a confirmation string. This prevents secrets from appearing in conversation context, being logged by the agent runtime, or being included in model inputs.

    vault_exec — tier-based free-form command execution. Agents provide any shell command string. No pre-approved template list is required. Shell escape patterns ($(), backticks, bash -c, sh -c, eval) are hard-blocked before the secret is decrypted, eliminating prompt injection as a path to arbitrary execution. The injection recipe (env var name, optional setup and teardown commands) lives on the vault entry as exec_config, set once by the user in the dashboard — the agent never needs to know how to wire a credential. Tier 1 secrets execute freely; Tier 2 secrets are gated by the session lock.

    Subprocess isolation. The secret is injected as a named environment variable (never a command argument — it never appears in process listings). A fixed list of sensitive parent-process keys (ANTHROPIC_API_KEY, NODE_AUTH_TOKEN, RESTIC_PASSWORD, etc.) are stripped from the child environment so they cannot be inherited. The parent process copies the secret into a Buffer and calls buf.fill(0) in a finally block immediately after the subprocess spawns, zeroing the buffer bytes. Command output is scrubbed for the secret value before being returned to the agent.

    Known limitation. JavaScript strings are immutable — the plaintext string produced by TextDecoder.decode() prior to the Buffer copy, and the string passed to the child environment, remain in the V8 heap until garbage-collected. The buf.fill(0) zeroes the Buffer copy only. This is an inherent limitation of the Node.js runtime and is documented as an accepted residual risk. The threat model assumes an attacker requires process memory access to exploit this, at which point the process is already fully compromised.

    Tier 2 session lock. Tier 2 vault_exec calls lock after a configurable number of uses or minutes of inactivity (default: 3 uses or 5 minutes). Tier 1 executes freely with no lock. Session lock state is HMAC-signed on disk — a tampered state file is detected on load and resets to locked. Re-authentication via vault_session_unlock is required to resume Tier 2 execution.

    inject_env path allowlist. vault_entry_inject_env only writes secrets to a fixed allowlist of config file paths (~/.npmrc, ~/.netrc, ~/.docker/config.json, project .env files). Paths under /tmp and other temporary directories are rejected. This closes the side-channel where an agent could write a secret to a temp file and read it back with a separate file-read tool, effectively narrating the plaintext to the conversation.

    4.7 Revocation

    Revocation is immediate and enforced at the API layer on every request — there is no credential caching. A revoked agent receives 401 Unauthorized on its next request regardless of how recently it last authenticated. Individual secrets can also be removed from an agent's vault without revoking the agent entirely.

    4.8 Audit Log

    Every agent vault operation is recorded in an append-only audit log: agent identity, secret name, action (accessed, denied, registered, revoked), declared purpose, IP address, timestamp, and outcome. Audit log entries are never deleted, even if the agent or secret is subsequently removed.


    5. Network and Transport Security


    6. Anti-Abuse Controls

    6.1 Rate Limiting

    All sensitive endpoints are rate-limited per IP address via slowapi:

    EndpointLimit
    Secret creation (POST /api/v1/secrets)20 / minute / IP
    Secret retrieval (POST /api/v1/secrets/{id}/unlock)30 / minute / IP
    Agent secret creation30 / minute / IP
    Agent secret retrieval30 / minute / IP
    Login / registration20 / minute / IP
    WebAuthn challenge10 / minute / IP
    Recovery code use5 / minute / IP

    6.2 Timing Oracle Protection

    When a secret lookup fails (not found, already burned, or expired), the server performs a dummy HMAC comparison against a fixed random value. This ensures that the response time for "wrong passphrase" and "secret not found" are indistinguishable to an observer. An attacker cannot determine whether a secret ID exists by measuring response latency.

    6.3 Bot Protection

    The public-facing signup flow is protected by Cloudflare Turnstile — a privacy-preserving CAPTCHA alternative that does not require users to solve challenges but provides strong bot detection signal server-side.

    6.4 Automatic Expiry

    One-time secrets are automatically purged at TTL expiry (minimum 60 seconds, maximum 7 days) even if never retrieved. A background cleanup task runs hourly in addition to TTL-based deletion at retrieval time. Expired sessions are also purged on the same schedule.


    7. Data Storage

    7.1 What is stored

    DataStored asServer can decrypt?
    One-time secret contentAES-256-GCM ciphertext + salt + nonceNo
    Vault secret contentAES-256-GCM ciphertext (encrypted with vault key K)No
    Vault key KAES-256-GCM encrypted with proof_keyNo
    User passphraseSHA256(HKDF-Expand(HKDF-Extract(PBKDF2(passphrase), account_secret), "auth-v1"))No — server cannot reverse to passphrase or account_secret
    Account SecretNot stored — lives in browser localStorage only. Never transmitted, never in DB.N/A
    Agent API keyHMAC(encryption_key, api_key)No
    Agent encryption keyStored encrypted by local daemon in agent-profiles.enc (machine-id-derived key via HKDF); never stored server-sideNever exists server-side
    Agent vault keyAES-256-GCM encrypted with agent's encryption_keyNo
    WebAuthn credentialsPublic key + credential IDN/A — public key only
    Recovery codeFirst 8 chars of bcrypt hash (for lookup only)No (vault key still requires recovery code)

    7.2 What is never stored


    8. Incident Response and Breach Scenario

    In the event of a full database compromise:


    9. Known Limitations


    10. Compliance Notes

    Wundervault's zero-knowledge architecture is well-suited for environments requiring secrets to be shared without exposing them to intermediary infrastructure. The server never processes plaintext credential data, which reduces the exposure of sensitive data and simplifies certain technical controls — organizations subject to SOC 2, PCI DSS, or HIPAA should evaluate their full obligations independently.

    Audit logging of all agent access with declared purpose, IP, and timestamp supports compliance requirements for access control audit trails.


    Questions about this document or Wundervault's security model? Contact us →
    Wundervault Weekly

    A weekly dispatch on agentic AI, security tooling, and building in public. No fluff, no sponsored content. Opt out any time.

    Subscribe — it's free →