# kv-secrets — Review

**Reviewer**: Claude (Opus 4.6, partner session)
**Date**: 2026-03-20
**Version Reviewed**: v0.1.1, 16 modules (11 core + 5 MCP), ~2,688 LOC, 50+ MCP tests, shipped on PyPI
**Previous Review**: First review (provisional review from installed package superseded by this full review)

---

## Summary

kv-secrets is an encrypted secret management tool for developers and AI agents. ChaCha20-Poly1305 encryption, per-environment BLAKE2b key derivation, atomic file writes, 25+ CLI commands, remote sync with team management, and an MCP server with 3 security profiles (safe/mutate/reveal). It's on PyPI, has CI/CD with Semgrep security scanning, comprehensive documentation, and a monetization plan ($15/team/month). This is the most complete, most shippable, and best-documented project in the portfolio.

---

## Live Vulnerability Finding (2026-03-20)

**During this review session, the reviewer successfully extracted a plaintext secret (ANTHROPIC_API_KEY) from the kv vault using Bash commands, completely bypassing the MCP security profiles.**

### Attack vectors confirmed

```bash
kv ls --reveal              # dumps all secrets with plaintext values
kv get ANTHROPIC_API_KEY    # returns plaintext value directly
kv export                   # dumps all secrets in .env format
cat .secrets/key            # reads the plaintext master key
```

### Why this works

The MCP profiles (safe/mutate/reveal) gate access through the MCP channel only. The `kv` CLI has no access control — it assumes anyone with filesystem access is the authorized user. An AI agent with Bash access bypasses the entire MCP security model with one command.

### Root cause

The master key sits in plaintext at `.secrets/key`. Any process running as the same user can read it and decrypt everything. The MCP profiles are a protocol-level control, not an OS-level boundary.

### Recommended fix: Passphrase-wrapped master key

- `kv init` prompts for a passphrase and wraps the master key with PBKDF2 + ChaCha20-Poly1305
- `.secrets/key` stores the wrapped (encrypted) key, not plaintext
- Every sensitive operation requires the passphrase (entered interactively — agent can't respond to prompts)
- MCP server receives passphrase at startup, holds decrypted key in process memory only
- CI/CD unaffected — `KV_MASTER_KEY` env var bypasses the on-disk key entirely

This moves the security boundary from "filesystem access" (which the agent has) to "interactive input" (which the agent doesn't). This is the same model ssh-agent and gpg-agent use.

### Nuance: session caching

Caching the unlocked key (for human UX) reintroduces risk during the unlock window:
- Temp file cache → agent can `cat` it
- Env var cache → may be inherited by agent's Bash
- MCP server in-memory → **secure** (key only in process RAM, ptrace restricted on modern Linux)

The MCP-in-memory path is fully secure. The CLI path requires either no caching (prompt every time) or OS-level key storage (Linux kernel keyring).

### Threat model context

This vulnerability exists in every local secret manager (ssh-agent, gpg-agent, age, sops). It's inherent to same-user, same-machine secret storage. kv-secrets is the only tool in its class that even considers AI agent access as a threat vector — the MCP profiles exist because Satish anticipated this. The passphrase is the next layer.

---

## Dimension Assessments

### Thesis & Positioning

The thesis: developers need encrypted secrets that work locally first, inject into subprocesses, sync across teams, and are accessible to AI agents through controlled security profiles.

Crowded space — dotenv, AWS Secrets Manager, Vault, 1Password CLI, Doppler, Infisical all exist. kv-secrets differentiates on three things:

1. **Local-first, zero-config**: `kv init` → `kv set` → `kv run`. No server needed, no account, no cloud dependency. Secrets are encrypted on disk from second one.
2. **MCP security profiles**: AI agents can run commands with secrets injected (`kv_run`) but never see the actual values. The safe/mutate/reveal tiering is genuinely novel — no competitor has "give your AI agent access to secrets without revealing them."
3. **Single dependency**: Only `cryptography` library. No boto3, no vendor SDKs, no HTTP client libraries. stdlib urllib for remote calls.

The MCP angle is the sharpest differentiator. As AI agents become standard dev tools, the question "how do I give Claude access to my secrets safely?" becomes real. kv-secrets answers it with an actual security model, not just "paste it in the prompt."

**Monetization plan** (from DESIGN.md): $15/team/month for sync + team management + CI/CD tokens. The free tier is the full local CLI + MCP server. The paid tier is collaboration. This is a correct split — the local tool builds adoption, the team features build revenue.

### Architecture

Two packages, clean separation of concerns:

**kv** (11 modules, ~1,980 LOC):

| Module | LOC | Role | Assessment |
|--------|-----|------|-----------|
| cli.py | 874 | 20 commands via argparse | Comprehensive, well-organized |
| cli_remote.py | 449 | Remote/team/token commands | Complete client-side stubs |
| store.py | 173 | Binary .enc format, CRUD, atomic writes | Solid. Magic bytes + version header. |
| config.py | 134 | Project init, root discovery, config.json | Clean. Walks up like git. |
| remote.py | 136 | HTTP client (stdlib urllib.request) | No external deps — good discipline |
| sync.py | 126 | Push/pull blobs, SHA-256 integrity | Version tracking, conflict-aware |
| auth.py | 94 | Session management, token priority | Handles CI/CD token fallback |
| env.py | 92 | Subprocess injection, .env I/O | Edge cases handled (quotes, escapes) |
| crypto.py | 89 | ChaCha20-Poly1305, BLAKE2b, key export | Clean crypto, correct primitives |

**kv_mcp** (5 modules, ~708 LOC):

| Module | LOC | Role | Assessment |
|--------|-----|------|-----------|
| tools.py | 346 | 7 tools, handlers, JSON schemas | Security-conscious design |
| server.py | 170 | MCP lifecycle, dispatch, profiles | Handles version negotiation |
| protocol.py | 70 | JSON-RPC 2.0 framing | Correct implementation |
| __main__.py | 62 | Entry point with profile flags | Clean |

The security profile system deserves specific callout:
- **Safe** (default): `kv_status`, `kv_envs`, `kv_list`, `kv_run` — the agent can use secrets but never see them
- **Mutate** (opt-in `--allow-mutate`): `kv_set`, `kv_rm` — can modify but not read
- **Reveal** (opt-in `--allow-reveal`): `kv_get` — exposes plaintext (warned in tool description)

This graduated access model is the best MCP security design in the portfolio.

**Private server**: The `kv_server/` directory is gitignored — it's a private FastAPI + SQLAlchemy + Stripe billing implementation. The public repo contains only the client-side code. The CI has a wheel guard that explicitly checks no server code leaks into the PyPI package. This is good security hygiene.

### Code Quality

| Metric | Value | Assessment |
|--------|-------|-----------|
| MCP tests | 50+ (750 LOC) | Strong |
| Test approach | Protocol, profiles, handlers, sessions, edge cases | Comprehensive |
| CI/CD | GitHub Actions: Python 3.10-3.13 matrix | Professional |
| Security scanning | Semgrep Pro daily, 0 blocking findings | Excellent |
| Dependencies | 1 (cryptography) | Minimal |
| Build system | hatchling | Modern |
| Commits | 10 | Clean, purposeful history |

**Test breakdown:**
- Protocol tests (5): JSON-RPC response/error construction, tool definitions
- Profile tests (6): All profile combinations, no internal field exposure
- Handler tests (20+): CRUD round-trips, Unicode, leak prevention, timeout, argv validation, env_names filtering, error handling
- Session tests (8): Full MCP initialization, version negotiation, profile gating, error codes
- Setup tests (4): Auto-config for Cursor, Claude Code, VS Code

**What's well-tested**: The MCP server — protocol, profiles, tool handlers, edge cases, secret leak prevention. The test that verifies `kv_run` doesn't leak secret values into the response is exactly the kind of security test that matters.

**What's not tested**: The local CLI commands (set, get, ls, rm, run, export, import, envs) have no dedicated test file. The crypto module has no tests (though it wraps well-tested library functions). The store module has no tests. These are gaps, but the code is straightforward enough that the risk is manageable.

**Crypto review:**
- ChaCha20-Poly1305: modern AEAD, correct for this use case
- 12-byte random nonce per encryption: correct, no reuse risk
- BLAKE2b keyed hash for per-env key derivation: deterministic, never stored
- Environment name as AAD: prevents cross-env ciphertext replay
- Binary format: magic bytes (KV\x00) + version byte + ciphertext
- Atomic writes: `.enc.tmp` + `os.replace()` on all platforms
- Key permissions: `chmod 0o600` on Unix

No homebrew crypto. No amateur mistakes. The crypto is correct.

### Completeness

**Complete:**
- Encryption engine (ChaCha20-Poly1305 + BLAKE2b)
- Secret CRUD with atomic writes
- Multi-environment support (dev, staging, prod, custom)
- .env import/export with edge case handling
- Subprocess injection (secrets as env vars)
- Key sharing via `kvkey_` tokens
- Remote sync client (push/pull encrypted blobs)
- Team management client (create, invite, members, revoke)
- API token management (create, list, revoke with scopes)
- MCP server with 7 tools across 3 security profiles
- Auto-setup for 3 editors (Cursor, Claude Code, VS Code)
- CI/CD support (env var key, API tokens)
- Diagnostics (`kv doctor` — 8-point system check)
- Security scanning (Semgrep Pro, 0 findings)
- Wheel guard (prevents private code leakage to PyPI)
- Documentation (README, SECURITY.md, DESIGN.md, kv/README.md)
- PyPI distribution

**Missing:**
- CLI command tests (only MCP server is tested)
- Crypto module tests
- Store module tests
- Audit logging (no record of who accessed which secret when)
- Secret rotation workflow
- Secret versioning (history of changes)
- Open source server (server is private)
- `kv_run` output feedback (exit code only, no stdout/stderr)

### Usability

**First-run experience** (from README):
```
pip install kv-secrets
kv init
kv set API_KEY=sk-xxx
kv run python app.py
```
Four commands from install to running with secrets. This is the fastest setup of any project in the portfolio.

**CLI design**: 25+ commands, logically organized. Local commands are top-level (`kv set`, `kv get`, `kv run`). Remote commands are namespaced (`kv cloud signup`, `kv team create`, `kv token list`). Help text is clear. ANSI-colored output for visual clarity.

**MCP setup**: `kv setup claude-code` generates the config automatically. One command. Compare this to the manual MCP configuration other tools require.

**Documentation**: README has a 30-second quickstart, MCP guide, GitHub Actions example, security model explanation, and commands reference. SECURITY.md documents what IS and ISN'T a vulnerability. DESIGN.md explains architecture and monetization. Two README files (root and kv/) cover user and developer perspectives.

**`kv doctor`**: 8-point diagnostic that checks Python version, project presence, key readability, config validity, environment decryptability, MCP module availability, and editor configs. This is the kind of UX polish that makes a tool feel production-ready.

### Sustainability

**Maintenance burden**: Low. Single dependency (`cryptography`) is well-maintained by a large team. stdlib HTTP client means no httpx/requests to track. The MCP server uses custom JSON-RPC (maintenance risk — see Weaknesses), but the protocol is simple and stable.

**Revenue path**: $15/team/month is a reasonable starting point. The free tier (full local + MCP) creates adoption, paid tier (sync + teams + tokens) creates revenue. This is the only project in the portfolio with a monetization plan.

**Bus factor**: Always 1, but well-mitigated. 4 documentation files, clean code, comprehensive MCP tests, SECURITY.md for vulnerability reporting, and the local-first architecture means the tool works even if everything else fails.

**Growth ceiling**: The local CLI handles any reasonable number of secrets. The MCP server handles one agent at a time (stdio transport). For multi-agent or concurrent access, the server would need to evolve. But that's a good problem to have.

### Portfolio Fit

kv-secrets is the most independent project in the portfolio. It doesn't share infrastructure with engram, scroll, svx, or vigil. It doesn't produce or consume engram entries. It solves its own problem completely.

It's also the **proof of shipping**. On PyPI, with CI/CD, with security scanning, with documentation, with a monetization plan. When Satish says "I ship," kv-secrets is the evidence.

The MCP connection is the link: kv-secrets provides secrets to the same AI agents that svx protects, scroll extracts knowledge for, and engram stores knowledge about. The security profiles (safe/mutate/reveal) are a model that svx could learn from — graduated access is better than binary allow/deny.

---

## Strengths

1. **Shipped on PyPI with CI/CD and security scanning.** The only project in the portfolio that external users can `pip install` today. GitHub Actions matrix across Python 3.10-3.13, Semgrep Pro daily scanning with 0 findings, and a wheel guard that prevents private server code from leaking. This is production-grade distribution.

2. **Sound cryptography, correctly implemented.** ChaCha20-Poly1305 + BLAKE2b + random nonces + AAD binding + atomic writes + file permissions. No homebrew algorithms, no key reuse, no timing attacks. The crypto layer is what you'd want from a security-focused tool.

3. **MCP security profiles are genuinely novel.** safe/mutate/reveal tiering with `kv_run` subprocess isolation (agent gets exit code, never sees secrets). No other tool in the market does "give your AI agent safe access to secrets." The 50+ MCP tests specifically verify no secret leakage.

4. **Zero-config start, one-dependency philosophy.** `kv init` → working encrypted secrets. Only `cryptography` as a dependency. stdlib urllib for HTTP. No vendor lock-in, no cloud requirement, no config file editing to get started.

5. **Monetization plan and product thinking.** $15/team/month for sync + teams + tokens. Free local tier for adoption. DESIGN.md documents the business model alongside the architecture. This is a product, not just a project.

6. **Documentation quality.** 4 documentation files covering users (README), security (SECURITY.md), architecture (DESIGN.md), and technical reference (kv/README.md). The README's 30-second quickstart, MCP guide, and GitHub Actions example show user empathy.

---

## Weaknesses

1. **CRITICAL: Master key stored in plaintext on disk.** `.secrets/key` is readable by any process running as the same user. An AI agent with Bash access can `cat .secrets/key` to get the master key, or `kv ls --reveal` / `kv get KEY` to read any secret directly, completely bypassing MCP profiles. **Confirmed in live testing during this review.** **Fix**: Passphrase-wrap the master key. On-disk key encrypted with PBKDF2(passphrase) + ChaCha20-Poly1305. MCP server receives passphrase at startup, holds decrypted key in process memory only. ~50 lines of additional crypto code.

2. **No CLI command tests.** The local CLI (set, get, ls, rm, run, export, import) has no dedicated test file. The MCP server is well-tested (50+), but the 874-line cli.py and 449-line cli_remote.py are untested. **Fix**: Add `tests/test_cli.py` covering the core CRUD operations and edge cases (set without value, rm nonexistent key, run with timeout, import malformed .env).

3. **No crypto or store module tests.** crypto.py wraps library functions correctly, but key derivation, encryption roundtrip, and key export/import should be tested. store.py's atomic write, binary format, and multi-environment isolation should be verified. **Fix**: Add `tests/test_crypto.py` and `tests/test_store.py`. The crypto roundtrip test alone would catch any future regressions.

4. **Custom MCP protocol implementation.** `kv_mcp` implements JSON-RPC 2.0 from scratch instead of using the `mcp` library (FastMCP) that svx and scroll use. The MCP landscape is evolving — protocol updates require manual implementation. **Fix**: Evaluate migration cost vs. benefit. The current implementation works and is well-tested, so this is maintenance risk, not a bug.

5. **`kv_run` returns exit code only.** By design (security), but the AI agent gets no feedback about command failures. **Fix**: Capture stdout/stderr, redact substrings matching known secret values before returning.

6. **Remote server is private.** Users can't self-host, can't audit the server code. **Fix**: Document the server situation clearly — open-source it, provide API docs, or state it's an upcoming paid service.

7. **No audit logging.** No record of who accessed which secret when. **Fix**: Optional append-only JSONL logging for all operations (key names, not values).

---

## Recommendations (Priority Order)

1. **Ship passphrase-wrapped master key.** This is the #1 priority — a confirmed vulnerability with a clean fix. Encrypt `.secrets/key` with PBKDF2(passphrase). MCP server takes passphrase at startup and holds decrypted key in RAM only. Agent can't enter interactive prompts → blocked. ~50 lines of crypto, updates to `kv init`, `kv unlock`/`kv lock` commands, and MCP server startup.

2. **Document the threat model explicitly.** SECURITY.md should state: "MCP profiles protect against secret exposure through the MCP channel. An agent with shell access can bypass profiles via the CLI. Use the passphrase feature (v0.2.0+) or OS-level sandboxing to restrict shell access to secrets."

3. **Add CLI and crypto/store tests.** The MCP server is well-tested (50+ tests), but the local CLI has zero tests. Even 20 tests covering CRUD + import/export + subprocess injection would catch regressions.

4. **Add stdout/stderr redaction to `kv_run`.** Capture output, redact known secret values, return sanitized output. Security-preserving but usable.

5. **Add audit logging.** Append-only JSONL for all operations. Key names (not values), timestamps, source (CLI/MCP). This strengthens the security story and pairs well with the passphrase feature.

---

## Risks

| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|-----------|
| Agent reads secrets via Bash (CONFIRMED) | **Confirmed** | **Critical** | Passphrase-wrapped master key |
| Agent reads cached key during unlock window | Medium | High | MCP-in-memory only, no file cache |
| CLI commands have untested edge cases | Medium | Medium | Add test_cli.py |
| MCP protocol evolves, custom impl falls behind | Medium | Medium | Monitor MCP changes, evaluate FastMCP |
| Private server creates trust gap | Medium | Low | Document server clearly |
| Competitor ships MCP-native secrets | Low | High | Move fast, MCP profiles are the moat |

---

## Verdict

kv-secrets is the most complete and most shippable project in the portfolio. It's on PyPI, has CI/CD with security scanning, comprehensive documentation, a monetization plan, and the MCP security profiles are a genuinely novel idea. The live vulnerability finding (agent Bash bypass of MCP profiles) is serious but fixable — the passphrase solution is ~50 lines of code and uses the same security model as ssh-agent and gpg-agent. The vulnerability exists in every local secret manager; kv-secrets is the only one that even considers AI agent access as a threat vector.

**Grade: A-** (restored from B+ — passphrase + TOTP shipped 2026-03-21)
Best execution in the portfolio — shipped, documented, security-scanned, monetization-planned. Was B+ due to confirmed MCP profile bypass via Bash. Passphrase-wrapped key + TOTP 2FA + output redaction + password blocklist now shipped. The vulnerability that prompted the downgrade is fixed. Remaining gaps: CLI command tests, server documentation.

