The Kvendra CLI
Alpha. The CLI works and is used daily, but it is early: expect rough edges, and the command surface and on-disk formats may still change. It is also optional — the hosted service and a self-hosted Platform work fully without it. Release binaries are not yet signed.
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.aws | s3_sync, s3_cp, cloudfront_invalidate, lambda_invoke | Cloud operations (object sync/copy, CDN cache invalidation, function invoke) with vault-injected credentials. |
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
From crates.io (or track the latest main from git):
cargo install --locked kvendra
# or
cargo install --locked --git https://github.com/KvendraAI/kvendra-cli kvendra
kvendra --version Pre-built binaries for macOS, Linux and Windows are attached to each GitHub release. They are unsigned: on macOS Gatekeeper may warn about an unidentified developer, on Windows SmartScreen about an unknown publisher. There is no Homebrew formula yet.
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 Inspecting an allowlist
kvendra secret show-allowlist <profile_id>
Prints a profile's allowlist YAML and its HMAC status —
VALID, TAMPERED, NO_HMAC, or
UNVERIFIED when the vault is locked. It reuses the active
session, never prompts for a password and never opens the secret itself.
It exits non-zero on TAMPERED or NO_HMAC, so you
can use it in a check script. An allowlist copied from another profile,
even with a valid signature, reports TAMPERED — exactly as
the broker would treat it.
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.
Local variables
Some values belong to one user on one machine — your
workspace path, the AWS profile of this laptop, a local host or port. They
should not live in shared knowledge-base text, and an agent should not
need to know them. Since 0.7.0 the CLI keeps them as
local variables, referenced in the KB as
{{lvr:<key>}}.
kvendra vars set <key> --type <type> # type: path, host, port, profile_id, string
kvendra vars verify <key>
kvendra vars list # never prints values
kvendra vars unset <key> -
set,verifyandunsetneed a real terminal and your master password: the value is typed by a human on the TTY — never passed in argv, an environment variable or through an agent. - Values are stored in their own encrypted file in the vault (included in backups; a restore marks every value unverified until you verify it again).
- Only the broker substitutes them, inside its primitives (a working directory, a profile id, a bounded argument, a URL). The allowlist is checked against the resolved arguments, each value is validated by its type, and every local value is masked in results and errors before they reach the caller.
- The masking removes literal occurrences; a value that the executed command itself re-encodes (base64, hex, split) is not detected, so keep local variables out of commands that transform their arguments.
-
On Windows,
vars setandvars verifyare not supported yet.
How a variable is declared in the knowledge base is covered in Private values and local variables.
Hosted sign-in: silent session refresh
If you use the CLI against the hosted service, sign in once with
kvendra login --pro. Since 0.8.0 that session
refreshes itself silently: the commands that talk to the
hosted service (such as kvendra backup and
kvendra notifs) renew the tokens before calling when they are
about to expire, so you no longer have to log in again every hour. The refresh token is stored in the session directory
with owner-only permissions and is never logged.
-
kvendra session infoshows whether refresh is available for your session;kvendra logoutremoves it. -
If the identity provider rejects the refresh, the current tokens keep
working until they expire, and then the CLI asks you to run
kvendra login --proagain. Network errors and server-side hiccups are treated as transient and do not end the session. - Sessions created by older versions have no refresh token. Update to the latest version and log in once more to get silent refresh; older versions will need a fresh login whenever the short-lived token expires.
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.