The Kvendra CLI
Pre-alpha. The CLI is under active development — the
kvendra binary is installed from source, the crates.io name is
reserved but not yet published, and a Homebrew formula and signed builds are
planned. Treat commands and defaults below as the current shape, not a
frozen contract.
The CLI is where Kvendra meets your machine. It is a foundational security layer — but optional to get started: the Platform and the Skills work without it, and you add it only when you want your agent to run real ops (a git push, a deploy) without exposing your tokens. It is a single Rust binary that does two jobs at once: it keeps your secrets in a local zero-knowledge vault, and it acts as an MCP capability broker that runs sensitive operations on behalf of a tool or agent — without ever handing the underlying credential to that caller.
The Kvendra CLI is a foundational security layer — optional to get started: a Rust (edition 2024, MSRV 1.85+) binary (kvendra, Apache-2.0).
The kvendra CLI is a Rust binary: a foundational security layer that is optional to get started. It is a zero-knowledge local vault (Level 2) plus an MCP capability broker, an HMAC-signed audit log, a detection layer and a TUI — you add it when you want your agent to run real operations without exposing your tokens.
The open-core primitives
The broker exposes 7 open-core primitives. Each primitive receives an injected secret resolved by the vault — the model issuing the call never sees the raw value.
| Primitive | Operations | Notes |
|---|---|---|
kvendra.git | clone, push, pull, commit, tag | SSH-only authentication by threat model — no inline tokens. |
kvendra.github | read_repo, read_issue, update_issue, update_repo, release, add_topics, create_issue, list_issues | Read ops are auth-light; create_issue is destructive and body-sanitized. |
kvendra.npm | publish, deprecate, read_metadata | Package registry operations. |
kvendra.pypi | upload, read_metadata | Python package registry operations. |
kvendra.http | request | A single generic outbound HTTP request primitive. |
kvendra.shell | exec | Run an allowlisted shell command. |
Beyond these, an audit-marked escape hatch exists: kvendra.unsafe.raw_token — an audit-marked escape hatch, always flagged unsafe in the audit log.
Introspection
kvendra capabilities (and —pretty) emits a canonical JSON manifest of the broker’s primitive surface: broker_version, schema_version, and each primitive with its ops, destructive ops and vault profile pattern. Auth-less, read-only, no vault unlock required.
The vault
The vault lives in a per-user home directory and holds: a config file (HMAC sidecar), a SQLite audit database (WAL, HMAC-chained), Argon2id-hashed recovery codes, a sentinel blob, AES-256-GCM secret blobs per profile, and HMAC-signed allowlist YAMLs per profile.
Cryptography: Argon2id KDF, AES-256-GCM AEAD, an HMAC chain over the audit log, a sentinel blob and recovery codes.
Modes of operation: Local (default): zero-knowledge vault, secrets resolved locally. A remote-broker resolver mode exists for hosted deployments via OIDC PKCE login.
What it is, in one idea
The design separates the thing that decides what to do (the LLM) from the thing that holds the keys (the vault). The model never sees a token. It calls a primitive over MCP; the broker resolves the secret, runs the operation in a subprocess outside the model's context, and returns only a sanitized result. The credential is used, but never exposed to the caller that asked for it.
The user flow, step by step
1. Install
Build from source while the project is pre-alpha:
cargo install --git https://github.com/KvendraAI/kvendra-cli kvendra
That gives you the kvendra binary. (Not on crates.io yet; brew
is pending.)
2. kvendra init — create the vault (once)
Run this a single time to set up your local vault. It establishes:
- a master password, from which the encryption key is derived with Argon2id (a memory-hard KDF);
- a BIP-39 recovery phrase — 12 words, shown exactly once — for recovering access if you forget the master password;
- 8 single-use recovery codes for critical actions.
Save the recovery material when it is shown — it is never displayed again. Nothing in clear text touches the disk: the vault stores only encrypted blobs.
3. Profiles — add a set of credentials
kvendra secret add <profile_id> --secret-file <path>
A profile is one named set of credentials, encrypted with
AES-256-GCM and stored as a per-profile blob in the vault
directory. Each profile carries a type — github_pat,
npm_token, aws_credentials,
generic, and so on — so the broker knows how to inject it.
4. Allowlist policies — declare what is permitted
Each profile gets a YAML allowlist, signed with HMAC, that declares precisely which operations and parameters the broker will run for it. Per primitive, for example:
- git — allowed repos / refs, forbidden args;
- shell — allowed binaries, argument constraints, a working-directory pattern;
- http — a URL pattern (regex) and forbidden headers.
Defaults are restrictive: anything ambiguous or not explicitly allowed is rejected. The HMAC signature is re-verified on every broker start, so an allowlist edited out of band is detected rather than silently honoured.
5. kvendra unlock — in your terminal, not the agent
Unlocking derives the key from your master password and writes a machine-bound session — tied to this host, user and home — with a time-to-live. You run this in a real terminal; the broker requires a genuine TTY, so a captured environment or an MCP client cannot unlock the vault on its own.
- Default TTL is 4 hours; the configurable maximum is 24 hours.
-
Flags include
--ttl(set the lifetime),--extend(refresh an active session),--no-keychain, and--recover(reset the master password via the BIP-39 phrase).
6. Use it with Claude Code
The MCP client launches kvendra mcp serve over stdio. The
broker reads the existing unlocked session —
the master password never enters the MCP process. When the
LLM calls a primitive, the vault resolves the secret and injects it
only into the operation's subprocess (an environment
variable or an ephemeral credential helper). The response passes through a
detection/sanitization layer and is returned to the agent
with the credential stripped out.
# Inspect the broker's primitive surface — no unlock required
kvendra capabilities --pretty The seven primitives (plus an escape hatch)
The broker exposes seven open-core capability primitives —
kvendra.git, kvendra.github,
kvendra.npm, kvendra.pypi,
kvendra.aws, kvendra.http and
kvendra.shell (their exact operations are in the manifest
above) — plus one escape hatch:
-
kvendra.unsafe.raw_token— explicit opt-in, rate-limited per session, requires a stated reason, and is always flagged in the audit log. It is the last resort when no structured primitive fits, not a normal path.
Approval modes
You choose how much the broker prompts before acting:
silent— never prompts (for non-interactive contexts);ask— prompts on every call;-
ask-destructive— the default: prompts only for operations the manifest marks destructive.
Under MCP the prompt is a native operating-system modal (macOS today; native Touch ID is pre-alpha). The mode is configurable by environment variable, per profile, or in global config — the most specific setting wins.
Break-glass vs. the escape hatch
These are two different things, and the distinction matters:
- Break-glass (
kvendra bypass/kvendra protect) temporarily grants a capability in workspace mode. Grants are signed with ed25519, scoped to a specificprimitive.opwith a TTL, and revocable withprotect. It widens what is allowed, briefly and accountably. - The escape hatch (
kvendra.unsafe.raw_token) hands a raw token to the caller. It does not widen an allowlist; it bypasses the whole brokering model for one call, which is why it is opt-in, rate-limited and always flagged.
Audit and recovery
Every brokered operation is written to a local HMAC-chained audit log (SQLite), where each entry commits to the one before it. You can verify the chain end to end:
kvendra audit --verify # detects tampering row by row If access is ever lost, recovery has two layers: the BIP-39 phrase resets the master password, and the single-use recovery codes authorize critical actions.