# kv — Design Document

## What

Encrypted secrets management for developers and AI coding agents.
Local-first, CLI-first. On PyPI. Website at kvsecure.com.

The core problem: AI agents need API keys to do real work, but giving them
the keys is dangerous. kv solves this with a daemon that holds secrets in
memory and lets agents USE keys without POSSESSING them.

## Why

- dotenvx = encryption only, no AI agent story
- Doppler = $21/user, cloud-only, no AI agent story
- 1Password = enterprise, SDK gives agent plaintext secrets
- AgentSecrets = Go, HTTP proxy, no subprocess support
- **kv's gap**: MCP-native daemon + passphrase barrier + subprocess support + zero dependencies

## The Security Model

Two channels, not equal:

- **kv_api** (primary) — daemon makes the HTTP call. The API key never enters any
  other process. Same model as ssh-agent: key can only be used for its purpose,
  never exported. Provider registry constrains destinations.

- **kv_run** (fallback) — for processes that genuinely need credentials in env vars
  (databases, cloud SDKs, non-HTTP protocols). Sandbox layers: command validation,
  network isolation, output redaction, file scanning. Defense-in-depth, not containment.

The foundational security layer is the **passphrase** — an interface barrier that
agents cannot bypass (they can't enter interactive input). With a passphrase-protected
vault, kv is client-agnostic: Claude Code, Cursor, any MCP client — same security.

## Encryption Architecture

- **Master key**: 32 random bytes, optionally wrapped with passphrase (PBKDF2 600K + ChaCha20)
- **Per-env keys**: Derived via BLAKE2b keyed hash — no per-env key storage
- **Cipher**: ChaCha20-Poly1305 with 12-byte random nonce + environment name as AAD
- **Storage**: Entire secrets dict encrypted as single JSON blob (no key name leakage)
- **2FA**: Optional TOTP (RFC 6238), secret encrypted with passphrase

## Storage Format

```
.secrets/
  key             # Master key (plaintext or passphrase-wrapped). NEVER committed.
  config.json     # Project metadata, security policy, allowlists
  dev.enc         # KV\x00 + version(1) + nonce(12) + ciphertext+tag
  staging.enc
  .gitignore      # Auto-generated (key excluded, .enc safe to commit)
```

## Package Structure

```
kv/                       # CLI + core library (on PyPI as kv-secrets)
  __init__.py             # Version (0.1.1)
  __main__.py             # Entry point
  cli.py                  # 30 commands, argparse, ANSI output
  cli_remote.py           # Remote commands (login, push/pull, team, token)
  crypto.py               # ChaCha20, BLAKE2b, PBKDF2 wrapping, TOTP, key sharing
  store.py                # .enc format, secret CRUD, atomic writes
  config.py               # Project init, config.json, security policy
  agent.py                # Daemon — holds secrets, TCP localhost, sandbox enforcement
  sandbox.py              # Command validation, network isolation, redaction, file scanning
  hook.py                 # Claude Code PreToolUse/PostToolUse hooks
  providers.py            # API provider registry, auth building, shared HTTP call logic
  env.py                  # .env import/export
  auth.py                 # Session management (~/.kv/session.json)
  remote.py               # HTTP client for cloud sync API
  sync.py                 # Push/pull orchestration

kv_mcp/                   # MCP server (separate package, same wheel)
  __init__.py
  __main__.py             # Entry point with --allow-mutate, --allow-reveal flags
  server.py               # JSON-RPC 2.0 lifecycle, profile enforcement
  protocol.py             # Message framing (newline-delimited JSON on stdio)
  tools.py                # 7 MCP tools across 3 profiles
```

## CLI Commands (30)

```
# Vault management
kv init                              Initialize project (passphrase prompt)
kv unlock [-b]                       Open vault (start daemon)
kv lock                              Close vault (stop daemon)
kv upgrade-security                  Add passphrase to plaintext vault
kv setup-2fa                         Enable TOTP 2FA

# Secrets
kv set KEY[=VALUE] [-e ENV]          Store (interactive or inline)
kv get KEY [-e ENV]                  Decrypt and print
kv ls [-e ENV] [--reveal]            List keys (values hidden by default)
kv rm KEY [-e ENV] [-f]              Remove
kv run COMMAND [-e ENV]              Run with secrets injected (requires daemon)
kv export [-e ENV] [-o FILE]         Export as .env
kv import FILE [-e ENV]              Import .env file

# Environments
kv envs                              List all environments
kv env create NAME                   Create environment
kv env copy SRC DST                  Copy secrets between environments

# Security policy
kv allow [PATTERN]                   Add/list allowlisted commands for kv_run
kv deny PATTERN                      Remove from allowlist

# AI editor setup
kv setup EDITOR                      Auto-configure MCP (cursor, claude-code, vscode)
kv mcp                               Show MCP config JSON

# Project health
kv status                            Vault status
kv doctor                            Run diagnostics
kv version                           Print version

# Key sharing
kv export-key                        Export master key as kvkey_ token
kv import-key TOKEN                  Import shared key

# Remote (server not yet deployed)
kv signup / login / logout           Account management
kv push / pull [-e ENV] [--all]      Sync encrypted blobs
kv remote                            Show sync status
kv team create/invite/members/revoke Team management
kv token create/list/revoke          CI/CD API tokens
```

## MCP Tools (7)

| Tool | Profile | Description |
|------|---------|-------------|
| kv_status | safe | Project status |
| kv_envs | safe | List environments |
| kv_list | safe | List secret names (no values) |
| kv_run | safe | Run command with secrets injected (sandboxed) |
| kv_api | safe | HTTP API call with credentials injected by daemon |
| kv_set | mutate | Store/update a secret |
| kv_get | reveal | Get secret value (WARNING: plaintext to agent) |

## Agent Daemon

Like ssh-agent for API keys. Holds decrypted secrets in memory. Communicates
via TCP on localhost (127.0.0.1, per-project port derived from project hash).

**Commands:** run, api, list, status, envs, check_file, tracked_files, secret_names, check_leak

**Security layers:**
1. PR_SET_DUMPABLE=0 — prevents ptrace / /proc/PID/mem reads
2. Command validation — allowlist or blocklist before execution
3. Network isolation — `unshare --user --net` (fallback: `bwrap --unshare-net`)
4. Output redaction — secret values replaced with [REDACTED]
5. File scanning — detect leaked secrets post-execution (plaintext, base64, hex, reversed)
6. Network policy — `network_commands` in config, not controlled by caller

## Key Decisions

1. **Single blob per env** — no key name leakage, atomic reads/writes
2. **BLAKE2b for key derivation** — master key is high-entropy, no slow KDF needed
3. **Daemon required for kv run** — all sandbox layers enforced, no unsafe fallback
4. **kv_api = ssh-agent model** — key can only make HTTP calls, never be exported
5. **Passphrase = client-agnostic security** — interface barrier agents can't bypass
6. **Network isolation by config, not by caller** — user decides, not the agent
7. **Provider registry constrains kv_api** — can't redirect to attacker-controlled servers
8. **Hooks are defense-in-depth** — Claude Code specific, not the primary security gate

## Monetization

- **Free**: Full local CLI, MCP server, daemon, encryption, multi-environment
- **$15/team/month**: Cloud sync, team management, CI/CD tokens, RBAC (coming soon)
- Website: kvsecure.com (landing page live, team waitlist active)
- Undercuts Doppler ($21/user x 5 = $105/mo) with per-team pricing

## Product Architecture

```
                    kvsecure.com (landing page)
                           │
                    ┌──────┴──────┐
                    │  Cloud API   │  (not yet deployed)
                    │  Zero-knowledge sync
                    └──────┬──────┘
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
   CLI (pip)          MCP Server          Claude Code Hooks
   30 commands        7 tools             PreToolUse/PostToolUse
        │                  │                  │
        └──────────────────┼──────────────────┘
                           │
                    ┌──────┴──────┐
                    │ Agent Daemon │
                    │ (ssh-agent   │
                    │  model)      │
                    └──────┬──────┘
                           │
                    ┌──────┴──────┐
                    │ Encrypted    │
                    │ Vault        │
                    │ ChaCha20     │
                    └─────────────┘
```

## Gotchas Found During Build

- `argparse.REMAINDER` field must not collide with subparser `dest` field name
- `subprocess.run` needs list args (not joined string) to preserve quoting
- Optional flags (`-e`, `-q`) MUST come before REMAINDER positional in parser
- TOTP test can flake on 30-second boundary — timing-sensitive
- Same-user security is advisory, not containment (engram LRN-012)
- Output redaction doesn't prevent filesystem exfiltration (engram MST-011)
